ZooKeeper动态重新配置

概观

在3.5.0版本之前,Zookeeper的成员资格和所有其他配置参数都是静态的 - 在引导期间加载,在运行时是不可变的。运营商使用“滚动重启” - 一种手动密集且容易出错的方法,用于更改导致数据丢失和生产不一致的配置。

从3.5.0开始,不再需要“滚动重启”!ZooKeeper完全支持自动配置更改:Zookeeper服务器集,其角色(参与者/观察者),所有端口甚至仲裁系统都可以动态更改,无需中断服务,同时保持数据一致性。与ZooKeeper中的其他操作一样,立即执行重新配置。使用单个重新配置命令可以进行多项更改。动态重新配置功能不限制操作并发性,不需要在重新配置期间停止客户端操作,为管理员提供非常简单的界面,并且不会增加其他客户端操作的复杂性。

新的客户端功能允许客户端了解配置更改并更新存储在ZooKeeper句柄中的连接字符串(服务器及其客户端端口列表)。概率算法用于在新配置服务器之间重新平衡客户端,同时保持客户端迁移的范围与集合成员资格的变化成比例。

本文档提供了重新配置的管理员手册。有关重新配置算法,性能测量等的详细说明,请参阅我们的论文:

注意:从3.5.3开始,默认情况下禁用动态重新配置功能,并且必须通过reconfigEnabled配置选项明确打开 。

配置格式的更改

指定客户端端口

服务器的客户端端口是服务器接受客户端连接请求的端口。从3.5.0开始, 不应再使用clientPortclientPortAddress配置参数。相反,此信息现在是服务器关键字规范的一部分,如下所示:

server.<positive id> = <address1>:<port1>:<port2>[:role];[<client port address>:]<client port>**

客户端端口规范位于分号的右侧。客户端端口地址是可选的,如果未指定,则默认为“0.0.0.0”。像往常一样,角色也是可选的,它可以是 参与者观察者 (默认参与者)。

合法服务器声明的示例:

server.5 = 125.23.63.23:1234:1235;1236
server.5 = 125.23.63.23:1234:1235:participant;1236
server.5 = 125.23.63.23:1234:1235:observer;1236
server.5 = 125.23.63.23:1234:1235;125.23.63.24:1236
server.5 = 125.23.63.23:1234:1235:participant;125.23.63.23:1236

standaloneEnabled标志

在3.5.0之前,可以在独立模式或分布式模式下运行ZooKeeper。这些是单独的实现堆栈,并且无法在运行时在它们之间进行切换。默认情况下(为了向后兼容),standaloneEnabled设置为 true。使用此默认值的结果是,如果以单个服务器启动,则不允许集合增长,并且如果从多个服务器启动,则不允许缩小以包含少于两个参与者。

将标志设置为false会指示系统运行分布式软件堆栈,即使整体中只有一个参与者也是如此。为此,(静态)配置文件应包含:

standaloneEnabled=false**

使用此设置,可以启动包含单个参与者的ZooKeeper集合,并通过添加更多服务器来动态增长它。同样,通过删除服务器,可以缩小整体,以便只剩下一个参与者。

由于运行分布式模式可以提供更大的灵活性,因此我们建议将标志设置为false。我们预计将来会弃用旧版独立模式。

reconfigEnabled标志

从3.5.0开始,3.5.3之前,无法禁用动态重新配置功能。我们希望提供禁用重新配置功能的选项,因为启用了重新配置后,我们有一个安全问题,即恶意行为者可以对ZooKeeper集合的配置进行任意更改,包括将受感染的服务器添加到集合中。我们倾向于由用户自行决定是否启用它,并确保采用适当的安全措施。因此,在3.5.3 中引入了reconfigEnabled配置选项,以便可以完全禁用重新配置功能,并且默认情况下,无论是否使用身份验证通过reconfig API重新配置群集的任何尝试都将失败,除非reconfigEnabled设置为 true

要将该选项设置为true,配置文件(zoo.cfg)应包含:

reconfigEnabled=true

动态配置文件

从3.5.0开始,我们将区分可在运行时更改的动态配置参数和静态配置参数,这些参数在服务器引导时从配置文件中读取,并且在执行期间不会更改。目前,以下配置关键字被视为动态配置的一部分:服务器 和权重

动态配置参数存储在服务器上的单独文件中(我们称之为动态配置文件)。此文件使用新的dynamicConfigFile关键字从静态配置文件链接 。

zoo_replicated1.cfg

tickTime=2000
dataDir=/zookeeper/data/zookeeper1
initLimit=5
syncLimit=2
dynamicConfigFile=/zookeeper/conf/zoo_replicated1.cfg.dynamic

zoo_replicated1.cfg.dynamic

server.1=125.23.63.23:2780:2783:participant;2791
server.2=125.23.63.24:2781:2784:participant;2792
server.3=125.23.63.25:2782:2785:participant;2793

当整体配置更改时,静态配置参数保持不变。动态参数由ZooKeeper推送,并覆盖所有服务器上的动态配置文件。因此,不同服务器上的动态配置文件通常是相同的(它们只能在重新配置进行时暂时不同,或者如果新配置尚未传播到某些服务器)。创建后,不应手动更改动态配置文件。仅通过下面概述的新重新配置命令进行更改。请注意,更改脱机集群的配置可能会导致存储在ZooKeeper日志中的配置信息(以及从日志中填充的特殊配置znode)不一致,因此非常不鼓励。

例2

用户可能更喜欢最初指定单个配置文件。因此以下也是合法的:

zoo_replicated1.cfg

tickTime=2000
dataDir=/zookeeper/data/zookeeper1
initLimit=5
syncLimit=2
clientPort=

如果每个服务器上的配置文件尚未采用此格式,则它们将自动拆分为动态和静态文件。因此,上面的配置文件将自动转换为示例1中的两个文件。请注意,如果它们是冗余的(如上例所示),则在此过程中将自动删除clientPort和clientPortAddress行(如果已指定)。备份原始静态配置文件(在.bak文件中)。

向后兼容性

我们仍然支持旧的配置格式。例如,以下配置文件是可接受的(但不推荐):

zoo_replicated1.cfg

tickTime=2000
dataDir=/zookeeper/data/zookeeper1
initLimit=5
syncLimit=2
clientPort=2791
server.1=125.23.63.23:2780:2783:participant
server.2=125.23.63.24:2781:2784:participant
server.3=125.23.63.25:2782:2785:participant

在引导期间,将创建动态配置文件,并包含配置的动态部分,如前所述。但是,在这种情况下,“clientPort = 2791”行将保留在服务器1的静态配置文件中,因为它不是冗余的 - 它没有使用格式指定为“server.1 = ...”的一部分在配置格式的更改部分中进行了解释。如果调用重新配置来设置服务器1的客户端端口,我们从静态配置文件中删除“clientPort = 2791”(动态文件现在包含此信息作为服务器1规范的一部分)。

升级到3.5.0

只有在将整体升级到3.4.6版本后,才能将正在运行的ZooKeeper集合升级到3.5.0。请注意,这仅适用于滚动升级(如果您完全关闭系统,则无需通过3.4.6)。如果您尝试滚动升级而不通过3.4.6(例如从3.4.5),您可能会收到以下错误:

2013-01-30 11:32:10,663 [myid:2] - INFO [localhost/127.0.0.1:2784:QuorumCnxManager$Listener@498] - Received connection request /127.0.0.1:60876
2013-01-30 11:32:10,663 [myid:2] - WARN [localhost/127.0.0.1:2784:QuorumCnxManager@349] - Invalid server id: -65536

在滚动升级期间,每个服务器依次关闭并使用新的3.5.0二进制文件重新启动。在使用3.5.0二进制文件启动服务器之前,我们强烈建议更新配置文件,以便所有服务器语句“server.x = ...”都包含客户端端口(请参阅“ 指定客户端端口 ”一节)。如前所述,您可以将配置保留在单个文件中,并保留clientPort / clientPortAddress语句(尽管如果以新格式指定客户端端口,这些语句现在是多余的)。

动态重新配置ZooKeeper集合

ZooKeeper Java和C API使用getConfig和reconfig命令进行扩展,以便于重新配置。这两个命令都有一个同步(阻塞)变量和一个异步变量。我们在这里使用Java CLI演示这些命令,但请注意,您可以类似地使用C CLI或直接从程序调用命令,就像任何其他ZooKeeper命令一样。

API

Java和C客户端都有两组API。

  • 重新配置API:重新配置API用于重新配置ZooKeeper集群。从3.5.3开始,重新配置Java API将从ZooKeeper类移入ZooKeeperAdmin类,使用此API需要ACL设置和用户身份验证(有关详细信息,请参阅安全性)。

  • 获取配置API:获取配置API用于检索存储在/ zookeeper / config znode中的ZooKeeper集群配置信息。使用此API不需要特定的设置或身份验证,因为/ zookeeper / config对任何用户都是可读的。

安全

3.5.3之前,没有强制安全机制来重新配置,因此任何可以连接到ZooKeeper服务器集合的ZooKeeper客户端都可以通过重新配置来更改ZooKeeper集群的状态。因此,恶意客户端可能将受损服务器添加到集合,例如,添加受损服务器或移除合法服务器。像这样的案例可能是个案的安全漏洞。

为解决此安全问题,我们从3.5.3开始引入了对reconfig的访问控制,以便只有一组特定用户可以使用reconfig命令或API,并且需要显式配置这些用户。此外,ZooKeeper集群的设置必须启用身份验证,以便可以对ZooKeeper客户端进行身份验证。

我们还为在安全环境中(即在公司防火墙后面)操作ZooKeeper集合并与之交互的用户提供了逃生舱口。对于那些想要使用重新配置功能但又不希望为重新配置访问检查配置授权用户的明确列表的开销的用户,他们可以将“skipACL”设置为“yes”,这将跳过ACL检查并允许任何用户重新配置簇。

总的来说,ZooKeeper为重新配置功能提供了灵活的配置选项,允许用户根据用户的安全要求进行选择。我们由用户自行决定是否采取适当的安全措施。

  • 访问控制:动态配置存储在特殊的znode ZooDefs.CONFIG_NODE = / zookeeper / config中。默认情况下,此节点对所有用户都是只读的,但超级用户和显式配置为写访问权限的用户除外。需要使用reconfig命令或重新配置API的客户端应配置为具有CONFIG_NODE写入权限的用户。默认情况下,只有超级用户具有完全控制权,包括对CONFIG_NODE的写访问权。通过设置具有与指定用户关联的写入权限的ACL,可以通过超级用户授予其他用户写入权限。可以在ReconfigExceptionTest.java和TestReconfigServer.cc中找到有关如何设置ACL并将重新配置API与身份验证配合使用的一些示例。

  • 身份验证:用户身份验证与访问控制正交,并委托给ZooKeeper可插入身份验证方案支持的现有身份验证机制。有关此主题的更多详细信息,请参阅 ZooKeeper和SASL

  • 禁用ACL检查:ZooKeeper支持 “skipACL”选项,以便在skipACL设置为“yes”时完全跳过ACL检查。在这种情况下,任何未经身份验证的用户都可以使用reconfig API。

检索当前的动态配置

动态配置存储在特殊的znode ZooDefs.CONFIG_NODE = / zookeeper / config中。新的 configCLI命令读取此znode(目前它只是一个包装器get /zookeeper/config)。与正常读取一样,要检索最新的提交值,您应该 sync先执行。

[zk: 127.0.0.1:2791(CONNECTED) 3] config
server.1=localhost:2780:2783:participant;localhost:2791
server.2=localhost:2781:2784:participant;localhost:2792
server.3=localhost:2782:2785:participant;localhost:2793

注意输出的最后一行。这是配置版本。版本等于创建此配置的重新配置命令的zxid。第一个已建立配置的版本等于第一个成功建立的领导者发送的NEWLEADER消息的zxid。将配置写入动态配置文件时,该版本将自动成为文件名的一部分,并使用新动态配置文件的路径更新静态配置文件。保留与早期版本对应的配置文件以进行备份。

在引导期间,从文件名中提取版本(如果存在)。用户或系统管理员不得手动更改版本。系统使用它来了解哪种配置是最新的。手动操作可能会导致数据丢失和不一致。

就像get命令一样, configCLI命令接受-w 标志用于在znode上设置监视,而-s标志用于显示znode的统计信息。它还接受一个新标志 -c,它只输出与当前配置相对应的版本和客户端连接字符串。例如,对于上面的配置,我们将得到:

[zk: 127.0.0.1:2791(CONNECTED) 17] config -c
400000003 localhost:2791,localhost:2793,localhost:2792

请注意,直接使用API​​时,将调用此命令 getConfig

作为任何读取命令,它返回客户端所连接的跟随者已知的配置,这可能稍微过时。可以使用该sync命令获得更强的保证。例如,使用Java API:

zk.sync(ZooDefs.CONFIG_NODE, void_callback, context);
zk.getConfig(watcher, callback, context);

注意:在3.5.0中,传递给sync()命令的路径并不重要, 因为所有服务器的状态都与领导者保持同步(因此可以使用不同的路径而不是ZooDefs.CONFIG_NODE)。但是,这可能会在未来发生变化。

修改当前动态配置

通过reconfig命令修改配置 。有两种重新配置模式:增量和非增量(批量)。非增量只是指定系统的新动态配置。incremental指定对当前配置的更改。该reconfig命令返回新配置。

下面是一些示例:ReconfigTest.java, ReconfigRecoveryTest.java和 TestReconfigServer.cc

一般

删除服务器:任何服务器都可以被除去,其中包括前导序列(虽然除去前导将导致在很短的不可用性,参见图6和8中的)。服务器不会自动关闭。相反,它成为“无投票的追随者”。这有点类似于观察者,因为其投票不计入提交作业所需的法定人数。但是,与非投票的关注者不同,观察者实际上看不到任何操作提议,也没有确认它们。因此,与观察者相比,无投票的追随者对系统吞吐量具有更显着的负面影响。无投票的跟随者模式应仅用作临时模式,然后关闭服务器,或将其作为跟随者或观察者添加到整体。出于两个主要原因,我们不会自动关闭服务器。第一个原因是我们不希望所有连接到此服务器的客户端立即断开连接,导致大量连接请求到其他服务器。相反,如果每个客户端决定何时独立迁移,则会更好。第二个原因是,为了将服务器从“观察者”改为“参与者”,有时(很少)可能需要删除服务器(这在本节中有解释)附加评论)。

请注意,新配置应具有最少数量的参与者才能被视为合法。如果该建议改变将离开集群少于2名人参加,并启用独立模式(standaloneEnabled = TRUE,请参见standaloneEnabled标志),则重新配置将不会被处理(BadArgumentsException)。如果禁用独立模式(standaloneEnabled = false),则合法保留1个或多个参与者。

添加服务器:在调用重新配置之前,管理员必须确保新配置的法定人数(大多数)参与者已连接并与当前领导者同步。为了实现这一点,我们需要在领导者正式成为整体之前将新的加入服务器连接到领导者。这是通过使用服务的初始列表启动加入服务器来完成的,这些服务器在技术上不是系统的合法配置,但是(a)包含加入者,以及(b)向加入者提供足够的信息以便它找到并连接对当前的领导者。我们列出了安全执行此操作的几个不同选项。

  1. 加入者的初始配置由最后提交的配置中的服务器和一个或多个加入者组成,其中 加入者被列为观察者。 例如,如果服务器D和E同时添加到(A,B,C)并且服务器C被删除,则D的初始配置可以是(A,B,C,D)或(A,B) ,C,D,E),其中D和E被列为观察者。类似地,E的配置可以是(A,B,C,E)或(A,B,C,D,E),其中D和E被列为观察者。请注意,将加入者列为观察者实际上不会使他们成为观察者 - 它只会阻止他们意外地与其他加入者形成仲裁。相反,他们将联系当前配置中的服务器,并采用最后提交的配置(A,B,C),其中不存在加入者。在发生这种情况时,会自动备份和替换加入者的配置文件。在连接到当前领导者之后,加入者成为非投票的追随者,直到系统被重新配置并且它们被添加到整体(作为参与者或观察者,视情况而定)。
  2. 每个加入者的初始配置由最后提交的配置中的服务器+ 加入者本身组成,列为参与者。例如,要将新服务器D添加到由服务器(A,B,C)组成的配置中,管理员可以使用由服务器(A,B,C,D)组成的初始配置文件启动D. 如果D和E同时加到(A,B,C),D的初始配置可以是(A,B,C,D),E的配置可以是(A,B,C, E)。类似地,如果添加D并同时去除C,则D的初始配置可以是(A,B,C,D)。切勿在初始配置中列出多个连接器作为参与者(请参阅下面的警告)。
  3. 无论是作为观察者还是作为参与者列出木匠,只要当前领导者在列表中,也可以不列出所有当前配置服务器。例如,当添加D时,如果A是当前的领导者,我们可以使用仅包含(A,D)的配置文件来启动D. 然而,这更加脆弱,因为如果A在D正式加入整体之前失败,则D不知道其他任何人,因此管理员将不得不干预并重新启动D与另一个服务器列表。

######注意

警告

切勿在与参与者相同的初始配置中指定多个加入服务器。目前,加入服务器不知道他们正在加入现有的整体; 如果多个加入者被列为参与者,他们可能会形成一个独立的法定人数,从而产生一种裂脑情况,例如独立于主要整体的处理操作。可以在初始配置中将多个加入者列为观察者。

如果现有服务器的配置发生更改,或者在连接器成功连接并了解更改配置更改之前它们变得不可用,则可能需要使用更新的配置文件重新启动连接器才能进行连接。

最后,请注意,一旦连接到领导者,joiner就会采用最后提交的配置,在该配置中它不存在(在重写之前备份joiner的初始配置)。如果连接器在此状态下重新启动,则由于配置文件中不存在该连接器,因此无法启动。为了启动它,您将再次必须指定初始配置。

修改服务器参数:通过将服务器参数添加到具有不同参数的集合,可以修改服务器的任何端口或其角色(参与者/观察者)。这适用于增量和批量重配置模式。没有必要删除服务器,然后将其添加回来; 只需指定新参数,就好像服务器尚未在系统中一样。服务器将检测配置更改并执行必要的调整。请参阅“ 增量模式”一节中的示例, 以及“ 附加注释 ”一节中此规则的例外情况。

也可以更改集合使用的仲裁系统(例如,将多数仲裁系统更改为动态的分层仲裁系统)。但是,只允许使用批量(非增量)重新配置模式。通常,增量重新配置仅适用于多数仲裁系统。批量重新配置适用于Hierarchical和Majority Quorum Systems。

性能影响:删除关注者时几乎没有性能影响,因为它不会自动关闭(删除的效果是服务器的投票不再被计算)。添加服务器时,没有领导者更改,也没有明显的性能中断。有关详细信息和图形,请参阅图6,在图7和8 的纸张

在下列情况之一发生领导者变更时,最严重的中断将发生:

  1. 领导者从整体中移除。
  2. 领导者的角色从参与者变为观察者。
  3. 领导者用于将事务发送给其他人(仲裁端口)的端口被修改。

在这些情况下,我们执行领导者交接,旧领导者提名新领导者。将得到的不可用性通常比当龙头崩溃,因为检测前导故障是不必要的,并且通常选举可以在越区切换时,能够避免一个新的领导人缩短(见图6和图8中的)。

修改服务器的客户端端口后,它不会删除现有的客户端连接。与服务器的新连接必须使用新的客户端端口。

进度保证:在重新配置操作的调用之前,需要为旧配置提供法定数量的连接,并为ZooKeeper连接以便能够取得进展。调用reconfig后,必须提供旧配置和新配置的法定数量。最终转换发生一次(a)新配置被激活,以及(b)在领导者激活新配置之前安排的所有操作都被提交。一旦发生(a)和(b),则只需要法定数量的新配置。但请注意,客户端无法看到(a)和(b)。具体地说,当重新配置操作提交时,它仅意味着领导者发出了激活消息。它并不一定意味着新配置的法定数量获得此消息(为了激活它而需要)或(b)已经发生。如果想要确保(a)和(b)都已经发生(例如,为了知道关闭已删除的旧服务器是安全的),可以简单地调用更新(set-data,或其他一些仲裁操作,但不是a sync)并等待它提交。另一种实现此目的的方法是在重新配置协议中引入另一轮(为简单起见,与Zab兼容,我们决定避免使用)。

增量模式

增量模式允许在当前配置中添加和删除服务器。允许多次更改。例如:

> reconfig -remove 3 -add
server.5=125.23.63.23:1234:1235;1236

add和remove选项都有一个逗号分隔的参数列表(没有空格):

> reconfig -remove 3,4 -add
server.5=localhost:2111:2112;2113,6=localhost:2114:2115:observer;2116

server语句的格式与“ 指定客户端端口 ”一节中描述的格式完全相同,并包含客户端端口。请注意,这里不是“server.5 =”,而是可以说“5 =”。在上面的示例中,如果服务器5已经在系统中,但具有不同的端口或不是观察者,则会更新它,并且一旦配置提交成为观察者并开始使用这些新端口。这是一种简单的方法,可以将参与者转变为观察者,反之亦然或更改任何端口,而无需重新启动服务器。

ZooKeeper支持两种类型的Quorum系统 - 简单的Majority系统(领导者在收到大多数选民的ACK后提交操作)和更复杂的Hierarchical系统,其中不同服务器的投票具有不同的权重,服务器分为投票组。目前,仅当领导者已知的最后一个提议配置使用多数仲裁系统时才允许增量重新配置(否则抛出BadArgumentsException)。

增量模式 - 使用Java API的示例:

List<String> leavingServers = new ArrayList<String>();
leavingServers.add("1");
leavingServers.add("2");
byte[] config = zk.reconfig(null, leavingServers, null, -1, new Stat());

List<String> leavingServers = new ArrayList<String>();
List<String> joiningServers = new ArrayList<String>();
leavingServers.add("1");
joiningServers.add("server.4=localhost:1234:1235;1236");
byte[] config = zk.reconfig(joiningServers, leavingServers, null, -1, new Stat());

String configStr = new String(config);
System.out.println(configStr);

还有一个异步API,以及一个接受逗号分隔的字符串而不是List的API。请参阅src / java / main / org / apache / zookeeper / ZooKeeper.java。

非增量模式

第二种重新配置模式是非增量的,即客户端提供新动态系统配置的完整规范。新配置可以在适当的位置给出,也可以从文件中读取:

> reconfig -file newconfig.cfg

//newconfig.cfg是动态配置文件,请参阅动态配置文件

> reconfig -members
server.1=125.23.63.23:2780:2783:participant;2791,server.2=125.23.63.24:2781:2784:participant;2792,server.3=125.23.63.25:2782:2785:participant;2793}}

新配置可能使用不同的Quorum系统。例如,即使当前集合使用多数仲裁系统,您也可以指定分层仲裁系统。

批量模式 - 使用Java API的示例:

List<String> newMembers = new ArrayList<String>();
newMembers.add("server.1=1111:1234:1235;1236");
newMembers.add("server.2=1112:1237:1238;1239");
newMembers.add("server.3=1114:1240:1241:observer;1242");

byte[] config = zk.reconfig(null, null, newMembers, -1, new Stat());

String configStr = new String(config);
System.out.println(configStr);

还有一个异步API,以及一个接受逗号分隔的String的API,其中包含新成员而不是List。请参阅src / java / main / org / apache / zookeeper / ZooKeeper.java。

有条件的重新配置

有时(特别是在非增量模式下)新建议的配置取决于客户端“认为”当前配置的内容,并且应仅应用于该配置。具体而言,reconfig仅当领导者的最后一个配置具有指定版本时才会成功。

> reconfig -file <filename> -v <version>

在先前列出的Java示例中,可以指定用于调整重新配置的配置版本而不是-1。

错误条件

除了正常的ZooKeeper错误情况之外,重新配置可能会因以下原因而失败:

  1. 另一个reconfig当前正在进行中(ReconfigInProgress)
  2. 如果启用独立模式,建议的更改将使群集的参与者少于2个,或者,如果禁用独立模式,则其合法保留1个或多个参与者(BadArgumentsException)
  3. 在重新配置处理开始时,新配置的法定数量未与领导者连接并且是最新的(NewConfigNoQuorum)
  4. -v x已指定,但y最新配置的版本 不是 x(BadVersionException)
  5. 请求了增量重新配置,但是领导者的最后一个配置使用的Quorum系统与Majority系统不同(BadArgumentsException)
  6. 语法错误(BadArgumentsException)
  7. 从文件读取配置时的I / O异常(BadArgumentsException)

其中大部分都是通过ReconfigFailureCases.java中的测试用例来说明的 。

附加评论

活动:为了更好地理解增量和非增量重新配置之间的区别,假设客户端C1将服务器D添加到系统,而另一个客户端C2添加服务器E.使用非增量模式,每个客户端将首先调用config以查找当前配置,然后通过添加自己的建议服务器在本地创建新的服务器列表。然后可以使用非增量提交新配置 reconfig命令。两次重新配置完成后,将只添加E或D中的一个(不是两者),具体取决于哪个客户端的请求第二个到达领导者,覆盖先前的配置。另一个客户端可以重复该过程,直到其更改生效。此方法可保证系统范围内的进度(即,对于其中一个客户端),但不能确保每个客户端都成功。要获得更多控制,C2可以请求仅在当前配置的版本未更改的情况下执行重新配置,如“ 条件重新配置 ”一节中所述。通过这种方式,如果C1的配置首先到达领导者,它可以避免盲目地覆盖C1的配置。

对于增量重新配置,因为它们是由前导一前一后简单地应用到当前的配置,不管它是(假设该第二重新配置请求到达前导它发送提交消息用于第一重新配置请求后两个更改将生效 - 目前领导者将拒绝提议重新配置,如果另一个已经等待)。由于两个客户都保证取得进步,因此这种方法可以保证更强的活力。实际上,多个并发重新配置可能很少见。非增量重新配置是目前动态更改Quorum系统的唯一方法。目前只允许使用Majority Quorum System进行增量配置。

将观察者变为追随者:显然,如果发生错误(2),即如果少于允许的最小参与者数量,则将参与投票的服务器更改为观察者可能会失败。然而,将观察者转换为参与者有时可能会因为更微妙的原因而失败:例如,假设当前配置是(A,B,C,D),其中A是领导者,B和C是追随者和D是一个观察者。另外,假设B已经崩溃了。如果提交重新配置,其中D被称为跟随者,则它将失败并出现错误(3),因为在此配置中,新配置中的大多数选民(任何3个选民)必须连接并且是最新的与领导者。观察者无法确认重新配置期间发送的历史记录前缀,因此它不计入这3个所需的服务器,重新配置将被中止。如果发生这种情况,客户端可以通过两个reconfig命令实现相同的任务:首先调用reconfig以从配置中删除D,然后调用第二个命令将其作为参与者(跟随者)添加回来。在中间状态期间,D是无投票的跟随者,并且可以确认在第二次重新配置命令期间执行的状态转移。

重新平衡客户端连接

启动ZooKeeper集群时,如果为每个客户端提供相同的连接字符串(服务器列表),客户端将随机选择列表中的服务器进行连接,这使得每个服务器的预期客户端连接数相同。的服务器。我们实现了一种方法,当服务器组通过重新配置进行更改时,该方法会保留此属性。见第4和5.1在

为了使该方法起作用,所有客户端都必须订阅配置更改(通过直接或通过getConfigAPI命令设置/ zookeeper / config上的监视)。当手表被触发,客户端应该通过调用读取新的配置sync,并getConfig与配置是否确实是新调用的updateServerListAPI命令。为了避免大规模客户端同时迁移,最好让每个客户端在调用之前随机地短时间休眠updateServerList

可以在以下几个示例中找到: StaticHostProviderTest.java和 TestReconfig.cc

示例(这不是一个配方,而是一个简单的例子,只是为了解释一般的想法):

public void process(WatchedEvent event) {
    synchronized (this) {
        if (event.getType() == EventType.None) {
            connected = (event.getState() == KeeperState.SyncConnected);
            notifyAll();
        } else if (event.getPath()!=null &&  event.getPath().equals(ZooDefs.CONFIG_NODE)) {
            // in prod code never block the event thread!
            zk.sync(ZooDefs.CONFIG_NODE, this, null);
            zk.getConfig(this, this, null);
        }
    }
}

public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {
    if (path!=null &&  path.equals(ZooDefs.CONFIG_NODE)) {
        String config[] = ConfigUtils.getClientConfigStr(new String(data)).split(" ");   // similar to config -c
        long version = Long.parseLong(config[0], 16);
        if (this.configVersion == null){
             this.configVersion = version;
        } else if (version > this.configVersion) {
            hostList = config[1];
            try {
                // the following command is not blocking but may cause the client to close the socket and
                // migrate to a different server. In practice its better to wait a short period of time, chosen
                // randomly, so that different clients migrate at different times
                zk.updateServerList(hostList);
            } catch (IOException e) {
                System.err.println("Error updating server list");
                e.printStackTrace();
            }
            this.configVersion = version;
        }
    }
}

 

 

转载来源:https://github.com/apache/zookeeper/blob/master/zookeeper-docs/src/main/resources/markdown/zookeeperReconfig.md

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐