ZooKeeper协调服务分布式

ZooKeeper官方文档

一、什么是ZooKeeper?

ZooKeeper是用于维护配置信息,命名,提供分布式同步和提供组服务的集中式服务。所有这些类型的服务都以某种形式被分布式应用程序使用。每次实施它们时,都会进行很多工作来修复不可避免的错误和竞争条件。由于难以实现这类服务,因此应用程序最初通常会跳过它们,这会使它们在发生更改时变脆并且难以管理。即使部署正确,这些服务的不同实现也会导致管理复杂。

二、ZooKeeper数据模型

ZooKeeper具有层次结构的名称空间,非常类似于分布式文件系统。唯一的区别是,名称空间中的每个节点都可以具有与其关联的数据以及子级。就像拥有一个文件系统一样,该文件系统也允许文件成为目录。到节点的路径始终表示为规范的,绝对的,斜杠分隔的路径。没有相对参考。

遵循以下约束,可以在路径中使用任何unicode字符:

  • 空字符(\ u0000)不能是路径名的一部分。(这会导致C绑定出现问题。)
  • 以下字符不能使用,因为它们不能很好地显示或以混乱的方式呈现:\ u0001-\ u001F和\ u007F
  • \ u009F。
  • 不允许使用以下字符:\ ud800-uF8FF,\ uFFF0-uFFFF。
  • “。” 字符可以用作其他名称的一部分,但“。” 和“ …”不能单独用于指示路径上的节点,因为ZooKeeper不使用相对路径。以下内容将无效:“ / a / b /./ c”或“ /a/b/…/c”。
  • 令牌“ zookeeper”被保留。
1.Znode节点

ZooKeeper树中的每个节点都称为znode。Znodes维护一个统计信息结构,其中包括用于数据更改和acl更改的版本号。stat结构也有时间戳。版本号和时间戳允许ZooKeeper验证缓存并协调更新。znode的数据每次更改时,版本号都会增加。例如,每当客户端检索数据时,它也会接收数据的版本。并且,当客户端执行更新或删除时,它必须提供要更改的znode的数据版本。如果它提供的版本与数据的实际版本不匹配,则更新将失败。

注意:

在分布式应用程序工程中,*节点*一词可指代通用主机,服务器,集合体的成员,客户端进程等。在ZooKeeper文档中,znodes指代数据节点。服务器是指组成ZooKeeper服务的机器;仲裁对等点是指组成集合的服务器;客户端是指使用ZooKeeper服务的任何主机或进程。

Znodes是程序员访问的主要实体:

手表

客户端可以在znodes上设置监视。对该znode的更改触发手表,然后清除手表。监视触发时,ZooKeeper向客户端发送通知。

资料存取

原子地读取和写入存储在命名空间中每个znode上的数据。读取将获取与znode关联的所有数据字节,而写入将替换所有数据。每个节点都有一个访问控制列表(ACL),用于限制谁可以执行操作。

ZooKeeper并非设计为通用数据库或大型对象存储。相反,它管理协调数据。这些数据可以采用配置,状态信息,集合点等形式。各种形式的协调数据的共同属性是它们相对较小:以千字节为单位。ZooKeeper客户端和服务器实现具有健全性检查,以确保znode的数据少于1M,但数据应比平均少得多。在相对大的数据量上进行操作将导致某些操作比其他操作花费更多的时间,并且会影响某些操作的延迟,因为需要更多时间才能通过网络将更多数据移动到存储介质上。如果需要大数据存储,则处理此类数据的通常方式是将其存储在大容量存储系统上,

临时节点

ZooKeeper还具有短暂节点的概念。只要创建znode的会话处于活动状态,这些znode就存在。会话结束时,将删除znode。由于这种行为,短暂的znode不允许有孩子。可以使用getEphemerals() API检索会话的临时列表。

getEphemerals()

检索会话为给定路径创建的临时节点列表。如果路径为空,它将列出该会话的所有临时节点。用例-如果需要收集会话的临时节点列表以进行重复数据输入检查,并且以顺序方式创建节点,因此您不知道重复检查的名称,则为示例用例。在这种情况下,可以使用getEphemerals()api获取会话的节点列表。这可能是服务发现的典型用例。

序列节点–唯一命名

创建znode时,您还可以要求ZooKeeper在路径末尾附加一个单调递增的计数器。该计数器对于父级znode是唯一的。计数器的格式为%010d,即10位数字,填充为0(零)(以这种格式格式化计数器以简化排序),即“0000000001“。有关此功能的示例使用,请参见队列食谱。注意:用于存储下一个序列号的计数器是父节点维护的带符号的int(4字节),当计数器的计数值超过2147483647时,计数器将溢出(导致名称 ”-2147483648“)。

容器节点

ZooKeeper具有容器znodes的概念。容器znode是特殊用途的znode,可用于诸如领导者,锁等配方。当删除容器的最后一个子代时,该容器将成为服务器将来要删除的候选对象。

给定此属性,您应该准备在容器znodes内创建子级时获取KeeperException.NoNodeException。即,在容器znode内创建子znode时,请始终检查KeeperException.NoNodeException并在发生时重新创建容器znode。

TTL节点

创建PERSISTENT或PERSISTENT_SEQUENTIAL znode时,可以选择为znode以毫秒为单位设置TTL。如果znode在TTL内没有被修改且没有子代,它将成为服务器将来某个时候要删除的候选者。

**注意:**TTL节点必须通过“系统”属性启用,因为默认情况下它们是禁用的。有关详细信息,请参见《管理员指南》。如果您尝试在没有设置适当的系统属性的情况下创建TTL节点,则服务器将抛出KeeperException.UnimplementedException。

2.时间在ZooKeeper

ZooKeeper通过多种方式跟踪时间:

  • Zxid对ZooKeeper状态的每次更改都会以zxid(ZooKeeper交易ID)的形式接收戳记。这将向ZooKeeper公开所有更改的总顺序。每个更改将具有唯一的zxid,并且如果zxid1小于zxid2,则zxid1在zxid2之前发生。
  • 版本号对节点的每次更改都会导致该节点的版本号之一增加。这三个版本号分别是版本(对znode的数据进行更改的次数),转换(对znode的子级进行更改的次数)和对版本(对znode的ACL进行更改的次数)。
  • 蜱当使用多服务器动物园管理员,服务器使用蜱定义事件,例如状态上载,会话超时,对等体之间的连接超时等蜱时间仅间接通过最小会话超时暴露的定时(2倍蜱时间) ; 如果客户端请求的会话超时小于最小会话超时,则服务器将告知客户端该会话超时实际上是最小会话超时。
  • 实时ZooKeeper完全不使用实时或时钟时间,只是在创建znode和修改znode时将时间戳记入stat结构中。
3.ZooKeeper统计结构

Zookeeper中每个znode的Stat结构由以下字段组成:

  • czxid导致创建此znode的更改的zxid。
  • mzxid上次修改此znode的更改的zxid。
  • pzxid最后一次修改此znode子级的更改的zxid。
  • ctime创建此znode时从纪元开始的时间(以毫秒为单位)。
  • mtime自上次修改此znode以来的时间(以纪元为单位)。
  • 版本此znode的数据更改数。
  • cversion此znode的子级更改的数量。
  • aversion此znode的ACL的更改数。
  • ephemeralOwner如果znode是一个临时节点,则此znode的所有者的会话ID。如果它不是临时节点,则它将为零。
  • dataLength此znode的数据字段的长度。
  • numChildren此znode的子级数。

三、ZooKeeper会话

ZooKeeper客户端通过使用语言绑定创建服务的句柄来与ZooKeeper服务建立会话。创建句柄后,该句柄将开始处于CONNECTING状态,并且客户端库尝试连接到组成ZooKeeper服务的服务器之一,此时它将切换为CONNECTED状态。在正常操作期间,客户端句柄将处于这两种状态之一。如果发生不可恢复的错误(例如会话到期或身份验证失败),或者应用程序显式关闭了句柄,则该句柄将移至CLOSED状态。

要创建客户端会话,应用程序代码必须提供一个连接字符串,其中包含用逗号分隔的host:port对列表,每个对对应于ZooKeeper服务器(例如“ 127.0.0.1:4545”或“ 127.0.0.1:3000,127.0.0.1” :3001,127.0.0.1:3002“)。ZooKeeper客户端库将选择一个任意服务器并尝试连接到该服务器。如果此连接失败,或者客户端由于任何原因与服务器断开连接,则客户端将自动尝试列表中的下一个服务器,直到(重新)建立连接为止。

假设我们有5台主机,现在更新列表以删除其中2台主机,连接到其余3台主机的客户端将保持连接状态,而连接到2台已删除主机的所有客户端将需要移动到其中一台3个主机,随机选择。如果断开连接,客户端将进入一种特殊模式,在该模式下,客户端将选择一个新服务器来使用概率算法进行连接,而不仅仅是轮询。

四、ZooKeeper手表

ZooKeeper中的所有读取操作**-getData()**,getChildren()和exist() -都可以选择将手表设置为副作用。这是ZooKeeper对手表的定义:手表事件是一次触发,发送给设置手表的客户端,该事件在设置手表的数据发生更改时发生。在手表的定义中,需要考虑三个关键点:

  • 一次性触发器数据更改后,一个监视事件将发送到客户端。例如,如果客户端执行getData(“ / znode1”,true),然后/ znode1的数据被更改或删除,则客户端将获得/ znode1的监视事件。如果/ znode1再次更改,则除非客户端执行另一次读取以设置新监视的操作,否则不会发送任何监视事件。
  • 发送给客户端这意味着事件正在传递给客户端,但是可能未成功到达更改操作的返回码到达发起更改的客户端之前,该事件尚未到达客户端。手表被异步发送给观察者。ZooKeeper提供了订购保证:客户端在第一次看到监视事件之前,将永远不会看到为其设置了监视的更改。网络延迟或其他因素可能导致不同的客户端在不同的时间看到监视并从更新中返回代码。关键是不同客户看到的所有内容将具有一致的顺序。
  • 为其设置手表的数据这是指节点可以更改的不同方式。可以将ZooKeeper视为维护两个手表列表:数据手表和儿童手表。getData()和exist()设置数据监视。getChildren()设置儿童手表。或者,可以考虑根据返回的数据类型设置手表。getData()和exist()返回有关节点数据的信息,而getChildren()返回子级列表。因此,setData()将触发数据监视是否设置了znode(假设设置成功)。成功的create()将触发对正在创建的znode的数据监视,以及对父znode的子监视。成功的delete()将同时触发要删除的znode的数据监视和子监视(因为不可能有更多的子监视)以及父znode的子监视。 手表在客户端连接到的ZooKeeper服务器上本地维护。这使手表的重量可以轻巧地设置,维护和分发。当客户端连接到新服务器时,将监视所有会话事件。与服务器断开连接时,不会收到手表。当客户端重新连接时,任何先前注册的手表将被重新注册并在需要时触发。通常,所有这些都是透明发生的。在某些情况下,可能会丢失监视:如果在断开连接的情况下创建并删除了znode,则会丢失尚未存在的znode的监视。

3.6.0中的新增功能:客户端还可以在znode上设置永久的,递归的手表,这些手表在被触发时不会被删除,并且会以递归方式触发已注册znode以及所有子znode的更改。

五、一致性保证

ZooKeeper是一项高性能,可扩展的服务。读取和写入操作都被设计为快速,尽管读取比写入快。原因是在读取的情况下,ZooKeeper可以提供较旧的数据,而这又归因于ZooKeeper的一致性保证:

  • 顺序一致性:来自客户端的更新将按照发送的顺序应用。
  • 原子性:更新成功或失败-没有部分结果。
  • 单个系统映像:无论客户端连接到哪个服务器,客户端都将看到相同的服务视图。也就是说,即使客户端故障转移到具有相同会话的其他服务器,客户端也永远不会看到系统的较旧视图。
  • 可靠性:一旦应用了更新,它将一直持续到客户端覆盖更新为止。此保证有两个推论:
  1. 如果客户获得成功的返回码,则将应用此更新。在某些故障(通信错误,超时等)上,客户端将不知道更新是否已应用。我们会采取措施以最大程度地减少失败,但是保证只有成功的返回码才能提供。(这在Paxos中称为单调性条件。)
  2. 从服务器故障中恢复时,客户端通过读取请求或成功更新看到的任何更新都不会回滚。
  • 及时性:保证系统的客户视图在特定的时间范围内(约数十秒)是最新的。客户端可以在此范围内看到系统更改,或者客户端将检测到服务中断。

使用这些一致性可以确保仅在ZooKeeper客户端上就可以轻松构建更高级别的功能,例如领导者选举,障碍,队列和读/写可撤销锁(无需对ZooKeeper进行任何添加)。有关更多详细信息,请参见食谱和解决方案。