本文已参与「新人创作礼」活动,一起开启掘金创作之路。 此方法主要理解 分布式锁工作原理:推荐使用 学习redisson 了解工作原理: blog.csdn.net/qq_17040587… 终极方案用springCache的例子 blog.csdn.net/qq_17040587…
简单分布式锁(基于redis)
此文章限于理解分布式工作原理,实际开发推荐后续文章的the Redlock 设计.java对应的是Redisson.
核心:原子加锁 原子解锁
加锁相关redis语句:
set lock 1 NX 解释:lock键无占用才设置键lock值为1
set lock 1 EX 300 NX 解释:lock键无占用时才设置键lock值为1 有效时间为:300s
解锁相关语句:
if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end
思路过程代码:
/**
* redis分布式锁
* @return
*/
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
//1.占分布式锁 去redis占坑 【set lock 1 EX 30 NX】 lock键无占用时才设置键lock值为1 有效时间为:300s
String uuid = UUID.randomUUID().toString();
//设置300s的过期时间 放置执行业务过程中 锁过期了。 让过期时间靠谱的大约业务时间 后面再执行脚本解锁
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(RedisConstant.INDEX_CATALOGJSON_LOCK, uuid,300,TimeUnit.SECONDS);
if(lock){
System.out.println("获取分布式锁成功");
//加锁成功 ... 执行业务
Map<String, List<Catelog2Vo>> dataFromDb = null;
try{
dataFromDb = getDataFromDb();
}finally {
//保证删除的锁 是自己的锁再删除
// 判断的原因:lock过期后,会有一个请求x可以进来,如果此时删锁,会导致x的占位失败会导致多个人进来
//获取值锁 远程
//String lockValue = stringRedisTemplate.opsForValue().get(RedisConstant.INDEX_CATALOGJSON_LOCK);
//瞬时异常: lockValue去请求返回的路上过期 lockValue带回来的确实是自己存的内容,此时lock已经被人占用成功,也会误删别人的锁
// if(uuid.equals(lockValue)){
//解锁远程
// stringRedisTemplate.delete(RedisConstant.INDEX_CATALOGJSON_LOCK); //删锁失败 造成思索
// }
// ↓ 优化
//获取值锁 + 对比成功删除 合并原子操作 脚本Lua脚本解锁
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
Long unlock = stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),
Arrays.asList(RedisConstant.INDEX_CATALOGJSON_LOCK), uuid);
}
return dataFromDb;
}else{
System.out.println("获取分布式锁失败 等待重试..");
//加锁失败 ...重试 synchronized()
//休眠100ms重试
try {
Thread.sleep(200);
}catch ( Exception e){
}
return getCatalogJsonFromDbWithRedisLock();// 自旋方式
}
}
两个必要性原子操作:
加锁 :设置锁独占 设置过期时间
解锁:查询锁值 删除所
整合:
/**
* redis分布式锁
* @return
*/
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
//1.占分布式锁 去redis占坑 【set lock 1 EX 30 NX】 lock键无占用时才设置键lock值为1 有效时间为:300s
String uuid = UUID.randomUUID().toString();
//设置300s的过期时间 放置执行业务过程中 锁过期了。 让过期时间靠谱的大约业务时间 后面再执行脚本解锁
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(RedisConstant.INDEX_CATALOGJSON_LOCK, uuid,300,TimeUnit.SECONDS);
if(lock){
System.out.println("获取分布式锁成功");
//加锁成功 ... 执行业务
Map<String, List<Catelog2Vo>> dataFromDb = null;
try{
dataFromDb = getDataFromDb();
}finally {
//获取值锁 + 对比成功删除 合并原子操作 脚本Lua脚本解锁
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
Long unlock = stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),
Arrays.asList(RedisConstant.INDEX_CATALOGJSON_LOCK), uuid);
}
return dataFromDb;
}else{
System.out.println("获取分布式锁失败 等待重试..");
//加锁失败 ...重试 synchronized()
//休眠100ms重试
try {
Thread.sleep(200);
}catch ( Exception e){
}
return getCatalogJsonFromDbWithRedisLock();// 自旋方式
}
}