分布式锁基本原理
redis 中有一个 SETNX 命令,该命令会向 redis 中保存一条数据,如果不存在则保存成功,存在则返回失败。
我们约定保存成功即为加锁成功,之后加锁成功的线程才能执行真正的业务操作
分布式锁演进一
核心代码:
public Map<String, List<Catalogs2Vo>> getcatalogJsonFromDBWithRedisLock() { //1. 占分布式锁 Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111"); if(lock){ //加锁成功 执行业务 Map<String, List<Catalogs2Vo>> dataFromDb = getDataFromDb(); //得到锁之后释放 删除锁 redisTemplate.delete("lock"); return dataFromDb; } else{ //加锁失败 再次请求 休眠100ms重试 getcatalogJsonFromDBWithRedisLock(); } return getDataFromDb(); }
问题:setnx占好了位,业务代码异常或者程序在页面过程中宕机。没有执行删除锁逻辑,这就造成了死锁
解决: 设置锁的自动过期,即使没有删除,会自动删除
分布式锁演进二
按照之前的弊端添加了过期时间,自动删除锁
问题:setnx设置好,正要去设置过期时间,宕机。又死锁了
解决: 设置过期时间和占位必须是原子的。redis支持使用setnx ex命令
分布式锁演进三
原子命令:设置过期时间和锁
set lock 1111 EX 300 NX
问题:如果由于业务时间很长,锁自己过期了,其他线程又进来创建锁执行业务代码,我们直接删除,有可能把别人正在持有的锁删除了
解决: 占锁的时候,值指定为uuid,每个人匹配是自己的锁才删除。
分布式锁演进四
设置uuid保证自己不能删除别人的锁
问题: 如果正好判断是当前值,正要删除锁的时候返回值的时候,锁已经过期,别人已经设置到了新的值。那么我们删除的是别人的锁
解决:删除锁必须保证原子性。使用redis+Lua脚本完成
分布式锁最终版
保证加锁【占位+过期时间】和删除锁【判断+删除】的原子性。更难的事情,锁的自动续期
压测访问
只有一个服务从数据库拿到了数据