参考
谷粒商城p158:https://www.bilibili.com/video/BV1np4y1C7Yf?p=158
redis官网:http://www.redis.cn/commands/set.html
提要
分布式场景下将原有的本地锁换为,基于redis的setnx命令的分布式锁
getCatalogJsonFromDb
:从数据库查数据
getCatalogJsonFromDbWithLocalLock
:利用本地锁查数据
getCatalogJsonFromDbWithRedisLock
:利用redis的setnx命令的分布式锁查数据,现需要完成的
分布式锁演进-阶段一
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public Map<String, List<Catalog2Vo>> getCatalogJsonFromDbWithRedisLock() { Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","111"); if(lock){ Map<String, List<Catalog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb(); redisTemplate.delete("lock"); return catalogJsonFromDb; }else{ return getCatalogJsonFromDbWithRedisLock(); }
}
|
问题
setnx
占好了位置,业务代码异常或程序宕机,没有执行删锁逻辑,死锁!!!
解决
分布式锁演进-阶段二
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public Map<String, List<Catalog2Vo>> getCatalogJsonFromDbWithRedisLock() { Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","111"); if(lock){ redisTemplate.expire("lock", 30, TimeUnit.SECONDS); Map<String, List<Catalog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb(); redisTemplate.delete("lock"); return catalogJsonFromDb; }else{ return getCatalogJsonFromDbWithRedisLock(); }
}
|
问题
- 加锁与设置过期时间非原子操作,所以仍会出现死锁问题
解决
分布式锁演进-阶段三
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public Map<String, List<Catalog2Vo>> getCatalogJsonFromDbWithRedisLock() { Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS); if(lock){ Map<String, List<Catalog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb(); redisTemplate.delete("lock"); return catalogJsonFromDb; }else{ return getCatalogJsonFromDbWithRedisLock(); }
}
|
问题
- 如果业务时间过长,我们的锁过期自动删除,这时直接删锁,有可能把别人正在持有的锁删除了
解决
- 占锁时指定
uuid
保证唯一性,删锁需要验证是否是自己的锁
分布式锁演进-阶段四
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public Map<String, List<Catalog2Vo>> getCatalogJsonFromDbWithRedisLock() { String uuid = UUID.randomUUID().toString(); Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS); if(lock){ Map<String, List<Catalog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb(); String lockValue = redisTemplate.opsForValue().get("lock"); if(uuid.equals(lockValue)){ redisTemplate.delete("lock"); } return catalogJsonFromDb; }else{ return getCatalogJsonFromDbWithRedisLock(); }
}
|
问题
- 注意在
redis
,get
锁的值时,即String lockValue = redisTemplate.opsForValue().get("lock");
,这时可能redis
还存在我们的锁,这时返回的正是我们uuid
,但是因为网络传输的延时,我们要执行delete
操作时,我们的锁已经因为过期策略删除了,所以虽然这是的锁不是我们的,但程序代码仍然会执行删除锁(并非我们的),本质仍是非原子操作问题
解决
分布式锁演进-阶段五
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public Map<String, List<Catalog2Vo>> getCatalogJsonFromDbWithRedisLock() {
String uuid = UUID.randomUUID().toString(); Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS); Map<String, List<Catalog2Vo>> catalogJsonFromDb = null; if (lock) { try { catalogJsonFromDb = getCatalogJsonFromDb(); } finally {
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; Long unlock = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid); } return catalogJsonFromDb;
} else { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } return getCatalogJsonFromDbWithRedisLock(); }
}
|
总结
使用redis
做分布式锁
- 加锁和设置过期时间的原子性问题
- 解锁与验证锁的归属的原子性问题
- 还有业务时间与过期时间的设置,有时需要延长过期时间
可以考虑使用Redisson