Redis分布式锁
·
一、什么是分布式锁
分布式锁(Distributed Lock):是一种在分布式系统中控制多个进程或服务对共享资源访问的机制,保证在同一时刻,只有一个客户端可以操作某个资源,避免数据竞争和并发冲突。
单机锁:像Java的synchronized或者C#的lock,只能在单机jvm/进程里生效。
分布式锁:能在多个机器、多个进程间生效,保证全局唯一性。
二、为什么用Redis做分布式锁
Redis是一个高性能的内存数据库,支持原子操作,如SEINX、DEL、EXPIRE等,非常适合做分布式锁。
优点:简单高效,Redis操作速度快;支持过期时间,防止死锁;易于扩展,适用于多节点部署。
三、Redis分布式锁的原理
基本思路:
1.客户端尝试向Redis写入一个键(key)作为锁标识。
2.使用原子操作确保同一时间只有一个客户端成功写入。
3.锁设置过期时间,避免客户端异常导致锁永远存在。(锁的时间>业务逻辑处理时间)。
4.操作完成后,删除锁。

四、实现用例
线程池创建用来模拟并发
package com.test.redisdemotest.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@EnableAsync //开启异步任务
@Configuration //配置类
public class ThreadPoolConfig {
@Bean
public Executor taskExecutor() {
// 创建线程池
ThreadPoolTaskScheduler scheduler=new ThreadPoolTaskScheduler();
// 设置线程池大小 获取可用处理器乘于二
scheduler.setPoolSize(Runtime.getRuntime().availableProcessors()*2);
// 设置线程名称前缀
scheduler.setThreadNamePrefix("my-scheduler-");
// 尝试等待线程池所有任务完成,最多等待60秒关闭
scheduler.setAwaitTerminationSeconds(60);
// 拒绝策略,CallerRunsPolicy 表示由调用线程处理该任务
scheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return scheduler;
}
}
操作Redis分布式工具类
package com.test.redisdemotest.util;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.concurrent.TimeUnit;
/**
* 分布式锁工具类
*/
@SuppressWarnings({"unchecked", "rawtypes"})
@Component
@Slf4j
@RequiredArgsConstructor //构造器注入
public class DistributedLockUtil {
//注入redisTemplate Redis模板
public final RedisTemplate redisTemplate;
/**
* 分布式锁处理操作方法,当前抢占到资源的线程可以实现数据的锁定
*
* @param key 数据锁标记,不同业务可能有不同的标记
* @param userId 操作用户ID,依据该数据可以判断是否重复获取锁
* @param expireTime 分布式锁有效时间(牵扯到自动失效问题)
* @param timeUnit 分布式锁有效时间单位
* @return 获取分布式锁成功返回true,否则返回false
*/
public boolean lock(String key, String userId, long expireTime, TimeUnit timeUnit) {
// 保存的数据
String value = userId + ":" + System.currentTimeMillis();
// 判断当前redis是否有指定的key数据,如果没有则进行保存 返回true ,如果存在就则不保存 返回false
if (Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, timeUnit))) {
return true;
}
// 如果存在则获取当前key的值,判断是否是当前用户获取的锁,如果是则进行续期
var lockValue = redisTemplate.opsForValue().get(key);
String executor = lockValue != null ? lockValue.toString() : null;
if(StringUtils.hasText(executor)){
if(executor.startsWith(userId)){
// 续期 同一个人又重复抢到了
redisTemplate.expire(key,expireTime,timeUnit);
return true;
}
}
return false;
}
/**
* 释放分布式锁
* @param key 锁的key
* @param userId 操作用户ID
*/
public void unlock(String key, String userId) {
var lockValue = redisTemplate.opsForValue().get(key);
String executor = lockValue != null ? lockValue.toString() : null;
if(StringUtils.hasText(executor)){
if(executor.startsWith(userId)){
// 删除数据
redisTemplate.delete(key);
}
} else {
log.info("释放锁失败,锁不存在");
}
}
}
模拟业务操作和进行上锁解锁
package com.test.redisdemotest.task;
import com.test.redisdemotest.util.DistributedLockUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
@RequiredArgsConstructor
public class ResourceTask {
// Key 名称 实际操作时候key根据业务来进行,比方调度发车,key就是车牌号,防止多个调度台给同一辆车同一个时间派发任务
private static final String BUSINESS_KEY = "business_key";
// 分布式工具
private final DistributedLockUtil distributedLockUtil;
@Async
public void handle(String userId) {
// 获取分布式锁
boolean lock = distributedLockUtil.lock(BUSINESS_KEY, userId, 300, TimeUnit.MILLISECONDS);
if (lock) {
log.info("用户{}获取到锁 ", userId);
// 模拟业务处理
try {
Thread.sleep(200);
} catch (InterruptedException e) {
log.error("错误异常:{}", e.getMessage());
}finally {
// 释放分布式锁
distributedLockUtil.unlock(BUSINESS_KEY, userId);
log.info("用户{}释放锁", userId);
}
} else{
log.info("用户{}未获取到锁", userId);
}
}
}
进行测试
package com.day03.redisdemotest;
import com.day03.redisdemotest.task.ResourceTask;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
@SpringBootTest(classes = RedisDemoTestApplication.class)
@ExtendWith({SpringExtension.class})
@WebAppConfiguration
@Slf4j
public class ResourceTaskTest {
@Autowired
private ResourceTask resourceTask;
@Test
public void testHandle() throws InterruptedException {
log.info("===========几乎三个同时进行===========");
for (int i = 0; i < 3; i++) {
resourceTask.handle("X-X" + (i + 1));
}
// 模拟中间10秒没有执行
Thread.sleep(1000 * 10);
log.info("===========三个中间间隔100毫秒===========");
for (int i = 0; i < 8; i++) {
Thread.sleep(100);
resourceTask.handle("Y-Y" + (i + 1));
}
}
}

由此可以看到,在没有释放锁或者锁到期的时间内只能有一个用户进行操作。
更多推荐



所有评论(0)