分布式锁
条评论分布式锁是什么
分布式锁是用来解决在分布式场景中,多服务实例并发争抢共享资源时,且该资源同一时刻只能一个访问,就需要通过加锁来让获得锁的应用执行,获取不到锁的服务不执行或者后续执行。
分布式锁的解决方案
Redis分布式锁
Redis分布式锁依赖setnx命令来实现。
具体实现思路:
- 服务1尝试拿锁的时候,
setnx key value返回的是1,表示设置值成功,也就是拿到了锁,顺利执行业务逻辑。 - 后续服务2尝试拿锁的时候,
setnx key value返回的是0,表示设置值失败,也就是拿锁失败。 - 这样就可以保证只有一个服务执行。
服务1执行完业务逻辑之后呢,应该有一个锁释放的动作,不然key永远存在,不会再有服务能够拿到锁,也就是出现死锁。
如何避免死锁
锁释放分为:主动删除和自然消亡。
- 主动删除就是业务逻辑执行完毕,服务代码主动删除,依赖
del key命令来实现。
需要注意的是,主动删除锁的逻辑最好放到代码块的finally里,可以防止代码执行异常无法释放锁的情况。
但是如果代码服务突然挂了呢,主动删除是解决不了的。 - 自然消亡就是通过Redis提供的key过期机制,依赖
set key value ex seconds nx命令来实现。
需要注意的是,过期时间的设置问题。
如果设置过短,可能业务还没有执行完;如果设置过长,可能就会造成卡顿等问题。
既然都有些问题,为了增加健壮性,那就采用两种结合的方式吧:代码主动删除key+redis_key自动过期,双道保险来保证。
那还有问题么?有的。
存在一种可能情况是,本服务key被其他服务释放掉的情况:
服务1处理业务很慢,大于key的过期时间,这时候锁就自然消亡,锁后面可以被服务2拿到,存在服务2执行业务过程中,key被服务1主动删除的情况。
针对这个情况,也好解决,就是set-key的时候,在value里加上当前服务的标识,可以是随机数,在主动删除key的时候,做一个get-key的value校验。
那还有问题么?还有的。
仔细分析流程后我们发现,判断锁是否属于当前服务和释放锁两个步骤并不是原子操作。
假如在执行删除锁的动作之前,系统卡顿了几秒钟,恰好在这几秒钟内,key自动过期了,
服务2就顺利获取到锁开始执行自己的逻辑了,此时,服务1卡顿恢复了,开始继续执行删除锁的动作,那么此时删除的还是服务2的锁。
如下图所示:

终极解决-Lua脚本
Lua是一门胶水语言,它支持原子性操作,Redis会将整个Lua脚本作为一个整体执行,中间不会被其他请求插入,因此Redis执行Lua脚本是一个原子操作。
在上面的流程中,我们把get-key、判断value是否属于当前服务、del-key这三步写到Lua脚本中,使它们变成一个整体交个Redis执行,改造后流程如下:

Lua脚本示例:
1 | local key = KEYS[1] |
当然,上面加锁的过程,我们也可以通过Lua脚本实现。
expire的时间适用性
- 提供
时间冗余,时间设置足够的长,优先保证业务完毕,但是不推荐使用。 - 用Redisson的
Watch Dog机制解决,简单来说就是,它是一个守护线程,定时去检查key的时效,如果业务还没有执行完&key快要过期了,就进行key的续期。
总结
针对上面redis分布式锁的解决方案,单机Redis节点是没什么问题的;
但是企业开发时,往往都会有’Redis集群’,主从同步。主节点挂了,从节点还没有同步完的时候,key丢了,就会有问题。
一般Redis分布式锁就够用了,所以其他的解决方案我们简单分析一下。
Zookeeper实现分布式锁
1 | 客户端1和客户端2都创建临时节点 /lock |
zk不用考虑过期时间,可以实现实现分布式锁,但是单独搞一个zk,我觉得成本太大。
RedLock解决方案
1 |
|
文章作者:米兰
原始链接:https://blog.milanchen.site/posts/distributed-lock.html
版权声明:转载请声明出处