diff --git a/README.md b/README.md index ba3e164..b656ecb 100644 --- a/README.md +++ b/README.md @@ -133,13 +133,13 @@ ZooKeeper 可以被用来实现分布式应用中的动态配置。在它最简 尽管ZooKeeper不是锁服务,但可以用来实现锁。使用 ZooKeeper 的应用程序通常使用根据其需求量身定制的同步原语,例如上面所示的那些。在这里,我们展示了如何使用 ZooKeeper 实现锁,以表明它可以实现各种各样的常规同步原语。 -最简单的锁实现使用“lock files”。该锁由 `znode` 表示。为了获取锁,客户端尝试使用`EPHEMERAL`标志创建指定的`znode`。如果创建成功,则客户端将持有该锁。否则,客户端可以设置 watch 标志读取`znode`,以便在当前领导者去世时得到通知。客户端宕机或显式删除`znode`时会释放该锁。其他等待锁定的客户端一旦观察到`znode`被删除,就会再次尝试获取锁定\(即创建 `znode` )。 +最简单的锁实现使用“lock files”。该锁由 `znode` 表示。为了获取锁,客户端尝试使用`EPHEMERAL`标志创建指定的`znode`。如果创建成功,则客户端将持有该锁。否则,客户端可以设置 watch 标志读取`znode`,以便在当前领导者去世时得到通知。客户端宕机或显式删除`znode`时会释放该锁。其他等待锁定的客户端一旦观察到`znode`被删除,就会再次尝试获取锁\(即创建 `znode` )。 -尽管此简单的锁定协议有效,但确实存在一些问题。首先,它具有惊群效应。如果有许多等待获取锁的客户端,则即使只有一个客户端可以获取锁,他们也会争夺该锁。其次,它仅实现互斥锁。以下两个原语显示了如何同时解决这两个问题。 +尽管此简单的锁定协议有效,但确实存在一些问题。首先,它具有惊群效应。如果有许多等待获取锁的客户端,则即使只有一个客户端可以获取锁,他们也会争夺该锁。其次,它仅实现互斥锁(没有实现读写锁等模式)。以下两个原语显示了如何同时解决这两个问题。 #### Simple Locks without Herd Effect -我们定义一个锁`znode` *l* 来实现这种锁。 直观地说,我们排队所有请求锁定的客户端,每个客户端都按照请求到达的顺序获得锁定。 因此,希望获得该锁的客户端执行以下操作: +我们定义一个锁`znode` *l* 来实现这种锁。 直观地说,我们令所有请求锁定的客户端排队,每个客户端都按照请求到达的顺序获得锁。 因此,希望获得该锁的客户端执行以下操作: ``` Lock @@ -154,15 +154,15 @@ Unlock 1: delete(n) ``` -在Lock的第1行中使用`SEQUENTIAL`标志,命令 client 尝试获取锁,并相对其它的尝试获得一个序列号。如果客户端的`znode`在第3行的序列号最小,则客户端将持有该锁。否则,客户端将等待删除 持有 Lock 或将在此客户端的`znode` 之前收到锁定的有更小序列号的 `znode` 。通过仅查看客户端`znode`之前的`znode`,我们仅在释放锁或放弃锁请求时才唤醒一个进程,从而避免了惊群效应。客户端监视的znode消失后,客户端必须检查它现在是否持有该锁。(先前的 Lock 请求可能已被放弃,并且具有较低序号的`znode` 仍在等待或保持锁定。) +在Lock的第1行中使用`SEQUENTIAL`标志,命令 client 尝试获取锁,并相对其它的尝试获得一个序列号。如果客户端的`znode`在第3行的序列号最小,则客户端将持有该锁。否则,客户端将等待删除下列两种 `znode`: 持有 Lock 的 `znode`,将在此客户端的`znode` 之前获得锁的 `znode` 。通过仅查看客户端`znode`之前的`znode`,我们仅在释放锁或放弃锁请求时才唤醒一个进程,从而避免了惊群效应。客户端 watch 的znode消失后,客户端必须检查它现在是否持有该锁。(先前的 Lock 请求可能已被放弃,并且具有较低序号的`znode` 仍在等待或保持锁。) -释放锁定就简单地直接删除代表 Lock 请求的`znode` *n*。通过在创建 `znode` 时使用`EPHEMERAL`标志,崩溃的进程将自动清除所有锁定请求或释放它们可能拥有的任何锁定。 +释放锁就简单地直接删除代表 Lock 请求的`znode` *n*。通过在创建 `znode` 时使用`EPHEMERAL`标志,崩溃的进程将自动清除所有锁定请求或释放它们可能拥有的任何锁定。 总之,此锁定方案具有以下优点: 1. 删除一个`znode`只会导致一个客户端唤醒,因为每个`znode`都被另一个客户端 watch,因此我们没有惊群效应; 2. 没有轮询或超时; -3. 由于我们实现了锁定的方式,因此通过浏览ZooKeeper数据可以看到 Lock 争用,中断锁定和调试锁定问题的进程的数量。 +3. 由于我们实现了锁的方式,因此通过浏览 ZooKeeper 数据可以看到 Lock 争用,中断锁定和调试锁定问题的进程的数量。 #### Read/Write Locks @@ -190,11 +190,11 @@ Read Lock 6: goto 3 ``` -该锁定过程与之前的锁定略有不同。 写锁仅在命名上有所不同。 由于读锁可能是 shared 的,因此第3行和第4行略有不同,因为只有较早的写锁`znode`会阻止客户端获得读锁。 当有多个客户端等待读锁时,当删除具有较低序号的`write-` znode时,我们可能会收到“惊群效应”。 实际上,这是一种期望的行为,所有那些需要读的客户端都应被通知,因为它们现在可能已具有锁定。 +该锁定过程与之前的锁定略有不同。 写锁仅在命名上有所不同。 由于读锁可能是 shared 的,因此第3行和第4行略有不同,因为只有较早的写锁`znode`会阻止客户端获得读锁。 当有多个客户端等待读锁时,当删除具有较低序号的`write-` znode时,我们可能会收到 “惊群效应”。 实际上,这是一种符合预期的行为,所有那些需要读的客户端都应被通知,因为它们现在可能已具有锁定。 #### Double Barrier -double barriers 使 client 能够同步计算的开始和结束。当有 barrier 限制定义的足够多的进程加入 barrier 时,进程将会开始它们的计算,并在计算结束后离开 barrier。我们在 ZooKeeper 中用`znode` 表示一个 barrier,称为*b*。 每个进程p都会在进入时通过将`znode`创建为 *b* 的子节点来向 *b* 注册,并在准备离开时取消注册,即删除该子节点。 当b的子`znode`数量超过障碍阈值时,进程可以进入 barrier。 当所有进程都删除了其子进程时,进程可以离开 barrier。 我们使用 watch 来有效地等待进入和退出 barrier 条件得到满足。 要进入 barrier,流程会监视是否存在 *b* 的 ready 子 `znode` ,该子 `znode` 将由导致子节点数超过障碍阈值的进程创建。 要离开 barrier,进程会 watch 特定的子节点的消失,并且仅在这个`znode`被删除之后检查退出条件。 +double barriers 使 client 能够同步计算的开始和结束。当有 barrier 限制定义的足够多的进程加入 barrier 时,进程将会开始它们的计算,并在计算结束后离开 barrier。我们在 ZooKeeper 中用`znode` 表示一个 barrier,称为*b*。 每个进程p都会在进入时通过将`znode`创建为 *b* 的子节点来向 *b* 注册,并在准备离开时注销,即删除该子节点。 当b的子`znode`数量超过 barrier 阈值时,进程可以进入 barrier。 当所有进程都删除了其子进程时,进程可以离开 barrier。 我们使用 watch 来有效地等待进入和退出 barrier 条件得到满足。 要进入 barrier,流程会监视是否存在 *b* 的 ready 子 `znode` ,该子 `znode` 将由导致子节点数超过障碍阈值的进程创建。 要离开 barrier,进程会 watch 特定的子节点的消失,并且仅在这个`znode`被删除之后检查退出条件。 ## 3 ZooKeeper Applications