zk watch丢事件[zookeeper和etcd比较]

zookeeper简介

zookeeper的内部是一个key/value存储引擎,key是以树状的形式构成了一个多级的层次结构,每一个节点既可以存储数据,又可以作为一个目录存放下一级子节点。
image

zookeeper提供了创建/修改/删除节点的api,如果父节点没有创建,字节点会创建失败。如果父节点还有子节点,父节点不可以被删除。

zookeeper满足了CAP定理的分区容忍性P和强一致性C,牺牲了高性能A【可用性蕴含性能】。zookeeper的存储能力是有限的,当节点层次太深/子节点太多/节点数据太大,都会影响数据库的稳定性。所以zookeeper不是一个用来做高并发高性能的数据库,zookeeper一般只用来存储配置信息。
zookeeper的读性能随着节点数量的提升能不断增加,但是写性能会随着节点数量的增加而降低,所以节点的数量不宜太多,一般配置成3个或者5个就可以了。
image

图中可以看出当服务器节点增多时,复杂度会随之提升。因为每个节点和其它节点之间要进行p2p的连接。3个节点可以容忍挂掉1个节点,5个节点可以容忍挂掉2个节点。
客户端连接zookeeper时会选择任意一个节点保持长链接,后续通信都是通过这个节点进行读写的。如果该节点挂了,客户端会尝试去连接其它节点。
服务器会为每个客户端连接维持一个会话对象,会话的ID会保存在客户端。会话对象也是分布式的,意味着当一个节点挂掉了,客户端使用原有的会话ID去连接其它节点,服务器维持的会话对象还继续存在,并不需要重新创建一个新的会话。
如果客户端主动发送会话关闭消息,服务器的会话对象会立即删除。如果客户端不小心奔溃了,没有发送关闭消息,服务器的会话对象还会继续存在一段时间。这个时间是会话的过期时间,在创建会话的时候客户端会提供这个参数,一般是10到30秒。
也许你会问连接断开了,服务器是可以感知到的,为什么需要客户端主动发送关闭消息呢?
因为服务器要考虑网络抖动的情况,连接可能只是临时断开了。为了避免这种情况下反复创建和销毁复杂的会话对象以及创建会话后要进行的一系列事件初始化操作,服务器会尽量延长会话的生存时间。

zk Watcher 特性

当数据发生变化的时候, zookeeper 会产生一个 watcher 事件,并且会发送到客户端。但是客户端只会收到一次通知。如果后续这个节点再次发生变化,那么之前设置 watcher 的客户端不会再次收到消息。(Watcher 是一次性的操作,当然,可以通过循环监听去达到永久监听效果)。

  • 一次性:watcher 是一次性的,一旦触发就会被移除,再次使用时需要重新注册;
  • 客户端顺序回调:watcher 回调是顺序串行执行的,只有回调后客户端才能看到最新的数据状态,一个 watcher 回调逻辑不应太多。以免影响其他回调 watcher 执行;
  • 轻量级:WatchEvent 是最小的通信单位,结构上只包含通知状态、事件类型和节点路径,并不会告诉节点变化的前后具体内容;
  • 实效性:watcher 只有在当前 session 彻底失效时才会无效,若在 session 有效期内快速重连成功,则 watcher 依然存在,仍可接收到通知。

事件发生的太快来不及watch怎么办

  • Watch 是一次性的,触发一次后会自动移除。如果在收到 watch 事件与重新设置 watch 之间,数据发生了多次变化,那么中间的变化可能会丢失。
    解决办法: 在 watch 回调中,读取最新数据并立即重新设置 watch,以减少丢失事件的可能性。
  • 客户端处理延迟
    如果客户端处理事件过慢,可能来不及处理新的事件,尤其是在高并发场景中。
    解决办法: 优化 watch 回调函数的逻辑,避免阻塞事件处理,必要时使用队列来异步处理事件。
  • 通常我们使用watch功能是为了让程序阻塞等待某些事件的发生并进行相应的处理,然而现实世界中处理的速度有可能跟不上事件发生的速度。
    比如ZooKeeper的watch在捕捉到一个事件后channel就会关闭,需要我们再次去发送watch请求。在此期间发生的事件将丢失,下文引用自ZooKeeper官网文档原文:
Because watches are one time triggers and there is latency between getting the event and sending a new request to get a watch you cannot reliably see every change that happens to a node in ZooKeeper. Be prepared to handle the case where the znode changes multiple times between getting the event and setting the watch again. (You may not care, but at least realize it may happen.)

zookeeper与etcd比较

zookeeper

提供了三种watch

  • ChildrenW
    关注子节点的创建和删除,但不能watch子节点值的变化,也不能递归watch孙节点
  • GetW
    关注某个节点的值的更新,注意当前node必须已经存在。
  • ExistsW
    关注节点从无到有、从有到无

局限性
watch是一次性的,触发之后需要重新watch
所以,在watch被触发和重新设置之间发生的事件将被丢弃,无法被捕捉

etcd

也提供了三种,单点watch,prefix watch以及ranged watch,区别仅在于对key的处理不同:
单点watch仅对传入的单个key进行watch;
ranged watch可以对传入的key的范围进行watch,
prefix则可以对所有具有给定prefix的key进行watch。由于key是通过'/'符号表示层架关系,所以prefix watch可以实现递归监听所有子节点、孙节点等后代节点的变化。

watch是永久的,触发后不需要重新watch。

除此之外,还在API的请求和响应中添加了一个版本号,客户端可以在watch请求中指定版本号来获取自该版本号以来发生的所有变化

zk server端逻辑

重点关注triggerWatch的方法,可以发现watch被移除后,即往watch中存储的client信息进行通知发送,没有任何确认机制,不会由于发送失败,而回写watch。
https://cloud.tencent.com/developer/article/1158972
结论:
到这里,可以知道watch的通知机制是不可靠的,zkServer不会保证通知的可靠抵达。虽然zkclient与zkServer端是会有心跳机制保持链接,但是如果通知过程中断开,即时重新建立连接后,watch的状态是不会恢复。

参考

https://www.cnblogs.com/qiniu/p/10735183.html
https://cloud.tencent.com/developer/article/2183969

posted @ 2024-11-28 10:38  LdreamerD  阅读(89)  评论(0)    收藏  举报