zookeeper client 设计开发手册
本手册是根据zookeeper服务特性及官方SDK特点,编写的一套开发手册。针对碰到的大量陷阱,提出注意点,并对编写生产可用的sdk提供参考建议。
本设计(语言基于C++)是基于官方C版Client库而写,对于golang、java及其它语言均有借鉴意见。 由于竞业协议,代码不便公开,请谅解。
实现了服务注册,服务发现,负载均衡,分布式锁 等 功能,其中负载均衡包含:主从节点(主节点优先),最优节点(负载较优),连接池和地址池(轮训)。
在开发sdk的过程中,浏览了很多github上的开源库(主要是C和C++),均存在bug,无法生产使用。后来参考了这个库:https://github.com/owenliang/zkclient.git 因为上面这个库,文档详细,但是bug依然很多,并没有解决zookeepeer的一些缺陷。
名次定义:
会话恢复:指在旧会话超时,重建新会话时后,恢复必要的 临时节点以及订阅。
调用线程:指使用库zookeeperClient的上层程序,即开发者程序。
完成回调:异步接口完成时 调用的回调函数。
watch回调:数据及子节点变化时 收到通知后调用的回调函数。
持续订阅:订阅一次,往后持续推送变化。
zk: zookeeper服务器。
tmpnode: 临时节点。
业务路径:所有服务归类,按照业务划分,例如/bigSys/smallSys/serviceType/servieName。bigSys是大系统划分,smallSys是小系统划分,serviceType是具体的服务类型的统称,servieName是具体服务(必须唯一)
一、zookeeper介绍
首先,zookeeper以会话作为上下文管理的手段。顺序保证,临时节点管理,watch监控都以会话作依据。
二、根据zookeeper和官方库的特点,提出如下注意点
clientid只有在收到事件ZOO_CONNECTED_STATE才能获取成功,并且只在会话有效期内可用。
一旦会话失效的,要想利用clientid重建会话,不可行。
zk的临时节点以及订阅请求 归属于会话,一旦会话失效,将无效。
zk只能完成单次订阅,需要再次订阅必须再次请求。
zk的会话超时检测发生在服务器端。客户端只有在重连上服务器时,才被告知会话超时,并强制关闭连接。
会话失效后zhandle必须调用zookeeper_close释放资源,防止资源泄漏。并且使用已关闭的zhandle会报错。
zhandle存在并发问题。如果想在回调线程中处理重连问题,调用线程和回调线程需要加锁(会话超时时会触发回调函数,依次调用是zookeeper_close,zookeeper_init,并重新得到新的zhandle)。
由于zookeeper_init是异步的,所以返回ZOK并不代表连接成功。
程序启动时首次调用zookeeper_init时,应该用带超时的信号量等待异步事件ZOO_CONNECTED_STATE。
会话超时后启动回调函数重连,调用zookeeper_init后无法立即“恢复会话”,必须在收到事件ZOO_CONNECTED_STATE才能恢复会话(包括临时节点及订阅)。
zk的所有回调在一个线程内调度,不允许阻塞等待。
zookeeper_init返回zhandle,但是可能在返回前触发回调函数,所以回调函数中必须用参数zhandle,而不是zookeeper_init返回的全局zhandle。
同步接口:调用线程及watch回调线程 的时序不定,使用者必须自己同步。
异步接口:调用线程,完成回调,watch回调(其中 完成回调和watch回调在一个线程内,并且完成回调总是早于watch回调),完成回调和watch回调不存在时序问题。
要是调用接口返回失败,完成回调和watch回调 都不会被触发。
因此,在设计持续订阅接口时,可以先调用同步接口(加锁),然后之后在watch回调中调用异步接口(无需加锁)。
两次会话之间(老会话创建,新会话还未创建),数据发生变化(客户端在订阅了数据变化的操作后会话超时,此时若节点数据变化了,在客户端重现建立新会话并且重新订阅watch后,客户端无法感知该数据变化)。
zk不支持持续订阅。
要实现支持持续订阅,必须考虑再次订阅时订阅失败的问题(客户端资源不足等),以及由于服务器资源而主动取消订阅的问题(此时收到NoWatch事件)。这个可以由单独的线程定期处理。
对同一path订阅多次(exist和get等同),只推送一次通知。这个可以客户端自己实现。
zk集群节点越多,同步时间越长,写性能越差。zk连接数越多,心跳检测即会话检测越多,性能越差。observer可以解决后者。
zk订阅子节点变化,在收到子节点变化,获取子节点列表,获取子节点数据之间,存在不一致问题,尤其是获取子节点