zookeeper 相信大家都不陌生,很多分布式中间件都利用 zk 来提供分布式一致性协调的特性。
dubbo 官方推荐使用 zk 作为注册中心,zk 也是 hadoop 和 Hbase 的重要组件。
其他知名的开源中间件中也都出现了 zk 的身影。
有很多童鞋认识 zk 很久了,知道其基本理念,知道如何使用。
但当面试时问到集群 zk 之间的选举和数据同步机制时,就陷入了盲区。
其实很多的分布式中间件的选举和同步,都和 zk 有异曲同工之妙。
这篇文章我就来重点聊下关于 zk 集群之间的选举和同步机制。
点击下方卡片,关注「码哥字节」
首先我们来看下半数运行机制:
集群至少需要三台服务器,且官方文档强烈建议使用奇数个服务器,因为 zookeeper 是通过判断大多数节点的的存活来判断整个服务集群是否可用的。
比如 3 个节点,它的一半是 1.5,向上取整就是 2。
挂掉了 2 个就是表示整个集群挂掉。
而用偶数 4 个的话,挂掉 2 个也表示不是大部分存活,因此也会挂掉。
所以用 4 台服务器的话 ,从使用资源上来说,并不划算。
配置语法:
server.<节点ID>=<IP>:<数据同步端口>:<选举端口>
假设现在有 3 个 zookeeper 节点,我们要为其编写 config 配置,也要编写 3 份,分别放在不同的服务器上,其配置如下:
initLimit=10
syncLimit=5
dataDir=/data/zk_data
clientPort=2181
# 集群配置
server.1=192.168.1.1:2888:3888
server.2=192.168.1.2:2888:3888
server.3=192.168.1.3:2888:3888
其中 dataDir 参数指定的是一个目录,用来存放 zk 的数据,里面有个文件 myid,3 台机器上 myid 文件里面分别存放 1,2,3,对应各自节点 ID。
配置好 3 个配置文件后,分别启动,这样我们一个 3 个节点的集群 zookeeper 就搭建好了。
./bin/zkServer.sh start conf/zoo.cfg
zookeeper 集群中公共有三种角色,分别是leader
,follower
,observer
。
角色 | 描述 |
---|---|
leader | 主节点,又名领导者。用于写入数据,通过选举产生,如果宕机将会选举新的主节点。 |
follower | 子节点,又名追随者,用于实现数据的读取,同时他也是主节点的备选节点,并拥有投票权。 |
observer | 次级子节点,又名观察者。 |
用于读取数据,与 follower 区别在于没有投票权,不能被选为主节点。
并且在计算集群可用状态时不会将 observer 计算入内。|
关于 observer 的配置:
只要在集群配置中加上 observer 后缀即可,示例如下:
server.3=127.0.0.1:2883:3883:observer
其中 leader 只有一个,剩下的都是 follower 和 observer,但是我们一般生产上不会配置 observer,因为 observer 并没有选举权,可以理解为 observer 是一个临时工,不是正式员工,没法获得晋升。
除此之外,它和 follower 的功能是一样的。
什么时候需要用到 observer 呢,因为 zk 一般读的请求会大于写。当整个集群压力过大时,我们可以增加几个临时工 observer 来获得性能的提升。
在不需要的时候的时候,可以随时撤掉 observer。
zk 进行连接时,一般我们都会把 zk 所有的节点都配置上去,用逗号分隔。
其实连接集群中的任意一个或者多个都是可以的。
只是如果只连一个节点,当这个节点宕机的时候,我们就断开了连接。
所以还是推荐配置多个节点进行连接。
我们可以利用以下命令来查看 zk 集群中的角色
./bin/zkServer.sh status conf/zoo.cfg
我在自己机器上搭建了 3 个节点的伪集群(共用一台机器),配置文件分别命名为 zoo1.cfg,zoo2.cfg,zoo3.cfg。使用以上命令查看的结果为:
可以看到,其中节点 2 为 leader,其他的为 follower。但是如果你按照 zoo1.cfg,zoo2.cfg,zoo3.cfg 的顺序启动,无论你启动多少遍,节点 2 总是 leader,而这时如果把节点 2 关掉,进行查看角色,发现节点 3 成了 leader。
以上这些现象都和 zookeeper 的选举机制有关
我们就拿 3 个节点的 zk 作一个简单选举的说明
zk 会进行多轮的投票,直到某一个节点的票数大于或等于半数以上,在 3 个节点中,总共会进行 2 轮的投票:
有的童鞋会问,zk3 呢,因为 zk2 已经当选了,投票终止了。
所以 zk2 也不会投票给 zk3 了。
当然这是一个比较简单版的选举,其实真正的选举还要比较 zxid,这个后面会讲到。
zk 选举什么时候会被触发呢?
一是启动时会被触发,二是 leader 宕机时会被触发。
上面的例子中,如果节点 2 宕机,根据规则,那获得 leader 的就应该是 zk3 了。
zookeeper 的数据同步是为了保证每个节点的数据一致性,大致分为 2 个流程:
正常客户端数据提交流程
客户端写入数据提交流程大致为:leader 接受到客户端的写请求,然后同步给各个子节点:
但是有童鞋就产生疑惑了,客户端一般连接的是所有节点,客户端并不知道哪个是 leader 呀。
的确,客户端会和所有的节点建立链接,并且发起写入请求是挨个遍历节点进行的,比如第一次是节点 1,第二次是节点 2。
以此类推。
如果客户端正好链接的节点的角色是 leader,那就按照上面的流程走。
那如果链接的节点不是 leader,是 follower 呢,则有以下流程:
如果 Client 选择链接的节点是 Follower 的话,这个 Follower 会把请求转给当前 Leader,然后 Leader 会走蓝色的线把请求广播给所有的 Follower,每个节点同步完数据后会走绿色的线告诉 Leader 数据已经同步完成(但是还未提交)。
当 Leader 收到半数以上的节点 ACK 确认消息后,那么 Leader 就认为这个数据可以提交了,会广播给所有的 Follower 节点,所有的节点就可以提交数据。
整个同步工作就结束了。
那我们再来说说节点宕机后的数据同步流程
当 zookeeper 集群中的 Leader 宕机后,会触发新的选举,选举期间,整个集群是没法对外提供服务的。
直到选出新的 Leader 之后,才能重新提供服务。
我们重新回到 3 个节点的例子,zk1,zk2,zk3,其中 z2 为 Leader,z1,z3 为 Follower,假设 zk2 宕机后,触发了重新选举,按照选举规则,z3 当选 Leader。
这时整个集群只整下 z1 和 z3,如果这时整个集群又创建了一个节点数据,接着 z2 重启。这时 z2 的数据肯定比 z1 和 z3 要旧,那这时该如何同步数据呢。
zookeeper 是通过 ZXID 事务 ID 来确认的,ZXID 是一个长度为 64 位的数字,其中低 32 位是按照数字来递增,任何数据的变更都会导致低 32 位数字简单加 1。
高 32 位是 leader 周期编号,每当选举出一个新的 Leader 时,新的 Leader 就从本地事务日志中取出 ZXID,然后解析出高 32 位的周期编号,进行加 1,再将低 32 位的全部设置为 0。
这样就保证了每次选举新的 Leader 后,保证了 ZXID 的唯一性而且是保证递增的。
查看某个数据节点的 ZXID 的命令为:
先进入zk client命令行
./bin/zkCli.sh -server 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183
stat加上数据节点名称
stat /test
执行结果为:
可以看到,有 3 个 ZXID,这 3 个 ZXID 各自代表:
cZxid:该节点创建时的事务 ID
mZxid:该节点最近一次更新时的事务 ID
pZxid:该节点的子节点的最新一次创建/更新/删除的事务 ID
查看节点最新 ZXID 的命令为:
echo stat|nc 127.0.0.1 2181
这个命令需提前在cfg文件后追加:4lw.commands.whitelist=*,然后重启
这里的 ZXID 就是当前节点最后一次事务的 ID。
如果整个集群数据为一致的,那么所有节点的 ZXID 应该一样。所以 zookeeper 就通过这个有序的 ZXID 来确保各个节点之间的数据的一致性,带着之前的问题,如果 Leader 宕机之后,再重启后,会去和目前的 Leader 去比较最新的 ZXID,如果节点的 ZXID 比最新 Leader 里的 ZXID 要小,那么就会去同步数据。
我们带着 ZXID 的概念再来看 ZK 中的选举机制。
假设还是有一个 3 个节点的集群,zk2 为 Leader,这时候如果 zk2 挂了。zk3 当选 Leader,zk1 为 Follower。这时候如果更新集群中的一个数据。
然后把 zk1 和 zk3 都关闭。然后挨个再重启 zk1,zk2,zk3。这时候启动后,zk2 还能当选为 Leader 吗?
其实这个问题,换句话说就是:在挨个启动 zk 节点的时候,zk1 和 zk3 的数据为最新,而 zk2 的数据不是最新的,按照之前的选举规则的话,zk2 是否能顺利当选 Leader?
答案为否,最后当选的为 zk1。
这是为什么呢。
因为 zk2 的最新 ZXID 已经不是最新了,zk 的选举过程会优先考虑 ZXID 大的节点。这时 ZXID 最大的有 zk1 和 zk3,选举只会在这 2 个节点中产生,根据之前说的选举规则。在第一轮投票的时候,zk1 只要获得 1 票,就能达到半数了,就能顺利当选为 Leader 了。