ZooKeeper动态配置(十四)
概述
在3.5.0发行之前,ZK的全体成员和所有其它的配置参数是静态加载的在启动的时候并且在运行的时候不可变。操作员诉诸于"滚动重启" - 一个手动密集和改变配置文件容易出错的方法,导致在生产环境数据丢失和不一致。从3.5.0开始,"滚动重启"不再需要!ZK开始支持自动地配置改变:ZK服务端的设置,它们的角色(参与者/观察者),所有端口,甚至法定人数系统可以被动态地改变,而不用服务中断和维修数据一致性。重新配置被立刻执行。就像ZK中的其它操作。很多改变可以通过使用一个重新配置命令完成。动态重新配置功能不限制操作的并发,不要求客户端操作停止在重新配置时,拥有一个非常简单的管理者接口,并且不会对其它客户端操作增加复杂性。
新的客户端特性允许客户端发现配置改变,并且更新存储在它们的ZK句柄中的连接字符串(服务端列表和它拉的客户端端口)。一个概率算法被使用来平衡新配置的服务端上的客户端。同时保持客户端迁移的程度与集群成员的变化成正比。
这个文档提供了重新配置的管理员手册。对于重新配置的算法,性能测量,还有其它更多的详细描述请参考我们的论文:Shraer, A., Reed, B., Malkhi, D., Junqueira, F. Dynamic Reconfiguration of Primary/Backup Clusters. In USENIX Annual Technical Conference (ATC) (2012), 425-437
连接:paper (pdf), slides (pdf), video, hadoop summit slides。
配置格式的改变
指定客户端商品
服务端的客户端端口是服务端接受客户端连接请求的端口。从3.5.0开始clientPort和clientPortAddress配置参数不应该被使用。相反地,这个信息现在是服务端关键词规范的一部分。它变成下面这样:
server.<positive id> = <address1>:<port1>:<port2>[:role];[<client port address>:]<client port>
客户端端口的规范是在分号的右边。客户端端口地址是可选的,如果没有指定它默认是"0.0.0.0"。想往常一样,角色也是可选的,它可以是participant或者observer(默认是participant)。
合法的服务端声明:
-
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之前,我们可以以单机模式运行ZK或以分布式模式。他们是不同的实现,并且在运行时切换是不可能的。默认(为了向后兼容)standaloneEnabled被设置成true。使用这个默认值的结果是如果启动一个单独的服务端群集将不会起来,并且如果启动多于一个服务端它将不允许缩小到小于两个参考者。
设置这个标识为false指示系统以分布式运行,即使集群里只有一个参与者。为了实现这样配置文件应该包含:
standaloneEnabled=false
有了这个设置可以启动只包含一个参与者的集群,并且可以动态地增加更多服务端。相似地,它也可以缩小一个群集到只有一个参考者,通过移除服务端。
因为运行在分布式模式允许更多灵活性,我们建议设置这个标识为false。我们期待遗留的单机模式在未来将被标识为过时的。
动态配置文件
从3.5.0开始我们开始区分动态配置参数,它可以在运行时被改变,和静态配置参数,当服务端启动的时候从配置文件中读取并且不能改变在运行的时候。现在下面的配置关键词被认识是动态配置的一部分:server, group和 weight。
动态配置参数被存储在服务端一个单独的文件中(我们称它为动态配置文件)。这个文件被连接在静态文件中使用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
当我们集成配置改变,静态配置参数保持不变。动态的参数被ZK推出并且在所有的服务端覆盖动态配置文件。因此,在不同服务端的动态配置文件通常是一样的(他们可以只有瞬间不同当一个重新配置正在处理中,或者如果一个新的配置还没有传播到一些服务端)。一旦被创建,动态的配置文件不应该被手动地修改。改变只能通过下面标出的新的重新配置命令实现。注意改变一个离线的集群的配置可能导致不一致相对于配置文件被存储在ZK的日志(特殊的配置znode,从日志中填充),并且因此非常地不推荐。
例子2
用户可能更倾向于一开始就指定一个配置文件。因此下面的也是合法的:
zoo_replicated1.cfg
tickTime=2000 dataDir=/zookeeper/data/zookeeper1 initLimit=5 syncLimit=2 clientPort=2791 // note that this line is now redundant and therefore not recommended 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
在每一个服务端的配置文件将被自动地分为自动和静态文件,如果他们不是以这种格式。所以上面的配置文件将被自动地转换为在例子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"行将保留在server 1的静态配置文件中,因为它不是冗余的 --它没有被指定作为 "server.1=..."的一部分,使用在Changes to Configuration Format部分讲解的格式。如果一个重新配置被调用来设置server 1的客户端部分,我从静态配置文件中删除"clientPort=2791"。(现在动态文件包含这个信息作为server1规范的一部分)。
升级到3.5.0
升级一个运行中的集群到3.5.0应该被完成只有在升级你的集群到3.4.6之后。注意这只对滚动升级的时候才是必须的(如果你可以完全地关闭系统,你不需要经过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=..."包含客户端端口(参考Specifying the client port的部分)。正如前面讲解的那样,你可以把配置放在一个的文件中,clientPort/clientPortAddress声明也一样。(尽管如果你指定客户端商品以新的格式,这些声明现在就是冗余的)。
ZK群集的动态重新配置
ZK的Java和C的API暴露了getConfig和reconfig命令来帮助重新配置。两个命令都有一个同步(阻塞)变体和一个异步变体。我们这里演示这些命令使用有Java 命令行界面,但是注意你可以相似地使用C命令行界面或者直接从程序中地调用这个命令就像其它ZK命令。
检索当前动态的配置
动态的配置被存储在一个特别的znodeZooDefs.CONFIG_NODE=/zookeeper/config。新的config命令行界面命令读这个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 version=400000003
注意输出的最后一行。这是配置版本号。这个版本号等于创建这个配置的重新配置命令的zxid。第一个被建立的配置版本号等于被第一个成功建立的领导者发出的NEWLEADER消息的zxid。当一个配置被写入到一个动态配置文件,版本号自动地变为文件名的一部分并且静态配置文件被更新带着指向的新的动态配置文件的路径。被保留的早期版本的配置文件是为了备份目的。
在启动期间版本号(如果存在)被从文件名中提取出来。版本号应该永远不被用户手动地修改或系统管理员。它被系统用来确定那一个配置是最新的。手动地修改可能导致数据丢失和不一致性。
就像一个get命令,config命令行命令接受-w标识来在这个znode上设置一个监视器,-s标识用来显示znode的统计。它另外接受一个新的标识 -c ,它只输出版本号和当前配置对应的连接字符串。例如,对于上面的配置,我们可以get:
[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命令来完成配置的修改。有两个重修改的模式:增量和非增量(bulk)。非增加的简单地指定系统新的动态配置。增量的指定当前配置的改变。reconfig命令返回新的配置。
几个例子在:ReconfigTest.java, ReconfigRecoveryTest.java and TestReconfigServer.cc。
通常
删除服务端:
任何服务端可以被删除,包括领导者(尽管删除领导者将导致一小会儿的不可胳膊,参考paper中的图6和8)。服务端将不会被自动地关闭。相反,它变成一个"不投票的追随者"。这有点类似于一个观察者因为它的投票不被计算在提交操作的投票的法定人数当中。然而,和非投票的追随者不一样的是,观察者不会真正地看到任何操作提议和不会回应它们。因此一个非投票的追随者具有更大的负面影响对于系统的吞吐量相对于观察者。非投票的追随者模型应该只被用作临时模型。在关闭服务端之前或者增加一个追随者或观察者到集群中。我们不自动地关闭服务端为了两个主要原因。第一个是我们不想所有连接到这个服务端的连接立刻失连,导致大量的连接请求到其它服务端。相反地,如果每一个服务端决定什么时候自主地迁移是更好的。第二个原因是删除一个服务端可能很少是必须的为了把它从“观察者”变为“参考者”(这在Additional comments部分讲解过)。
注意新的配置应该有最小数量的参考者为了被认为是合法的。如果提出的改变将使集群小于2个参考者和单机模式被启用(standaloneEnabled=true,参考The standaloneEnabled flag部分),这个重新配置操作将不会被执行。如果单机模式被禁用(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),这里的加入者不存在。加入者的配置文件被备份起来并且被自动地替换录这发生。在连接到当前的领导者之后,加入者变为非投票的追随者直接系统被重新配置并且他们被加入到集群(作为参考者或才观察者,视情况而定)
- 每一个加入者的初始配置包含了最后提交的配置服务端和加入者自己,作为参与者被列出。例如,增加一个新的服务端D到一个(A,B,C),管理者可以启动D使用一个包含(A,B,C,D)的初始配置文件。如果D和E同时加入到(A,B,C),D的初始配置将会是(A,B,C,D)和E的初始配置将会是(A,B,C,E)。相似地,如果同时D被加入和C被删除,D的初始配置将会是(A, B, C, D)。永远不要列出超过1个作为参与者的加入者在初始配置中(参考下面的警告)
- 作为观察者或参考者列出加入者,所有的当前服务端不列出也是可以的,只要当前的领导者在列表中。例如,当增加D我们可以启动D使用一个只包含(A,D)的配置文件如果A是当前的领导者。然而这是更脆弱的因为如果A在D正式加入群集之前失效,D不知道任何其它的服务端并且因此管理员将不得不干预并且重启D用另一个服务端列表。
警告
永远不要在相同的初始配置中指定多于一个作为参考者加入的服务端。当前,正在加入的服务端不知道他们正在加入一个存在的集群;如果多个加入者被作为参考者列出,他们可能形成一个独立的法定人数创建一个脑裂的情况例如独立于你的主集群中处理操作。作为观察者列表多个加入者是OK的。
最后,注意一旦连接到一个领导者,加入者接受最后提前的配置,在这里它是不在的(加入者的初始配置被备份在被重写之前)。如果加入者在这个状态重启,它将不能启动因为它是不存在它的配置文件中的。为了启动它,你将不得不再次指定一个配置文件。
修改服务端参数:你可以修改服务端的任何端口,或者它的角色(参与者/观察者)通过加入它到集群中使用不同的参数。这可以以增量和非增量的重新配置模式一实现。不必要删除服务端然后再加回来。只需要指定新的参数就好像服务端还没有在系统中。服务端将检测到配置改变并且执行必要的调整。参考Incremental mode部分的例子和Additional comments部分这个规则的一个例外。
也可以通过集群改变法定人数系统(例如,实时地改变多数法定人数系统为分层法定人数系统)。然而这只能使用非增加地重新配置模式。通常增量的重新配置只和多数法定人数系统一起工作。非增加重新配置可以和分层和多数法定人数系统工作。
性能影响:当删除一个追随者的时候没有特别地性能影响,因为它不能自动地关闭(删除的影响是这个服务端的投票不再被计算在内)。当增加一个服务端,没有领导者改变和没有可观察性能中断。更详细的内容和曲线图请参考paper中的Figures6,7,8。
更严重的中断将会发生当领导者改变以下面的案例发生:
- 领导者从集群中被删除。
- 领导者的角色从参与者被改变为观察者
- 被领导者用来发送事务的端口被修改。
在这种情况下我执行一个领导者传递,老的领导者提名一个新的领导者。导致的不可用通常比当一个领导者崩溃进的时间少因为检测领导者失效不是必须的,并且选举一个新的领导者通常可以被避免在传递的过程中(参考 paper中的Figures6 和8)
当服务端的客户端端口被修改,它不丢弃正在存在的客户端连接。到这个服务端的新连接将不得不使用新的客户端商品。
过程保证:取决于reconfig操作的调用,老配置的法定人数要求是可用的并且对ZK的连接可以取得进展。一旦reconfig被调用,老的和新的配置的法定人数必须是可用的。最后的事务发生一旦(a)新的配置被启用。并且(b)在新的配置被启用之前所有操作被提交。一旦a和b发生,只有新配置的法定人数是需要的。然而请注意a和b对客户端来说都是不可见的。特别地,当一个重新配置操作提交,它只意为一个激活消息被领导者发送出去。它不绝对意为它新配置的法定人数获取这个消息(为了激活它是需要的)或者b已经发生。如果你想确保a和b都已经发生(例如,为了知道关闭被删除的老的服务端是安全的),你可以简单地调用一个更新(set-data,或者其它法定人数操作,但是不能是sync)并且等待它提交。另一种方法来实现这是引进另一个轮的重新配置协议(为了简单和兼容Zab,我们决定避免这种方式)。
增量模式
增量模式允许增加和删除当前配置的服务端。多个改变被允许。例如:
> reconfig -remove 3 -add server.5=125.23.63.23:1234:1235;1236
增加和删除选项都有一个逗号分割的参数列表(没有空格):
> reconfig -remove 3,4 -add server.5=localhost:2111:2112;2113,6=localhost:2114:2115:observer;2116
服务端声明的格式和在Specifying the client port部分描述的一模一样,并且包含客户端端口。注意这里代替"server.5",你可以仅仅用"5="。在上面的例子中,如果server 5已经存在系统中,但是有不同的端口或者不是一个观察者,它被更新并且一旦配置提交变为一个观察者并且使用新端口启动。这是一个简单的方式把参与者变为观察者和返过来。或者改变他们端口中的任何一个,而不用重启服务端。
ZK支持两种法定人数系统 - 简单的多数系统(领导者提交操作在收到多数投票的确认信息后)和一个更复杂的分层系统,不同的服务端的投票有不同的权重并且服务端被分为投票组。当前,增量重新配置只被允许如果最后提议的配置被使用多数法定人数系统的领导者知道(否则抛出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,这个API接受逗号分隔的字符串而不是List<String>。参考src/java/main/org/apache/zookeeper/ZooKeeper.java。
非增量模式
第种重新配置的模式是非增量,客户端提供一个完整的新的动态系统配置规范。新的配置可以从一个文件中给出或从一个文件中读取:
> reconfig -file newconfig.cfg ///newconfig.cfg 是动态配置文件 参考Dynamic configuration file
> 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
新配置可能使用一个不同的法定人数系统。例如,你可以指定一个分层法定人数系统即使当前的集群使用多数法定人数系统。
非增量模式 - 使用Java API 的例子:
ArrayList<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,这个API接受逗号分隔的包含List<String>的新成员的字符串。参考src/java/main/org/apache/zookeeper/ZooKeeper.java。
条件reconfig
有时候(特别是非增量模式)一个新提出的配置取决于客户端depends on what the client "believes" to be the current configuration,并且应该只应用这个配置。特别地,reconfig成功只有领导者的最后的配置文件有指定的版本号。
> reconfig -file <filename> -v <version>
在前面列出的Java例子,除了-1,你可以指定一个配置版本来给这个重新配置加条件。
错误条件除了普通的ZK错误条件,重新配置可以因为下面的原因而失败:
- 另一个reconfig现在正在进行(ReconfigInProgress)
- 提出的改变将使集群小于2个参与者,在单机模式启用的情况下,或者如果单机模式被禁用那么它是合法的保留1或多个参与者(BadArgumentsException)
- 新配置没有法定人数被连接和更新和领导者当重新配置开始处理(NewConfigNoQuorum)
- -v x被指定,但是最新的配置版本号y不是x(BadVersionException)
- 一个增量的重新配置被请求但是领导者最新的配置使用一个不多数系统不同的法定人数系统(BadArgumentsException)
- 语法错误(BadArgumentsException)
- I/O异常当从一个文件中读取配置(BadArgumentsException)
他们的大部分被说明在测试用例ReconfigFailureCases.java中。
附言
Liveness:为了更好的理解增量和非增量之间的不同,假设客户端C1增加服务端D到系统中,同时一个不同的客户端C2增加服务端E。对于非增量模式,每一个客户端将首先调用config来找出当前的配置,然后在本地创建一个新的服务端列表通过增加他们自己建议的服务端。新配置然后可以被提交使用有非增量的reconfig命令。在两个重新配置都完成之后,只有E或D中的一个将被加入(不是两个),取决于那一个客户端的请第二个到达领导者,覆盖了先前的配置。其它的客户端可以重复这个过程只到它的改变生效。这个方法保证全系统的进展(也就是说,客户端中的一样),但是不能确保每一个客户端成功。为了对C2更多控制可以请求只执行重新配置在当前配置没还有改变的情况下。正如在Conditional reconfig中讲解的那样。用这种方式它可以避免盲目地覆盖C1的配置文件,如果C1的配置先到达领导者。
对于增加重新配置,两个改变将生效因为他们简单地被应用被领导者一个接一个应用到当前的配置,不管它是什么(假如第二个reconfig请求到达领导者在第一个reconfig请求发送一个提交消息 -- 现在领导者将将拒绝提议一个重新配置如果另一个已经正在悬而未决)。因为这两个客户都保证取得进展,这个方法保证更强的liveness。在实践中,多个并发的重新配置可能是罕见的。非增量的重新配置是当前唯一的方式来动态地改变多数系统。增量的配置是当前唯一被允许的对于多数法定人数系统。
改变一个观察者为一个追随者:显然地,改变一个参与投票的服务端为一个观察者可能失败如果错误(2)发生。也就是说,如果小于参与者的最小被允许的数量。然而,转变一个观察者为一个参与者有时可能会失败因为一个更微妙的原因:假如,例如当前的配置是(A,B,C,D),这里A是领导者,B和C是追随者并且D是一个一观察者。另外,假如B已经崩溃。如果一个重新配置被提交,这个配置使D变为一个追随者,它将会失败因为错误(3)因为在这个配置中,新配置中的多数投票者(任何3个投票者),必须被连接和更新和领导者。一个观察者无法确认在重新配置过程中发送的历史前缀,因此,它不指望这3个所需的服务器并且重新配置将会被取消。如果发生这种情况,一个客户端可以达到相同的任务通过两个reconfig命令:首先调用reconfig命令来从配置中删除D然后调用第二个命令来把它增加回来作为参考者(追随者)。在中间状态D是一个不投票的追随者并且可以可以响应在第二个reconfig命令执行其中的状态转换。
平衡客户端连接
当一个ZK集群被启动,如果每一个客户端使用相同的连接字符串(服务端列表),客户端将随机地选择一个服务端来连接,使每个服务器的客户端连接的预期数量相同。我们实现一个方法来保证这个特性当通过重新配置服务端列表改变。参考paper中的4和5.1部分。
为了使这个方法起作用,所有客户端必须订阅配置改变(通过在/zookeeper/config上设置一个监视器,直接地或通过getConfigAPI命令)。当监视器被触发,客户端应该读取新的配置通过调用sync和getConfig,并且如果配置确实是新的,调用updateServerList API命令。为了避免大量的客户端同时迁移,最好每一个客户端睡觉一个随机的时间在调用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; } } }
插播个广告
老丈人家的粉皮儿,农产品,没有乱七八糟的添加剂,欢迎惠顾