欢迎访问shiker.tech

请允许在我们的网站上展示广告

您似乎使用了广告拦截器,请关闭广告拦截器。我们的网站依靠广告获取资金。

什么时候要使用zookeeper?
(last modified Aug 2, 2023, 1:21 AM )
by
侧边栏壁纸
  • 累计撰写 178 篇文章
  • 累计创建 62 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

什么时候要使用zookeeper?

橙序员
2023-08-01 / 0 评论 / 4 点赞 / 259 阅读 / 4,877 字 / 正在检测百度是否收录... 正在检测必应是否收录...
文章摘要(AI生成)

原文:https://zookeeper.apache.org/doc/r3.8.2/recipes.html在本文中,您将找到使用 ZooKeeper 实现高阶函数的指南。它们都是在客户端实现的约定,不需要 ZooKeeper 的特殊支持。希望社区能够在客户端库中捕获这些约定,以简化其使用并鼓励标

原文:https://zookeeper.apache.org/doc/r3.8.2/recipes.html

在本文中,您将找到使用 ZooKeeper 实现高阶函数的指南。它们都是在客户端实现的约定,不需要 ZooKeeper 的特殊支持。希望社区能够在客户端库中捕获这些约定,以简化其使用并鼓励标准化。

关于 ZooKeeper 最有趣的事情之一是,即使 ZooKeeper 使用异步通知,您也可以使用它来构建同步一致性原语,例如队列和锁。正如您将看到的,这是可能的,因为 ZooKeeper 对更新强加了总体顺序,并且具有公开此顺序的机制。

请注意,下面的秘诀尝试采用最佳实践。特别是,它们避免轮询、计时器或其他任何会导致“羊群效应”的东西,从而导致流量激增并限制可扩展性。

还有很多可以想象到的有用的功能这里没有包括——可撤销的读写优先级锁,仅作为一个例子。这里提到的一些构造(特别是锁)说明了某些要点,尽管您可能会发现其他构造(例如事件句柄或队列)是执行相同功能的更实用的方法。一般来说,本节中的示例旨在激发思考。

关于错误处理的重要说明

实施配方时,您必须处理可恢复的异常(请参阅常见问题解答)。特别是,一些应用场景采用了连续的临时节点。创建顺序临时节点时,会出现错误情况,举个例子虽然在服务器上成功 create() ,但服务器在将节点名称返回给客户端之前崩溃了。当客户端重新连接时,其会话仍然有效,因此该节点不会被删除。这意味着客户端很难知道其节点是否已创建。下面的应用场景包括解决这个问题的措施。

开箱即用的应用程序:名称服务、配置、组成员资格

名称服务和配置是 ZooKeeper 的两个主要应用。这两个函数是由ZooKeeper API直接提供的。

ZooKeeper直接提供的另一个功能是组成员资格。该组由一个节点表示。组成员在组节点下创建临时节点。当ZooKeeper检测到故障时,异常故障的成员节点将被自动删除。

屏障

分布式系统使用屏障来阻止一组节点的处理,直到满足允许所有节点继续进行的条件。在 ZooKeeper 中,屏障是通过指定屏障节点来实现的。如果屏障节点存在,则屏障建立。这是伪代码:

  1. 客户端在barrier节点上调用ZooKeeper API的 exists() 函数,并将watch 设置为true。
  2. 如果exist() 返回 false,则障碍消失并且客户端继续运行
  3. 否则,如果 exists() 返回 true,则客户端等待来自 ZooKeeper 的屏障节点的监视事件。
  4. 当watch事件被触发时,客户端重新发出exists() 调用,再次等待,直到屏障节点被移除。

双重屏障

双屏障使客户端能够同步计算的开始和结束。当足够多的进程加入屏障时,进程开始计算并在完成后离开屏障。本节展示了如何使用 ZooKeeper 节点作为双重屏障。

本节中的伪代码将屏障节点表示为b。每个客户端进程p在进入时向屏障节点注册,并在准备离开时注销。节点通过下面的Enter过程向屏障节点注册,它会等到x客户端进程注册后再继续计算。(此处的x由您根据您的系统确定。)

Enter Leave
1. 子节点名称:n = b +“/”+ p 1.获取子节点数量: L = getChildren(b, false)
2. 设置就绪节点的watcher:exists( b+ ‘’/ready’', true) 2.如果没有子节点,则退出
3. 创建子节点:create( n , EPHEMERAL) 3.如果p只是L中的进程节点,则delete(n)并退出
4. 获取子节点数量:L = getChildren(b, false) 4.如果p是L中最低的进程节点,则等待L中最高的进程节点
5. 如果 L 中的子进程少于_x_,则等待监视事件 5.否则如果仍然存在则delete( n ) 并等待 L 中最低的进程节点
6. 否则创建就绪节点 create(b + ‘’/ready’', 常规) 6. 转到 1

进入时,所有进程都会监视就绪节点并创建一个临时节点作为屏障节点的子节点。除最后一个进程外的每个进程都进入屏障并等待第 5 行出现就绪节点。创建第 x 个节点的进程(最后一个进程)将在子节点列表中看到 x 个节点并创建就绪节点,从而唤醒其他流程。请注意,等待进程仅在退出时才会唤醒,因此等待是高效的。

退出时,您不能使用诸如 “就绪” 之类的标志,因为您正在观察进程节点是否消失。通过使用临时节点,进入屏障后失败的进程不会阻止正确进程的完成。当进程准备离开时,它们需要删除其进程节点并等待所有其他进程执行相同操作。

当没有进程节点作为b的子节点时,进程退出。不过,为了提高效率,可以使用最低的进程节点作为就绪标志。准备退出的所有其他进程都会监视最低的现有进程节点是否消失,最低进程的所有者会监视任何其他进程节点(为简单起见,选择最高的)是否消失。这意味着每个节点删除时只会唤醒一个进程,但最后一个节点除外,最后一个节点在删除时会唤醒所有节点。

分布式队列

分布式队列是一种常见的数据结构。要在ZooKeeper中实现分布式队列,首先要指定一个znode来保存队列,即队列节点。分布式客户端通过调用以“queue-”结尾的路径名的 create() 将某些内容放入队列中,并将create() 调用中的序列临时标志设置为 true。由于设置了序列标志,因此新路径名的格式为path-to-queue-node /queue-X,其中 X 是单调递增的数字。想要从队列中删除的客户端调用 ZooKeeper 的getChildren() 函数,并带有 watch 在队列节点上设置为 true,并开始处理编号最小的节点。客户端不需要发出另一个 getChildren() ,直到耗尽从第一个调用 getChildren() 获得的列表。如果队列节点中没有子节点,则读取器等待监视通知以再次检查队列。

笔记

现在 ZooKeeper 目录中存在一个队列实现。这是随发布工件的release --zookeeper-recipes/zookeeper-recipes-queue 目录一起分发的。

优先级队列

要实现优先级队列,您只需对通用队列专题进行两个简单的更改。首先,要添加到队列,路径名以“queue-YY”结尾,其中 YY 是元素的优先级,数字越小代表优先级越高(就像 UNIX)。其次,当从队列中删除时,客户端使用最新的子列表,这意味着如果队列节点触发监视通知,客户端将使先前获得的子列表无效。

分布式锁

全局同步的完全分布式锁,这意味着在任何时间快照,没有两个客户端认为它们持有相同的锁。这些可以使用 ZooKeeper 来实现。与优先级队列一样,首先定义一个锁节点。

笔记

现在 ZooKeeper 配方目录中存在一个 Lock 实现。这是随发布工件的release --zookeeper-recipes/zookeeper-recipes-lock 目录一起分发的。

希望获得锁的客户端执行以下操作:

  1. 使用路径名“ locknode /guid-lock-”并设置序列临时标志来调用create()。如果错过create()的返回结果,则需要guid。请参阅下面的注释。
  2. 在锁定节点上调用getChildren() 而不是设置监视标志(这对于避免羊群效应很重要)。
  3. 如果步骤1中创建的路径名具有最低序列号后缀,则客户端拥有锁定并且客户端退出协议。
  4. 客户端调用exists(),并在锁定目录中具有下一个最低序列号的路径上设置监视标志。
  5. 如果exists() 返回null,则转至步骤2。否则,请等待上一步中的路径名通知,然后再转到步骤2

解锁协议非常简单:希望释放锁的客户端只需删除他们在步骤 1 中创建的节点即可。

以下是一些需要注意的事项:

  • 删除一个节点只会导致一个客户端唤醒,因为每个节点都由一个客户端监视。这样,你就可以避免羊群效应。
  • 没有轮询或超时。
  • 由于实现锁定的方式,很容易看到锁争用的数量、打破锁定、调试锁定问题等。

可恢复的错误和 GUID

  • 如果调用create() 时发生可恢复错误,客户端应调用getChildren() 并检查包含路径名中使用的guid 的节点。这处理了create() 在服务器上成功但服务器在返回新节点名称之前崩溃的情况(如上所述)

共享锁

您可以通过对锁协议进行一些更改来实现共享锁:

获取读锁: 获取写锁:
1. 调用 create() 创建一个路径名为“ guid-/read- ”的节点。这是协议后面使用的锁定节点。确保设置了序列 标志和临时 标志。 1. 调用create() 创建一个路径名为“ guid-/write- ”的节点。这就是协议后面提到的锁定节点。确保设置序列临时标志。
2.在锁定节点上调用getChildren() 而不设置监视标志 - 这很重要,因为它避免了羊群效应。 2.在锁定节点上调用getChildren() 而不设置监视标志 - 这很重要,因为它避免了羊群效应。
3. 如果不存在路径名以“ write- ”开头且序列号低于步骤1中创建的节点的子节点,则客户端拥有锁并可以退出协议。 3. 如果不存在序列号低于步骤1中创建的节点的子节点,则客户端拥有锁并且客户端退出协议。
4.否则,调用exists(),并在锁定目录中的节点上设置监视标志,该节点的路径名以具有下一个最低序列号的“ write- ”开头。 4.在具有下一个最低序列号的路径名的节点上调用exists(),并设置监视标志。
5. 如果exists() 返回false,则转至步骤2 5. 如果 exists() 返回false,则转至步骤2。否则,请等待上一步中的路径名通知,然后再转到步骤2
6. 否则,请等待上一步中的路径名通知,然后再转到步骤2

笔记:

  • 看起来这个方法可能会产生羊群效应:当有一大群客户端在等待读锁时,当序列号最低的“ write- ”节点被删除时,所有客户端或多或少都会同时收到通知。实际上。这是有效的行为:因为所有等待的读者客户端都应该被释放,因为它们拥有锁。羊群效应是指在实际上只有一台或少量机器可以进行的情况下释放“羊群”。
  • 有关如何在节点中使用 guid 的信息,请参阅Locks 的注释。

可撤销的共享锁

通过对共享锁协议进行少量修改,您可以通过修改共享锁协议来使共享锁可撤销:

在获取读取器和写入器锁协议的步骤1中,在调用create() 后立即调用设置了监视的 getData()。如果客户端随后收到其在步骤1中创建的节点的通知,它会在该节点上执行另一个getData(),并设置监视并查找字符串“unlock”,该字符串向客户端发出信号,表明它必须释放锁定。这是因为,根据这个共享锁协议,您可以通过在锁节点上调用 setData() 并向该节点写入“unlock”来请求拥有锁的客户端放弃锁。

请注意,该协议要求锁持有者同意释放锁。这种同意很重要,特别是如果锁持有者需要在释放锁之前进行一些处理。当然,您始终可以通过在协议中规定,如果在一段时间后锁持有者没有删除锁,则允许撤销者删除锁节点,从而使用怪异激光束实现可撤销共享锁*。*

两阶段提交

两阶段提交协议是一种让分布式系统中的所有客户端同意提交事务或中止事务的算法。

在 ZooKeeper 中,您可以通过让协调器创建一个事务节点(例如“/app/Tx”)和每个参与站点一个子节点(例如“/app/Tx/s_i”)来实现两阶段提交。当协调器创建子节点时,它会保留未定义的内容。一旦参与事务的每个站点从协调器接收到事务,该站点就会读取每个子节点并设置监视。然后,每个站点处理查询并通过写入其各自的节点来投票“提交”或“中止”。一旦写入完成,其他站点就会收到通知,并且一旦所有站点都获得所有投票,它们就可以决定“中止”或“提交”。请注意,如果某个站点投票支持“中止”,则节点可以提前决定“中止”。

此实现的一个有趣的方面是,协调器的唯一角色是决定站点组、创建 ZooKeeper 节点并将事务传播到相应的站点。事实上,甚至传播交易也可以通过 ZooKeeper 来完成,只需将其写入交易节点即可。

上述方法有两个重要的缺点。一是消息复杂度,为 O(n²)。第二个是无法通过临时节点检测站点的故障。要使用临时节点检测站点的故障,该站点必须创建该节点。

为了解决第一个问题,您可以只通知协调器事务节点的更改,然后在协调器做出决定后通知站点。请注意,这种方法是可扩展的,但速度也较慢,因为它需要所有通信都通过协调器。

为了解决第二个问题,您可以让协调器将事务传播到站点,并让每个站点创建自己的临时节点。

领导人选举

使用 ZooKeeper 进行领导者选举的一个简单方法是在创建代表客户端“提案”的 znode 时使用SEQUENCE|EPHEMERAL标志。这个想法是有一个 znode,比如“/election”,这样每个 znode 都会创建一个带有两个标志 SEQUENCE|EPHEMERAL 的子 znode“/election/guid-n_”。使用序列标志,ZooKeeper 会自动附加一个序列号,该序列号大于之前附加到“/election”子级的任何序列号。创建具有最小附加序列号的 znode 的进程是领导者。

但这还不是全部。重要的是要注意领导者的失败,以便在当前领导者失败的情况下,新客户作为新领导者出现。一个简单的解决方案是让所有应用程序进程监视当前最小的 znode,并在最小的 znode 消失时检查它们是否是新的领导者(请注意,如果领导者失败,最小的 znode 将消失,因为该节点是短暂的)。但这会导致羊群效应:当前领导者失败后,所有其他进程都会收到通知,并在“/election”上执行 getChildren 以获得“/election”当前的子进程列表。如果客户端数量很大,则会导致 ZooKeeper 服务器必须处理的操作数量激增。为了避免羊群效应,观察 znode 序列中的下一个 znode 就足够了。如果客户端收到其正在监视的 znode 已消失的通知,那么在没有更小的 znode 的情况下,它会成为新的领导者。请注意,这可以避免所有客户端都监视同一个 znode,从而避免羊群效应。

这是伪代码:

让 ELECTION 成为应用程序的选择路径。自愿成为领导者:

  1. 创建路径为“ELECTION/guid-n_”的 znode z,并带有 SEQUENCE 和 EPHEMERAL 标志;
  2. 设C为“ELECTION”的子节点,I为z的序号;
  3. 观察“ELECTION/guid-n_j”的变化,其中 j 是满足 j < i 的最大序列号,n_j 是 C 中的 znode;

收到 znode 删除通知后:

  1. 设 C 为 ELECTION 的新子集;
  2. 如果z是C中最小的节点,则执行leader过程;
  3. 否则,请注意“ELECTION/guid-n_j”的变化,其中 j 是满足 j < i 的最大序列号,n_j 是 C 中的 znode;

笔记:

  • 请注意,子节点列表中没有前面的 znode 的 znode 并不意味着该 znode 的创建者知道它是当前的领导者。应用程序可以考虑创建一个单独的 znode 来确认领导者已经执行了领导者过程。
  • 有关如何在节点中使用 guid 的信息,请参阅Locks 的注释。
4

评论区