美文网首页
如何高效获取数据变化通知

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

作者: 一生逍遥一生 | 来源:发表于2021-03-25 11:39 被阅读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与事件的快速匹配,具有良好的扩展性。

    相关文章

      网友评论

          本文标题:如何高效获取数据变化通知

          本文链接:https://www.haomeiwen.com/subject/dumlhltx.html