如何高效获取数据变化通知

2021-03-25  本文已影响0人  一生逍遥一生

etcd的Watch特性是Kubernetes控制器的工作基础。

Kubernetes中watch特性,有四大核心问题:

  • client获取事件的机制,etcd使用轮询模式还是推送模式,两者各有什么缺点
  • 事件是如何存储的?会保留多久?watch命令汇总的版本号具有什么作用?
  • client和server发生短暂网络波动等异常因素后,导致事件堆积时,server端会丢弃事件吗?若监听的历史版本号server端不存在了,应该如何处理
  • 创建了上万个watcher监听key变化,当server端收到一个写请求后,etcd是如何根据变化的key快速找到监听它的watcher

获取事件的机制

client获取事件机制,分为两种:轮询和推送,两种机制etcd都使用过。
在etcd v2 Watch机制实现中,使用的是HTTP/1.x,实现简单、兼容性好,每一个watcher对应一个TCP连接。client通过HTTP/1.x协议长连接定时轮询
server,获取最新的数据变化事件。
etcd v3使用基于HTTP/2的gRPC的协议,双向流的watch API设计,实现了连接多路复用,并且HTTP消息被分解成为独立的帧,交错发送,帧是最小的数据单位。
每个帧会表示属于那个流,流有多个数据帧组成,每一个流拥有一个唯一ID,一个数据流对应一个请求或响应包,用来解决请求阻塞、连接无法复用,
实现多路复用、乱序发送。

一个client支持多个gRPC Stream,一个个gRPC Stream支持多个watcher,显著降低了开发复杂度。当watch连接的节点故障, etcd v3库支持带
自动重连到健康节点,并使用之前已接收的最大版本号创建新的watcher,避免旧事件回放等。

轮询消耗资源

事件是如何保存的,保存多长事件,版本号的作用

事件是如何保存的,保存多长事件,版本号的作用的本质是历史版本存储。
滑动窗口是仅保存有限的最近历史版本到内存中,MVCC机制则将历史版本保存在磁盘中,避免了历史版本的丢失,极大的提升了watch机制的可靠性。

etcd v2的滑动窗口是如何实现的:

  • 使用环形数组来存储历史事件版本,当key被修改后,相关事件就会添加到数组中来。
  • 如果超过eventQueue的容量,淘汰最旧的事件,容量为1000,如果超过1000条数据,可能会造成OOM。
  • 只能保存有限的历史版本事件版本,是不可靠的。当请求多、网络波动等异常时,会导致事件丢失,发起多次请求。

etcd v3的MVCC:将一个key的历史修改版本保存到boltdb里面。

watch命令的版本号作用:版本号是etcd逻辑时钟,出现网络波动等异常时,通过版本号,可以获取到错过的历史版本,不需要全量同步,是etcd Watch
机制数据增量同步的核心。

可靠的事件推送机制

etcd核心解决方案是复杂度管理,问题拆分。
etcd按照watcher的类型不同分为:synced watcher、unsynced watcher。
synced watcher表示此类watcher监听的数据都已经同步完毕,在等待新的变更。
创建的watcher未指定版本号、或指定的版本号大于etcd server当前最新的版本号,那么它就会保存到synced watcherGroup中。watcherGroup负责管理多个
watcher,能够根据key快速找到监听该key的的一个或多个watcher。

unsynced watcher:表示此类watcher监听的数据还未同步完成,落后于当前最新数据变更,正在努力追赶。

synced和unsynced的区别:synced和最新的版本号一致,不存在数据差异,unsyned落后于最新版本号,存在数据差异。

最新事件推送机制

请求经过KVServer、Raft模块后Apply到状态机时, 在MVCC的put事务中,它会将本次修改后的mvccpb.KeyValue保存到一个changes数组中。
将KeyValue转换成为Event事件,然后回调watchableStore.notify函数。notify会匹配出监听过此key并处于synced watherGroup中的watcher,
同时事件中的版本号要大于等于watcher监听的最小版本号,才能将事件发送到此watcher的事件channel中。
serverWatchStream的sendLoop goroutine监听到channel消息后,独处消息立即推送到client。

异常场景重试机制

若出现channel buffer满的情况,将此watcher从synced watcherGroup中删除,将其添加到vitim的watcherBatch结构中,通过异步机制
重试保证事件的可靠性。

WatchabkeKV模块会启动两个异步goroutine,其中一个是syncVitimLoop:负责slower watcher的堆积的事件推送:遍历vitim watcherBatch数据结构,将数据推送出去,
如果推送失败,进行重试;如果推送成功,将小于server当前版本号加入到unsynced watcherGroup,如果watcher的最小版本号大于server当前版本号,加入到
synced watcher集合。

历史事件推送机制

WatchableKV模块的另一个goroutine:syncWatchersLoop,正是负责unsynced watchergroup中的watch历史事件推送。
syncWatcherLoop会选择一批unsynced watcher批量同步,选出这一批unsynced watcher中监听的最小版本号,因为boltdb的key是按照版本号存储的,
通过指定查询key范围的最小版本号作为开始区间,当前server最大版本号作为结束区间,遍历boltdb获得所有历史数据。

高效事件匹配

区间树支持快速查找一个key是否在某个区间内,事件复杂度为O(LogN),因此etcd基于map和区间树实现了watcher与事件的快速匹配,具有良好的扩展性。

上一篇下一篇

猜你喜欢

热点阅读