分布式锁解决方案
分布式锁解决方案
分布式锁:集群的情况下会产生
CAP理论:一致性 可用性 分区容错性
解决方案:
基于数据库实现分布式锁
基于缓存(Redis等)实现分布式锁
基于Zookeeper实现分布式锁
1、基于数据库实现分布式锁
唯一性约束,这里如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功。
缺点:
依赖数据库
锁没用失效时间
非重入锁
解决:
数据库集群
定时解锁
记录加锁的信息判断能否重入
2、基于缓存(Redis等)实现分布式锁
redis setnx
缺点:
1、set 和 expire 不是原子性的会造成死锁
解决: set key value nx ex sec
2、A处理时间大于过期时间,会自动释放锁,此时B获取到锁,这是A执行完毕,会删除B的锁
解决:删除之前先判断是不是自己的锁
问题:判断和释放不是原子性
解决:获得锁之后另外开启一个守护线程定期expire,执行完成之后显示关闭线程
3、基于Zookeeper实现分布式锁
- 在Zookeeper当中创建一个持久节点ParentLock
- 当第一个客户端想要获得锁时,需要在ParentLock这个节点下面创建一个临时顺序节点。
- 查找ParentLock下面所有的临时顺序节点并排序,判断自己所创建的节点Lock1是不是顺序最靠前的一个。如果是第一个节点,则成功获得锁。
- 如果不是最小的节点 则抢锁失败,监听前一个节点
- 当任务完成时,显示调用删除当前节点的指令。
- 下一个节点获取到锁
缺点:
性能上可能并没有缓存服务那么高。因为每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。ZK中创建和删除节点只能通过Leader服务器来执行,然后将数据同不到所有的Follower机器上。
使用Zookeeper也有可能带来并发问题,只是并不常见而已。考虑这样的情况,由于网络抖动,客户端可ZK集群的session连接断了,那么zk以为客户端挂了,就会删除临时节点,这时候其他客户端就可以获取到分布式锁了。就可能产生并发问题。这个问题不常见是因为zk有重试机制,一旦zk集群检测不到客户端的心跳,就会重试,Curator客户端支持多种重试策略。多次重试之后还不行的话才会删除临时节点。(所以,选择一个合适的重试策略也比较重要,要在锁的粒度和并发之间找一个平衡。)
三种方案的比较
上面几种方式,哪种方式都无法做到完美。就像CAP一样,在复杂性、可靠性、性能等方面无法同时满足,所以,根据不同的应用场景选择最适合自己的才是王道。
从理解的难易程度角度(从低到高):
数据库 > 缓存 > Zookeeper
从实现的复杂性角度(从低到高):
Zookeeper >= 缓存 > 数据库
从性能角度(从高到低):
缓存 > Zookeeper >= 数据库
从可靠性角度(从高到低):
Zookeeper > 缓存 > 数据库