分布式锁使用场景
现在的系统都是集群部署,每个服务都不是单节点的了。比如库存服务,可能部署到3台机器上分别命名为节点1,节点2,节点3。库存服务需要扣减库存,扣减库存肯定需要锁吧,如果使用Lock
或者synchronized
,只能锁住自己的节点。而从前台访问是随机路由到这3台节点的。如果线程一进来使节点1上了锁,当线程二进来可能访问到的是节点2,这时节点2还没有上锁,那么库存就会扣减错误。而库存扣减还是一个核心操作,现在居然有Bug,想想就可怕。
这时我们就需要一个全局的锁了。
实现全局的锁不一定是Redis。MySQL,Zookeeper也可设计为分布式锁。本篇主要讲的是Redis分布式锁的实现方式,其他的实现方式不做讲解。MySQL用作分布式锁在性能上并不好,这里不建议使用。对Zookeeper分布式锁有兴趣的可以看看我写的这篇文章。
“ ”
MySQL锁示意图
Zookeeper锁示意图
当然市面已经有成熟的框架去实现分布式锁了,不需要你重复造轮子了。
分布式锁实现
Redis分布式锁底层分析
记得之前面试被问Redis分布式锁的底层原理,我是这么回答的
Redis分布式锁底层
setnx
保证锁的唯一性。过期时间保证锁在异常情况下也能解锁。采用Lua脚本操作Redis,使操作具有原子性。后台进程心跳检测,如果当前时间持有锁并且锁还未失效,延长锁的失效时间。如果当前线程没有获取到锁,会一直自旋,知道获取到锁为止。
手写Redis分布式锁
编写加锁方法
我们来看看这段代码,redisTemplate.execute
参数解释如下
String result = (String) redisTemplate.execute(scriptLock,
redisTemplate.getStringSerializer(),
redisTemplate.getStringSerializer(),
Collections.singletonList(key),
uuid.toString(),
String.valueOf(timeOut));
scriptLock
为执行的Redis命令,里面是Lua脚本
脚本里面有setnx
操作,还设置了超时时间。
两个redisTemplate.getStringSerializer()
为key和value序列化工具。
后面3个参数为设置key,设置value,设置超时时间。分别对应Lua脚本中的KEYS[1],ARGV[1],ARGV[2]
。
如果setnx操作成功,说明锁创建成功,返回new RedisLock(key, uuid.toString())
。
如果失败,则一直循环拿锁,直到成功。
“另外,这里的value为随机生成的uuid,这是为什么呢?
”
因为如果某个客户端获取到了锁,但是阻塞了很长时间才执行完,此时可能已经自动释放锁了,此时可能别的客户端已经获取到了这个锁,要是你这个时候直接删除key的话会有问题,所以得用随机值加上面的Lua脚本来释放锁。
编写释放锁的方法
执行scriptLock2
,Lua脚本如下:
测试代码
测试结果
2020-08-29 20:54:43.484 INFO 21880 --- [main] com.lvshen.demo.RedisLockTest : 获得锁
2020-08-29 20:54:49.532 INFO 21880 --- [main] com.lvshen.demo.RedisLockTest : 未获得锁
这里没有做可重入功能,所以第二次访问的时候,锁还没有释放,所以未获得锁。
我们画一个流程图,完善下上面的流程
Redis锁逻辑
有关Redis主从同步问题
在Redis集群中,如果Master节点数据还没同步到Slave节点,Slave节点就挂了,下次Slave节点好了之后,就没有保存锁的数据,从而导致锁失效。那该怎么办?
这个场景是假设有一个Redis Cluster,有5个Redis Master实例。然后执行如下步骤获取一把锁:
-
获取当前时间戳,单位是毫秒
-
跟上面类似,轮流尝试在每个Master节点上创建锁,过期时间较短,一般就几十毫秒
-
尝试在大多数节点上建立一个锁,比如5个节点就要求是3个节点(n / 2 +1)
-
客户端计算建立好锁的时间,如果建立锁的时间小于超时时间,就算建立成功了
-
要是锁建立失败了,那么就依次删除这个锁
-
只要别人建立了一把分布式锁,你就得不断轮询去尝试获取锁
当超半数的主从同步成功了,才能判定为上锁成功。
Redis分布式锁缺点
我们来说说Redis分布式锁的缺点:
“Redis分布式锁,其实需要自己不断去尝试获取锁,比较消耗性能。
如果是Redis获取锁的那个客户端出Bug了或者挂了,那么只能等待超时时间之后才能释放锁。
Redis主从同步RedLock算法存在缺陷,锁的续命设计也很麻烦。
”
文中涉及的源码见Github
“https://github.com/lvshen9/demo/tree/lvshen-dev/src/main/java/com/lvshen/demo/redis/dislock
”
福利
关注公众号:Lvshen_9,后台回复【Redis】,可获取【Redis命令大全】,【Redis专项面试文档】。