分布式锁 🔒

分布式锁 🔒

Redis 分布式锁

Redis 实现的分布式锁,请阅读 Redis 分布式锁

ZooKeeper 分布式锁

最简单锁实现

  • lock: 创建一个带有 EPHEMERAL 标志的 designated znode,创建成功,锁成功;否则,客户端可以设置上 watch 标志,当当前的 leader 挂掉的时候,即时地获得通知。
  • unlock: 客户端显示地删除 znode 的时候,或者客户端挂掉的时候,锁自动释放。znode 被删除的时候,其他想要获取锁的客户端可以获取到通知。

缺点:

  • herd effect (羊群效应):如果有许多客户端等待获取锁,那么当锁被释放时,他们都会争夺锁,即使只有一个客户端可以获取锁。
  • 只实现了独占锁。

没有羊群效应的锁实现

顺序临时节点:Zookeeper 提供一个多层级的节点命名空间(节点称为 Znode),每个节点都用一个以斜杠(/)分隔的路径来表示,而且每个节点都有父节点(根节点除外),非常类似于文件系统。节点类型可以分为持久节点(PERSISTENT )、临时节点(EPHEMERAL),每个节点还能被标记为有序性(SEQUENTIAL),一旦节点被标记为有序性,那么整个节点就具有顺序自增的特点。一般我们可以组合这几类节点来创建我们所需要的节点,例如,创建一个持久节点作为父节点,在父节点下面创建临时节点,并标记该临时节点为有序性。

Lock:

n = create(l + “/lock-”, EPHEMERAL|SEQUENTIAL)
C = getChildren(l, false)
if n is lowest znode in C, exit
p = znode in C ordered just before n
if exists(p, true) wait for watch event
goto 2

Unlock:

delete(n)

首先,我们需要建立一个父节点,节点类型为持久节点(PERSISTENT) ,每当需要访问共享资源时,就会在父节点下建立相应的顺序子节点,节点类型为临时节点(EPHEMERAL),且标记为有序性(SEQUENTIAL),并且以临时节点名称 + 父节点名称 + 顺序号组成特定的名字。在建立子节点后,对父节点下面的所有以临时节点名称 name 开头的子节点进行排序,判断刚刚建立的子节点顺序号是否是最小的节点,如果是最小节点,则获得锁。如果不是最小节点,则阻塞等待锁,并且获得该节点的上一顺序节点,为其注册监听事件,等待节点对应的操作获得锁。当调用完共享资源后,删除该节点,关闭 zk,进而可以触发监听事件,释放该锁。

一般我们还可以直接引用 Curator 框架来实现 Zookeeper 分布式锁,代码如下:

InterProcessMutex lock = new InterProcessMutex(client, lockPath);
if ( lock.acquire(maxWait, waitUnit) ) {
    try {
        // do some work inside of the critical section here
    } finally {
        lock.release();
    }
}

读/写锁

Write Lock:

n = create(l + “/write-”, EPHEMERAL|SEQUENTIAL)
C = getChildren(l, false)
if n is lowest znode in C, exit
p = znode in C ordered just before n
if exists(p, true) wait for event

Read Lock:

n = create(l + “/read-”, EPHEMERAL|SEQUENTIAL)
C = getChildren(l, false)
if no write znodes lower than n in C, exit
p = write znode in C ordered just before n
if exists(p, true) wait for event
goto 3

参考