ZooKeeper ( 简称 zk ) 是一个开源的分布式协调服务,其经常使用作分布式服务的注册中心html
本文要讲的是 Node.js 的 zk sdk -- node-zookeeper-clientnode
在这以前,先了解一下 zk 相关的基本知识git
ZooKeeper Transaction Id
,用于保证 zk 分布式一致性[总长度, header, payload]
先从使用示例开始github
/** * xiedacon created at 2019-06-14 10:07:46 * * Copyright (c) 2019 Souche.com, all rights reserved. */ 'use strict'; const ZK = require('node-zookeeper-client'); const util = require('util'); const client = ZK.createClient('127.0.0.1:2181'); client.getChildren = util.promisify(client.getChildren); (async () => { console.log('Result:', await client.getChildren('/xxx')); })().catch((err) => { console.log(err); }); client.connect();
上面的代码总共分红 3 步:apache
ZK.createClient('127.0.0.1:2181')
,建立 zk 客户端实例client.connect()
,与 zk 服务端创建链接client.getChildren('/xxx')
,获取 /xxx 节点下的全部子节点zk 客户端可大体分为三块:服务器
index.js
lib/connectionManager.js
lib/watcherManager.js
client.connect()
,connect 方法内调用 connectionManager.connect()
,将 connectionManager 状态置为 CONNECTING findNextServer()
获取一个 zk 服务器地址connect
、data
、drain
、close
、error
事件若是 tcp 链接创建成功,则触发 connect 事件,并执行 onSocketConnected
方法session
若是 tcp 链接创建失败,则分别触发 error
、close
事件,并执行 onSocketError
、onSocketClosed
方法app
若是 zk 服务器容许 client 接入socket
onSocketData
方法,此时 connectionManager 状态为 CONNECTING connectResponse.timeOut <= 0
意味着当前 client 的 session 过时:将 connectionManager 状态置为 SESSION_EXPIRED,稍后 zk 服务器将自动关闭 socket,即触发 5drain
,发送阻塞 request,使数据流动起来若是 zk 服务器拒绝 client 接入async
PS:其实并无触发 socket drain
的代码,而是调用的 onPacketQueueReadable
方法,但它们的效果同样
client.getChildren('/xxx')
connectionManager.queue
,将 request 放入 packetQueue 中,同时触发 socket drain
onPacketQueueReadable
方法中,对于非 Auth、Ping 等的外部 request,设置 xiddata
事件,此时 connectionManager 状态为 CONECTED 若是 xid 为内部 xid 则进行内部处理,不然对比 pendingQueue 队头 request 的 xid 与当前 xid 是否一致
client.getChildren
的回调方法PS:其实并无触发 socket drain
的代码,而是触发 packetQueue 的 readable
事件,但它们的效果同样
各方法与状态机的关系以下:
node-zookeeper-client 自己提供断线重连的能力
对于长期断线 ( 30s 以上 ),链接从新创建后,当前客户端的 session 已通过期,connectionManager 将进入 SESSION_EXPIRED 状态,此时调用 queue
方法会抛出 SESSION_EXPIRED
错误
这一问题在 zookeeper 文档 中也有提到:
It means that the client was partitioned off from the ZooKeeper service for more the the session timeout and ZooKeeper decided that the client died.
当客户端收到 SESSION_EXPIRED
错误时,那意味着当前客户端已经从 ZooKeeper 服务中断开,即 sesssion 过时,ZooKeeper 断定当前客户端应当关闭。
If the client is only reading state from ZooKeeper, recovery means just reconnecting. In more complex applications, recovery means recreating ephemeral nodes, vying for leadership roles, and reconstructing published state.
若是客户端以只读方式链接的 ZooKeeper,那么只须要从新链接就行了。但在更复杂的状况下,重连就意味着从新建立临时节点,参与 leader 竞争,重建已发布的状态。
所以 node-zookeeper-client 的作法也合情合理,当 session 过时时直接禁止发送,由调用程序决定是否重连,重连后须要作什么操做
但在大多数状况下,node-zookeeper-client 只会做为一个简单的客户端链接 zookeeper 服务,不会参与 zk 选举,所以,它最多须要作的事情也只是恢复临时节点
目前的方案是 session 过时自动重连,重连成功后将会进入 CONNECTED 状态并触发 connect
事件,在事件内从新注册临时节点
在 zk 集群中,不一样服务节点之间经过 ZAB 协议,保证分布式一致性。zxid ( ZooKeeper Transaction Id ) 就是用于保证分布式一致性的标识符
zk 直接挂掉或者手动重启都将会致使集群 zxid 重置。当客户端重连时,因为客户端自己存储的 xzid 大于 zk 集群 xzid,此时,zk 集群将拒绝客户端链接。外部表现为 connectionManager 疯狂执行 connect 操做,全部调用阻塞,zk 日志输出 Refusing session request for client /192.168.0.1:33400 as it has seen zxid 0x300000012 our last zxid is 0x0 client must try another server
虽然 zk 集群判断出 zxid 有误,但直接断开链接的处理方式确实是有待考量。由于经过这种方式处理,客户端彻底不知道发生了什么,只能经过重连来解决,然而重连又链接不上,这就造成了一个死循环
因为客户端无需在乎 zk 集群版本,所以,能够在每次链接时,直接重置 zxid 便可