什么是分布式锁
在单体的应用开发场景中涉及并发同步的时候,大家往往采用Synchronized(同步)或者其他同一个JVM内Lock机制来解决多线程间的同步问题。在分布式集群工作的开发场景中,就需要一种更加高级的锁机制来处理跨机器的进程之间的数据同步问题,这种跨机器的锁就是分布式锁。
目前分布式锁,比较成熟、主流的方案:
(1)基于数据库的分布式锁。db操作性能较差,并且有锁表的风险,一般不考虑。
(2)基于Redis的分布式锁。适用于并发量很大、性能要求很高而可靠性问题可以通过其他方案去弥补的场景。
(3)基于ZooKeeper的分布式锁。适用于高可靠(高可用),而并发量不是太高的场景。
基于Redis实现分布式锁可以参考对应的文章。
传送门:https://blog.csdn.net/u010355502/article/details/132241180
基于数据的设计思路:
利用数据库唯一索引的排它性。大概步骤如下:
向数据库表插入一条数据,字段对应唯一索引
如果插入成功,表示获取到了锁
继续执行同步业务逻辑;
释放锁,即删除插入的唯一索引记录;
如果插入失败,需要等待获取锁
轮循插入唯一索引记录,插入成功表示获取到了锁,继续执行同步业务逻辑完后释放锁;
如何防止死锁?
如果获取锁的客户端宕机,导致加锁记录没有被删除,那么其它的客户端就永远获取不到锁了,此时就产生了死锁。可以在加锁时带上一个过期时间插入到记录中,比如30秒,另起一个分布式定时任务定期检查过期时间,如果过期了就删除掉对应的记录(解锁)。在客户端加锁时也起一个异步任务,如果检查到当前加锁业务逻辑还没执行完,则更新数据库锁记录的过期时间(锁续期),比如10秒。如此以来,就解决了数据库实现锁带来的死锁问题。(其实借鉴的就是redisson实现分布式锁的原理~)
大部分分布式环境下redis基本就是标配,而且锁性能比数据库高多了,所以基本不会用到数据实现分布式锁。
ZooKeeper实现分布式锁
设计思路一
大概流程如下:
客户端尝试获取锁
创建临时节点/lock
是否创建成功
成功则获取到了锁
执行同步逻辑
执行完后释放锁
失败则等待,监听该/lock节点
直到收到节点被删除的通知,然后再次尝试获取锁,重试第一步
上面的思路有个问题,即羊群效应:当锁被释放后,所有的其他客户端都会收到锁被释放的通知,但是最终只有一个客户端能竞争到锁,其他的客户端竞争锁请求都是无效的,消耗性能,这个就是羊群效应。
设计思路二
使用临时顺序ZNode来表示获取锁的请求,创建最小后缀数字ZNode的用户成功拿到锁。公平锁的实现。
在实际的开发中,如果需要使用到分布式锁,不建议去自己“重复造轮子”,而建议直接使用Curator客户端中的各种官方实现的分布式锁,例如其中的InterProcessMutex可重入锁。
Curator客户端分布式锁
如何使用
curator是zk的客户端,实现了zk的分布式锁,具体如何使用,请参考如下文章。
传送门:https://blog.csdn.net/u010355502/article/details/132316237
优缺点
- 优点
ZooKeeper分布式锁(如InterProcessMutex),具备高可用、可重入、阻塞锁特性,可解决失效死锁问题,使用起来也较为简单。
- 缺点
因为需要频繁地创建和删除节点,性能上不如Redis。
在高性能、高并发的应用场景下,不建议使用ZooKeeper的分布式锁。而由于ZooKeeper的高可用性,因此在并发量不是太高的应用场景中,还是推荐使用ZooKeeper的分布式锁。
版权归原作者 Firechou 所有, 如有侵权,请联系我们删除。