一、redis分布式锁基本信息
1.详细讲解:
Redis 分布式锁是一种用于控制分布式系统中多个进程对共享资源的并发访问的机制。通过 Redis 的原子操作和过期时间功能,可以实现一个简单而有效的分布式锁。接下来,我们将详细介绍其工作原理、基本操作步骤以及代码实现,并提供详细的注释。
2.基本原理
2.1获取锁:
使用 SET 命令尝试设置一个键,并使用 NX 参数(如果键不存在则设置)和 PX 参数(设置键的过期时间)。
如果 SET 操作返回成功(即 OK),则表示获取锁成功。
2.2释放锁:
释放锁时需要确保只有持有锁的客户端才能释放锁,因此需要在释放锁时检查锁的值是否是当前客户端的标识。
使用 Lua 脚本来保证原子性:检查锁的值并删除锁。
二、代码示例
1.redis加锁以及解锁的工具类
1.1注意事项:
1.1.1 由于RedisTemplate交由spring管理,所以我们工具类也需要注册给spring,之后通过注入方式使用工具类,这样redisTemplate就不会是null;
1.1.2 用脚本的方式执行redisTemplate.execute会出现异常java.lang.UnsupportedOperationException: io.lettuce.core.output.ValueOutput does not support set(long),好像是返回值无法转换,由于redis版本不匹配,我这边redis版本是5.0.14.1;(暂未解决脚本执行后续会修改);
@Slf4j
@Component
public class LockUtil {
@Resource
private RedisTemplate<String, Object> redisTemplate;
//定义变量
private final Object RELEASE_SUCCESS = 1;
private final Object RELEASE_ERROR = 0;
/**
* 以阻塞方式的获取锁
* @param key key
* @param value value
* @param lockTimeout 锁超时时间
* @param getTimeout 获取锁超时时间
* @return
*/
public boolean lockBlock(String key, String value, long lockTimeout, long getTimeout, TimeUnit timeUnit) {
long start = System.currentTimeMillis();
//循环执行是否能加锁成功判断
while (true) {
//检测是否超时
if (System.currentTimeMillis() - start > getTimeout) {
log.error(Thread.currentThread().getName() + "get lock timeout");
return false;
}
//执行set命令 ,如果返回 true,表示获取锁成功;如果返回 false,表示获取锁失败。
Boolean absent = redisTemplate.opsForValue().setIfAbsent(key, value, lockTimeout, timeUnit);
//是否成功获取锁
if (absent != null && absent) {
return true;
} else {
log.info(Thread.currentThread().getName() + "get lock fail:{},{}", key, value);
}
}
}
/**
* 解锁:由于我这边无法执行redis脚本,一直返回异常,下面redis操作并非是原子性操作
* @param key 加锁key
* @param value 锁value
* @return 返回是否解锁成功
*/
public boolean unlock(String key, String value) {
Object result = 0;
String o = (String) redisTemplate.opsForValue().get(key);
if (o != null && o.equals(value)) {
Boolean delete = redisTemplate.delete(key);
if (delete){
result = 1;
}
}
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
log.error(Thread.currentThread().getName() + "unlock error");
return false;
}
}
2.调用实现
采用多线程方式模拟调用
@Slf4j
@RestController
public class TestController {
@Resource
private LockUtil lockUtil = new LockUtil();
@GetMapping(value = "/redisLock")
public void testRedis(@RequestParam(value = "key") String key) throws ExecutionException, InterruptedException {
ExecutorService threadPoolTaskExecutor = Executors.newFixedThreadPool(10);
CompletableFuture<Void> completableFuture1 = this.getCompletableFuture(threadPoolTaskExecutor, key);
CompletableFuture<Void> completableFuture2 = this.getCompletableFuture(threadPoolTaskExecutor, key);
CompletableFuture.allOf(completableFuture1, completableFuture2).get();
threadPoolTaskExecutor.shutdown();
}
private CompletableFuture<Void> getCompletableFuture( ExecutorService threadPoolTaskExecutor,String key){
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
String value = UUID.randomUUID().toString();
try {
if (lockUtil.lockBlock(key, value, 3L, 10L, TimeUnit.SECONDS)) {
log.info(Thread.currentThread().getName() + "获取锁成功,value is {}", value);
Thread.sleep(2000);
} else {
log.info(Thread.currentThread().getName() + "获取锁失败,value is {}", value);
}
} catch (InterruptedException e) {
log.info(Thread.currentThread().getName() +"获取锁异常,value is {}", value);
} finally {
if (lockUtil.unlock(key, value)) {
log.info(Thread.currentThread().getName() + "释放锁,value is {}", value);
}
}
}, threadPoolTaskExecutor);
return completableFuture;
}
}
3.测试结果显示
4.上面方法解决的问题
4.1通过对于redis key添加过期防止锁无法释放造成死锁;
4.2通过加锁时间限制防止加锁失败一直加锁,造成死锁;
4.3通过value比对保证解的锁是自己持有的;
5.上面方法存在的问题
5.1代码执行超过redis存放时间:
redis释放之后,别人可以获取锁这样锁就相当于失效,解决方案:看门狗机制,我们redisson已经解决上面问题了
5.2代码实现
1.redisson配置
@Configuration
@Slf4j
public class RedissonConfig {
@Bean
public RedissonClient createRedissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379");
//.setPassword(""); // 如果没有密码,可以省略这一行
return Redisson.create(config);
}
}
2.代码调用实现
@RestController
public class TestController {
@Resource
private RedissonClient redissonClient;
@GetMapping(value = "/redisLock")
public void testRedis(@RequestParam(value = "key") String key) throws ExecutionException, InterruptedException {
ExecutorService threadPoolTaskExecutor = Executors.newFixedThreadPool(10);
CompletableFuture<Void> completableFuture1 = this.getCompletableFuture(threadPoolTaskExecutor, key);
CompletableFuture<Void> completableFuture2 = this.getCompletableFuture(threadPoolTaskExecutor, key);
CompletableFuture.allOf(completableFuture1, completableFuture2).get();
threadPoolTaskExecutor.shutdown();
}
private CompletableFuture<Void> getCompletableFuture(ExecutorService threadPoolTaskExecutor, String key) {
RLock lock = redissonClient.getLock(key);
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
try {
// 尝试获取锁,等待时间10秒,上锁时间5秒
if (lock.tryLock(10, 5, TimeUnit.SECONDS)) {
log.info(Thread.currentThread().getName() + "获取锁成功");
Thread.sleep(2000);
} else {
log.info(Thread.currentThread().getName() + "获取锁失败");
}
} catch (InterruptedException e) {
log.info(Thread.currentThread().getName() + "获取锁异常");
} finally {
lock.unlock();
log.info(Thread.currentThread().getName() + "释放锁成功");
}
}, threadPoolTaskExecutor);
return completableFuture;
}
}
3.运行结果
不足之处,望海涵,希望大佬指点;