ZOOKEEPER WATCH事件丢失分析
Watch实现
服务端
zookeeper server为每个本地client连接(不是session)维持一份watch表(map表),用于接收该client的watch请求,但不会把watch请求同步到其它节点(官方解释这样轻量化设计,好出多多…)
每次更新某个数据时,就会触发watch通知,开始遍历本地client是否有对应watch请求事件,如果有,再判断该连接是否连接正常,连接正常则立马发通知到client,否则跳过当前client,遍历下一个。与ETCD的机制不同,zks并不会存储该事件为历史事件(对比etcd可以通过index获取错过的事件,这点还是ETCD做的比较好)。
Zks会启动独立线程,定时执行删除已断开连接的watcher,避免失活的watcher驻留消耗资源。
客户端
根据应用请求watch类型,向当前连接的服务器申请watch请求,异步等待请求回复。若收到对应的watch回复,则执行对应的回掉后,删除该watch请求。
当连接断开时,则所有client的watcher会收到session断开事件执行回调,但其watch请求并没有并移除。
当连接恢复时,无论是连接本地服务还是切换集群其它服务节点,本地client 所有 watcher请求会收到session恢复事件,回调再次被执行,请求不会被删除。接着,client的watch请求,会重新注册到连接服务器上,继续等待watch回调。
官方设计文档
https://zookeeper.apache.org/doc/r3.5.9/zookeeperProgrammers.html#ch_zkWatches
特性要点
- 仅支持单次触发。每次正常成功触发后(网络断开重连会触发额外增加断开和恢复两次回调),需要重新注册。
- Watch本地保存。Watch请求不会同步到集群其它服务器。
- 连接断开时, 会执行一次watch回调。
- client中断期间,其watch事件会被原先的服务器直接丢弃,不会历史缓存。
- 连接恢复时,会执行一次watch回调。
- Client重连成功,会重新注册本地watch请求。
事件丢失可能场景
1. watch触发到完成再次注册期间,服务端对应节点发生变更的事件会丢失。
由于单次触发的特性,收到事件执行回调到再次注册watch时间差没法避免,通常的做法是先注册,再获取变更的数据,可以确保获取最新的变更不丢失,没法实现保证过程事件不丢失。(ETCD的index属性可以基本做到过程事件不丢失)
2. 断网期间,服务端所发生的更新事件会全部丢失。
根据zks的watch设计机制,其watch是本地的,实时的。当事件发生时,服务端watch事件会实时发送,且不缓存。链路正常就发送,不正常就跳过,简单粗暴,效率高。
开发建议
- 所有watch回调,若需要一直观察该节点变化,则先注册再获取数据,可以确保不丢失最新数据,能满足基本业务场景需求。
- Zk不适合用于连续性状态同步的业务场景(如严格要求从1,2,3顺序,不许间隔。。。)
- 断网恢复时需要做一次主动同步。避免follower节点断网期间,其它节点变更事件永久丢失,导致同步失败。