Zookeeper FAQ
1. 如何处理CONNECTION_LOSS?
在Zookeeper中,服务器和客户端之间维持一个长连接,CONNECTION_LOSS意味着这个连接断开了。客户端API返回CONNECTION_LOSS时,不能确定请求是成功还是失败(视连接断开发生在请求发送之前还是之后,遗憾的是无法区分这两种情况),应用需要自己的逻辑来确认操作是否成功。
连接断开可能是由于网络抖动,或者是连接的那台服务器死机等原因。出现这种情况后,客户端的LIB内部会自动重连。如果在session超时内连上一个服务器,连接状态变为CONNECTED,如果连上时已经超时了,服务端将告知客户端session已经超时了,对应SESSION_TIMEOUT事件。
通常情况下并不需要处理CONNECTION_LOSS事件,需要特别注意的是收到该事件时,调用zookeeper_init或者new Zookeeper重新初始化一个连接是没有意义的。
2. 如何处理SESSION_EXPIRE?
Session超时由服务端管理。客户端在初始化连接时会指定该Session的超时时间(下面用T表示),相当于服务端给客户端的一个Lease。客户端的每次请求到达服务端后,服务端会刷新该Session的Lease(续约),如果客户端在T/3时间内都无请求,则内部会发送一个PING来续约。正常情况下,客户端与服务端的Session将一直保持,当出现网络闪断或者连上的那台服务器断电等情况,只要客户端在Lease时间内重连上Cluster中某台机器,Session将仍然有效。
如果服务端在时间T内未收到一个Session的任何信息时,Zookeeper认为该Session已经超时了,之后由集群的Leader来下发Session超时的命令,该Session的相关信息(Session本身、其创建的临时节点及添加的Watch)将被Zookeeper清除。在Session超时这一过程中,客户端通常会先看到连接断开,只有与Zookeeper重新建立连接后,客户端才能收到Session超时事件(也就是说SESSION_EXPIRE事件一定是服务端告诉客户端的,客户端本身没有相关的计时逻辑)。
收到SESSION_EXPIRE后,客户端要做的事情视应用的场景而定。一般情况,需要重新与Zookeeper建立建立(注意一定要关闭之前的连接),重新建立临时节点,重新添加的Watch。
3. 客户端重连的机制是什么?
客户端初始化时需要指定一个Server List或者一个域名(内部会查DNS解析成一个List),每个客户端初始化时会用Random Shuffle算法随机打乱该List,并保存打乱后的结果,之后需要重连时,会采用Round Robin的方式逐个尝试。在公司内部1.0.8及以上版中,如果采用域名初始化,在合适的时机,客户端的LIB会重新解析域名,以应对集群更换机器的情况(避免重启客户端进程)。
重连发生在两种情况,其一是socket连接发生异常,其二是socket连接正常,但是2T/3时间内未收到服务端的响应。
4. 客户端修改某个节点的数据时,其他客户端能立即读取到最新数据吗?
Zookeeper采用Fast Paxos保证一致性,写一个节点时在多数写返回成功时就给客户端返回成功,比如N=5时,只需3台返回写成功即可。因此,不保证当客户端A写返回成功时,客户端B都能立即读取到该数据。如果B需要获取当前时间该节点的最新数据,可以在获取数据前,显示的执行sync(zoo_async),sync为异步的原因是不想把读操作的延时翻倍。
Zookeeper本身会保证全局有序,即所有的客户端将看到一致的修改顺序。同时在同一Session内,保证单调一致(不会因为重连到其他机器而读取到旧数据)。
5. Watch机制是什么,为什么提供一次性而不提供永久的Watch机制?
Watch类似于观察者模式,事件发生时告知观测者。比如对节点/a设置Data Watch,当/a的值被(自己或其他客户端)改变后,将收到通知。
不支持永久Watch的原因是Zookeeper无法保证性能。永久Watch意味着数据的每一次修改都要通知到所有的观察者,这是无法接受的。在大多数情况下,客户端并不需要追踪节点的每一次变化。
6. Watch机制为什么只通知改变,而不同时发送改变之后的数据?
在Zookeeper最初始的版本中,事件通知带上了具体的变更内容,比如/a的值变成了v2,但这很难被正确使用。Watch的目的是用来追踪节点最近的变化,直接通知内容会有如下问题:比如Client A首先获取/a的内容为v1,并设置了Watch,之后Client B将/a的值依次设置为v2,v3,这样Client A将只能获取到/a的内容变成了v2,无法得知其已经变为v3。
因此,Zookeeper只通知改变,客户端收到通知后,可以重新查询并添加Watch,这样可以保证追踪到该节点的最新数据(get前的更改被get获取,get后的更改将收到通知)。
7. 能否捕获节点每次的数据变化吗?
收到通知与重新添加Watch不是原子的,如果在之间节点的内容被修改多次,这中间的修改将不被感知。
如果一定需要追踪节点每次的变化,需要仔细的设计观察者与写者之间的协议,可参考ZKC一致性cache的做法。
8. 使用watch的几个注意事项
a. Watch的通知是一次性的;
b. 客户端在CONNECTIONLOSS之后有重新连上Zookeeper(未发生SESSION超时),那么这个连接注册的Watch依然在。内部会重新发送添加过的Watch,并且如果Watch的节点在CONNECTIONLOSS阶段发生了改变,重连上之后会收到对应的事件(不会因为与Zookeeper的连接断开而丢失事件);
c. 需要注意的一种情况是:对一个不存在的节点添加exists的Watch,之后该Session与Zookeeper断开连接,在重新连上之前,这个节点被创建了,又被删除了,该Session重新连上之后,将不会收到通知。
d. 变化指节点的版本号,即使已相同的值set该节点,将触发CHANGED事件;
e. 对同一个节点多次注册Watch,只会收到一次事件,如执行zoo_wexists(“/a”)两次,当/a被创建或者被删除时,该Session只会收到一次通知;
f. 切忌在回调函数中抛出异常
9. 什么时候需要使用临时节点?
临时节点的生命期与Session绑定,当Session超时后,临时节点将被Zookeeper删除。通常在需要某种宕机检测的场景中使用临时节点,比如分布式锁,采用临时节点避免因宕机而死锁。
10. 在测试异常情景时,是否有简单的方法让一个Session超时?
在客户端新建Session成功时,会收到服务端发送过来的Session ID和对应的Passwd。测试应用的异常处理时,可以拿该Session的ID及Passwd新建一个连接,并执行Close关闭Session,之后原来的Session将收到SESSION_EXPIRE事件。
11. Zookeeper怎样升级,影响是什么?
Zookeeper升级采用逐台重启,并且先Follower最后Leader的方式升级。重启Follower时,其上的连接经过CONNECTION_LOSS后会重新连上Zookeeper集群,到最后重启Leader时,集群将重新选主,期间服务不可用,最坏情况的选主时间按1MIN/GB来计算。
即使不升级,Zookeeper集群也可能发生选主。因此,应用需要处理选主时集群不可服务的异常情况,最直接的做法就是等待并重试。
12. Zookeeper集群如何扩容?
目前Zookeeper不支持动态扩容,扩容需要重启服务。
需要注意的是扩容能提高集群的读能力,但是会降低集群的写能力。通常从稳定性的角度考虑集群大小,线上通常采用5台机器搭建集群,最多能容仍2台机器异常。
13. Zookeeper集群宕机对已存在session的影响?
假设客户端以30s新建Session成功后,OP把整个集群停掉1分钟,之后重启。这种场景下,客户端是能够重新与集群建立连接的。在集群停机的这段时间内,计时器是不会增加的,集群重启后,会把之前存在的Session全部刷新,并重启计时器。因此,只要客户端在30s内重新连上集群,该Session不会超时。
当集群重新选主时,情形一样。
14. Zookeeper对节点大小的限制是什么?
默认节点大小不能超过1M,当Client尝试写或者创建超过限制的节点时,该Client会被Server直接关闭,API返回CONNECTION_LOSS。