变更通知
概念:
etcd 通过对 watcher 进行分类,来实现事件的可靠性:
- synced watcher,此类 watcher 监听的数据都已经同步完毕,在等待新的变更。
- unsynced watcher,此类 watcher 监听的数据还未同步完成,落后于当前最新数据变更,正在努力追赶。
- victim watcher,此类 slower watcher 的推送 channel buffer 堆满,etcd 会将其移动到专门的队列中异步机制重试。
订阅流程:
- 当 Client 发起一个 watch key 请求的时候,etcd 的 WatchServer 收到 watch 请求后,会创建一个 serverWatchStream, 它负责接收 client 的 gRPC Stream 的 create/cancel watcher 请求 (recvLoop goroutine),并将从 MVCC 模块接收的 Watch 事件转发给 client(sendLoop goroutine)。
- 当 serverWatchStream 收到 create watcher 请求后,serverWatchStream 会调用 MVCC 模块的 WatchStream 子模块分配一个 watcher id,并将 watcher 注册到 MVCC 的 WatchableKV 模块。
- etcd 启动后,WatchableKV 模块会运行 syncWatchersLoop 和 syncVictimsLoop goroutine,分别负责不同场景下的事件推送。
etcd 使用 map 记录了监听单个 key 的 watcher,但是你要注意的是 Watch 特性不仅仅可以监听单 key,它还可以指定监听 key 范围、key 前缀,因此 etcd 还使用了区间树。当收到创建 watcher 请求的时候,它会把 watcher 监听的 key 范围插入到上面的区间树中,区间的值保存了监听同样 key 范围的 watcher 集合 /watcherSet。
当产生一个事件时,etcd 首先需要从 map 查找是否有 watcher 监听了单 key,其次它还需要从区间树找出与此 key 相交的所有区间,然后从区间的值获取监听的 watcher 集合。区间树支持快速查找一个 key 是否在某个区间内,时间复杂度 O(LogN),因此 etcd 基于 map 和区间树实现了 watcher 与事件快速匹配,具备良好的扩展性。
推送流程:
- 当你创建完成 watcher 后,此时你执行 put hello 修改操作时,如上图所示,请求经过后的 mvccpb.KeyValue 保存到一个 changes 数组中。
- 在 put 事务结束时,它会将 KeyValue 转换成 Event 事件,然后回调 watchableStore.notify 函数。notify 会匹配出监听过此 key 并处于 synced watcherGroup 中的 watcher,同时事件中的版本号要大于等于 watcher 监听的最小版本号,才能将事件发送到此 watcher 的事件 channel 中。
注意:
若 watcher 监听的版本号已经小于当前 etcd server 压缩的版本号,历史变更数据就可能
已丢失,因此 etcd server 会返回 ErrCompacted 错误给 client。client 收到此错误后,需重新获取数据最新版本号后,再次 Watch。在业务开发过程中,使用 Watch API 最常见的一个错误之一就是未处理此错误。
其次,Watch 返回的 WatchChan
有可能在运行过程中失败而关闭,此时 WatchResponse.Canceled
会被置为 true
,WatchResponse.Err()
也会返回具体的错误信息。所以在 range WatchChan 的时候,每一次循环都要检查 WatchResponse.Canceled
,在关闭的时候重新发起 Watch 或报错。
选型分析
方案选型可以从业务系统的需求和 etcd 的特性、性能,两个方面着手。
业务系统
先看使用 etcd 提供服务的目标系统。如果你正在深入 Kubernetes 或开始使用服务网格,您可能会遇到术语“控制平面(control plane)”和“数据平面(data plane)”。术语 “控制平面” 和 “数据平面” 都是关于关注点的分离,即系统内职责的明确分离。控制平面是一切与策略建立和下发有关的部分,而数据平面是一切与执行策略有关的部分。当控制平面出现故障,只会影响新的策略变更变更,但不会影响已有策略执行,即,数据平面的功能。
以 Kubernetes 为例,其核心服务包括:
组件 | 描述 |
kube-apiserver | 提供了资源的唯一入口,并提供认证、授权、访问控制、API 注册和发现等 |
kube-scheduler | 负责资源的调度,按照预定的调度策略将 Pod 调度到相应的机器上 |
kube-controller-manager | 负责维护集群的状态,比如故障检测、自动扩展、滚动更新等 |
etcd | 存储整个集群的状态 |
kube-proxy | 负责为 Service 提供 cluster 内部的服务发现和负载均衡 |
以上服务故障,并不会影响当前已有 Pod 正常对外提供服务。
Why etcd
再看 etcd 本身。要了解 etcd 适用的场景,质量最高的来源是其官网。
介绍:
“etcd” 名字来源于两个想法:unix “/etc” 文件夹和 分布式( “d”istributed)系统。“/etc” 文件夹是存储单个系统的配置数据的地方,而 etcd 存储大规模分布式系统的配置信息。因此,“d”istributed “/etc” 是 “etcd”。
etcd 被设计为大规模分布式系统的通用基座。这类系统永远不容忍裂脑操作,并愿意牺牲可用性来实现该目标。
分布式系统使用 etcd 用于配置管理、服务发现和协调分布式工作。etcd 的常见分布式模式包括领导者选举、分布式锁和监控机器活动。
使用场景:
- CoreOS 的 Container Linux:在 Container Linux 上运行的应用程序可以获得自动、零停机的 Linux 内核更新。Container Linux 使用 Locksmith 来协调更新。Locksmith 在 etcd 上实现了分布式信号量,以确保在任何给定时间只有集群的一个子集在重新启动。
- Kubernetes 将配置数据存储到 etcd 中,用于服务发现和集群管理;etcd 的一致性对于正确调度和操作服务至关重要。Kubernetes API 服务器将集群状态持久化为 etcd。它使用 etcd 的 watch API 来监视集群并生效关键的配置变更。( 2016 年 Kubernetes 1.6 发布,默认启用 etcd v3,助力 Kubernetes 支撑 5000 节点集群规模)
其他:
- 最大可靠数据库大小: 数 GB
- 因为缺少数据分片,复制无法水平扩展
- 租约提供了一种用于减少中止请求数量的优化机制。
从基本介绍以及使用场景来看,etcd 的定位在于存储数据量小、更新频率低的数据,用于一致性要求高于可用性、无需水平扩展的场景。
性能
硬件推荐
以 超大型集群 为例,一个超大型型集群服务的客户端超过 1500 个,每秒请求超过 10000 个,存储数据超过 1 GB。
云厂商 | 机型 | CPU | 内存 (GB) | 最大并发 IOPS | 磁盘带宽 (MB/s) |
AWS | m4.4xlarge | 16 | 64 | 16,000 | 250 |
GCE | n1-standard-16 + 500GB PD SSD | 16 | 60 | 15,000 | 250 |
性能指标
压测的硬件配置:
- Google Cloud Compute Engine
- 3 machines of 8 vCPUs + 16GB Memory + 50GB SSD
- 1 machine(client) of 16 vCPUs + 30GB Memory + 50GB SSD
- Ubuntu 17.04
- etcd 3.2.0, go 1.8.3
写性能
Key 数量 | Key 大小 (byte) | Value 大小 (byte) | 连接数 | Client 数 | 目标 etcd server | 平均写 QPS | 平均请求延迟 | 平均服务 RSS |
10,000 | 8 | 256 | 1 | 1 | leader only | 583 | 1.6ms | 48 MB |
100,000 | 8 | 256 | 100 | 1000 | leader only | 44,341 | 22ms | 124MB |
100,000 | 8 | 256 | 100 | 1000 | all members | 50,104 | 20ms | 126MB |
读性能
请求数 | Key 大小 (byte) | Value 大小 (byte) | 连接数 | Client 数 | 一致性 | 平均读 QPS | 平均请求延迟 |
10,000 | 8 | 256 | 1 | 1 | Linearizable | 1,353 | 0.7ms |
10,000 | 8 | 256 | 1 | 1 | Serializable | 2,909 | 0.3ms |
100,000 | 8 | 256 | 100 | 1000 | Linearizable | 141,578 | 5.5ms |
100,000 | 8 | 256 | 100 | 1000 | Serializable | 185,758 | 2.2ms |
总结
一般 etcd 的集群为 3 或 5 个节点,Key 数量为 10w~ 规模下,预估集群性能如下:
- 写请求只有 Leader 才能处理,所以写性能不随节点数增加而增加,只取决于单机配置,处理量级大概为 1w~ QPS,平均延迟在 10ms~50ms。
- 串行读取所有节点均可处理,无需共识,处理量级大概为 10w~ QPS,平均延迟在 5ms 以内。
- 线性读取所有节点均可处理,但需要请求 Leader 获取 ReadIndex,性能会稍差,节点数对其提升有限,且容易受写请求影响,处理量级大概为 10w QPS,平均延迟在 10ms 以内。
适用场景
综合目标系统和 etcd 本身的细节来看:
- 首先,不建议将 etcd 用于目标系统的数据面。例如,配置中心产品,不适合使用 etcd 作为存储;
- 其次,谨慎将 etcd 用于对数据分片和水平扩展有要求的控制面系统。例如:跨可用区的服务发现,可以对服务类型进行区分,尽量减少多个可用区之间需要复制同步的服务数据量。
- 最后,etcd 租约、变更通知等功能的复杂度偏高。技术可控要求较高的系统,应谨慎使用相关功能。
本文作者 : cyningsun
本文地址 : https://www.cyningsun.com/01-27-2023/etcd-implement-and-tech-selection.html
版权声明 :本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!