数据库实现分布式锁
- 基于主键或唯一索引来实现,想要获得锁,就插入一条记录。
- 并发下只有一个插入可以成功(获得锁),插入失败就获得锁失败,释放锁时就删除这条记录。
- 失败的线程轮询再次操作,直至获得锁成功
缺点:
- 锁没有失效时间,如果获得锁成功后未释放锁(服务挂了),其他线程无法获取锁。
- 大量的轮询会消耗CPU
- 单机的 mysql 无法满足高可用
- 集群高可用场景下,主机宕机主备切换存在数据一致性问题(数据同步本身是弱一致性有延迟,备库可能缺失一部分数据)
Redis实现分布式锁
- 类似数据库,set命令获得锁(set + expire 不行,不是原子的),del命令释放锁
- 获得锁失败的线程轮询再次请求。
- 获得锁的服务宕机了怎么办?设置过期时间防止死锁
- 锁到期被删除怎么办?加一个守护线程,在快要过期时给锁续约
- 锁被误删怎么办?set 时 value 设置成唯一值,del 时比较 value;也就是说我只能释放我自己加的锁。
缺点:
- 大量的轮询会消耗CPU
- 单机的 redis 无法满足高可用
- 集群高可用场景同 mysql
Zookeeper实现分布式锁
能够解决上述方案的所有痛点:
- 服务宕机引发死锁?我们使用临时节点,一定时间内接收不到服务的心跳包,临时节点自动删除。
- 线程主动轮询?有延迟,有压力,使用 watch 机制
- watch 同一个节点,删除事件唤醒大量线程争抢锁?有压力。只 watch 上一个节点。
- 单机问题?支持分布式高可用
- 主备切换存在一致性问题?选举选出的节点肯定是数据最全的。即使有数据缺失,其内部队列中的log是能够保证最终一致性的。而且其顺序一致性保证了后来的请求无法影响到先到的请求。
过程是这样的:
- 线程在目录下创建一个临时序列节点
- 线程获取目录下的所有节点,如果它刚才创建的节点是最小节点,认为这个线程获得了锁;如果不是最小节点,这个线程 wait,同时监听比自己小的节点的删除事件(exist方法)
- 无论是线程结束删除节点(释放锁),还是超时删除节点,都会唤醒监听其节点删除事件的线程,被唤醒的线程获得锁。
分布式锁方案
原文:https://www.cnblogs.com/felix-1/p/14756616.html