基于Zookeeper的分布式锁原理及实现(上篇)

1 背景

在单进程应用内,我们经常使用锁来保障多个线程并发访问同一资源的互斥性。在多进程、分布式场景下,如果多个系统或者单个系统的多个节点并发访问同一资源,为了保障对资源读写的互斥性,就需要用到分布式锁。而由于分布式场景比单机场景要复杂很多,分布式锁的实现方式也有很多种。我们这里主要介绍使用 Zookeeper 实现分布式锁的原理及实现方式。

2 为什么用Zookeeper来实现分布式锁?

Zookeeper能够保障分布式数据的一致性、有序性、原子性及可靠性,它的所有写入动作会在 Leader 节点持久化,并在集群过半数节点写入成功才会返回;它也能够支持节点的崩溃恢复、以及客户端的最终一致性视图。对于分布式锁场景来说,数据一致性的保障以及锁服务的容灾保障至关重要。

另外,Zookeeper还提供了三种在分布式锁场景下非常有用的特性:

  1. 临时节点
    客户端可以指定 zk 创建一个临时节点,此节点将在这个客户端与服务端建立的 session 到期时自动删除,这个特性可以保障客户端创建的分布式锁节点在客户端宕机或者网络通讯中断一段时间后自动释放该临时节点,从而避免分布式锁由于客户端或网络原因导致的死锁问题。
  2. 有序节点
    客户端可以指定 zk 创建一个有序节点,此节点将自动在客户端指定的节点名后面添加一个单调递增序号来确保多个客户端同时创建相同的节点名时能够创建成功,并且保障越早创建的节点的序号越小。利用该特性可以实现锁的互斥性和公平性,即同一时刻只有一个客户端能够成功获取到锁(序号最小的一个获取到锁),获取锁失败的节点可以按照创建顺序进行锁等待。
  3. watcher 机制
    可以对一个节点的读操作注册一个watcher监听器,当节点有变化时(例如节点被删除或更新)zk 服务端将主动通知注册了监听的客户端。这样对于正在等待锁的客户端可以及时得知锁被释放的事件从而重新进行抢锁动作。

以上三种特性可以结合使用,比如创建一个临时 + 有序节点,再注册一个其它序号节点的 watcher 监听来感知其他节点的变化。我们可以利用Zookeeper原生提供的这些特性实现各种可靠、安全的分布式锁。

3 常用的分布式锁类型

  • 排它锁(MutexLock)
    任意时刻只有一个线程能够获取到锁,其他线程等待持有这把锁的线程释放锁后才能尝试获取锁。
  • 信号量(Semaphore)
    允许多个线程持有一定数量的租约(Lease)。在当前租约数量小于最大租约数时,允许新的线程请求获取到租约,一旦当前租约数等于最大租约数,则新的请求将等待已获取到租约的线程释放租约后才能尝试获取。一般用来控制一个资源池的最大并发度。
  • 读写锁(ReadWriteLock)
    写锁作为排它锁,任意时刻只有一个线程能获取到写锁。读锁作为共享锁,当没有写锁被持有的前提下,允许有多个线程同时获取到读锁。
  • 联锁(MultiLock)
    保障多个不同资源的锁获取或释放的原子性的一种组合锁。多个锁资源被封装成一个联锁后,要么全部获取成功,要么全部获取失败,联锁保障不会出现部分获取成功的情况。

注意:以上提到的"线程"可能属于同一进程内,也可能属于不同进程。Zookeeper能够保障在跨进程场景下数据的一致性。

4 分布式锁的通用特性

除了Zookeeper提供的一致性保障之外,分布式锁一般还需要提供如下的通用特性:

  • 公平性
    在多个客户端抢锁的过程中,需要保障获取锁的公平性,先抢锁的请求能够先获取到锁(基于Zookeeper的有序节点特性来实现)。
  • 等待超时
    为了避免死锁,一般在获取锁时都需要传递一个超时时间,超过此时间没有获取到锁的话则获取锁失败。
  • 可重入性
    在一个线程内,如果已经持有一把锁,则在这把锁被释放前可以多次重复获取锁,其获取次数和释放次数需要保障一致。

注意:并不是所有的场景都需要支持线程级别的可重入性,某些对加锁次数有限制的场景(例如信号量)可以不支持重入。

5 分布式锁的实现库

基于Zookeeper来实现分布式锁的场景,推荐使用Apache Curator库,它不仅封装了分布式锁的所有实现细节,还提供了友好易用的 API。以下是Curator已经实现的分布式锁相关功能:

  • 可重入锁:InterProcessMutex实现了可重入的排它锁,支持锁等待超时、保证获取锁的公平性。
  • 不可重入锁:InterProcessSemaphoreMutex实现了不可重入的排它锁,支持锁等待超时、保证获取锁的公平性。可以在多个线程间传递和释放锁,从而满足异步调用场景下的锁需求。
  • 信号量:InterProcessSemaphoreV2实现了信号量(信号量不支持重入),支持信号量等待超时、保证获取信号量的公平性。另外,在多进程场景下,可以通过SharedCountReader来保障最大租约数的一致性,避免不同的进程设置不同的最大租约数。
  • 读写锁:InterProcessReadWriteLock实现了可重入读写锁,支持锁等待超时、保证获取锁的公平性。并且支持写锁降级(持有写锁的线程可以同时获取到读锁),不支持读锁升级(持有读锁的线程不能同时获取到写锁)。
  • 联锁:InterProcessMultiLock实现了联锁,它使用装饰器模式实现多把锁的组合,与可重入锁、不可重入锁实现相同的接口,使得可以像使用单锁一样使用联锁。联锁获取成功代表所有它拥有的锁都获取成功,联锁获取失败则会自动释放所有内部已经获取成功的部分锁,从而保证联锁的原子性语义。

总结

以上,我们简要介绍了基于Zookeeper实现的分布式锁的基本特性,以及Curator这个封装良好的分布式锁的库。下一篇我们将详细介绍每一种锁的实现原理,并从Curator源码的角度来详细讲解其实现方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值