分布式锁方案和缺陷
分布式锁使用场景
- 解决业务层幂等性,防止双次点击(譬如更新接口)
- 解决 MQ 消费端多端接受同一消息时保证只有一端处理消息
- 使用 schedule 执行定时任务时,多实例部署时只有一台实例执行任务
Redis
特点
- 单线程串行处理
- 获取锁性能特别好
- setnx 不存在则设置成功否则失败
- 没有心跳机制,需要设置失效时间
- CAP 中的 AP 模型,因为用的是 gossip 协议,所以不是强一致性
多个业务获取锁场景
锁失效时间设置问题
锁定了10s后过期,但业务执行了30s(可能碰到fullgc,死循环等场景)。
主从切换问题
业务1从主获取锁,此时主挂机了,从晋升为主,恰好此时从未同步这个锁的值 。
Zookeeper
特点
- 有序节点,按排序命名节点
- 临时节点,客户端断连后自动消失
- 事件监听,节点下发生更新时会有事件通知
- ZAB 协议,强一致,属于 CP 模型
- zk 集群变大后,性能持续下降
多个业务获取锁场景
客户端挂掉或假死
客户端断连会把临时节点删除,锁也就随着释放。另一个业务即可获取锁。
但其实客户端没挂,只是心跳维持间断了。原因有好多,譬如fullgc,网络问题(redis碰到网络问题最多获取锁失败)等。
Etcd
特点
- 如果存在 Key 的话就不能写入,也就意味着不能获取到锁,如果集群中,可以写入 Key,就意味着获取得到锁。
- Raft 保证了集群的一致性,强一致性,并且数据是可以进行持久化
- 没有心跳机制,需要设置失效时间
多个业务获取锁场景
锁失效时间设置问题
锁定了10s后过期,但业务执行了30s(可能碰到fullgc,死循环等场景)。
使用失效时间的锁时间续租问题
在获取到锁的业务线程,可以开启一个子线程去维护和轮训这把锁的有效时间,并定时的对这把锁进行续租。
假设业务线程获取到一把锁,锁的 Expire 时间为 10s,业务线程会开启一个子线程通过轮训的方式每 2 秒钟去把这把锁进行续租,每次都将锁的 Expire 还原到 10s。
存在的问题
- 业务死循环
- 业务 fullgc
这些问题都会导致续租线程无法执行,从而导致锁提前失效。