bigywater.tech
微服务 · 49

变更风暴与容量:大量实例同时重启时 watch 的压力,实例数与 watch 连接数

一批实例同时重启重新注册,etcd 的 watch 会被推多少事件?watcher 数、key 的设计怎么影响?所有客户端同时重连时又是什么压力?这篇用单节点 etcd 3.7.2 逐项量出来。

50 万个事件
1000 次注册遇到 500 个监听同一前缀的 watcher 时 etcd 要推送的事件总数(put 延迟 p99 从 16.9 ms 涨到 78.1 ms)
20 万对 1 万
200 个 watcher 监听全局前缀与按服务前缀时收到的事件总数
+110 MB
200 个 watcher 同时重连、各补发 1000 个事件时 etcd 的内存变化

注册风暴与 watcher 数

实测 · 本机执行输出
  watcher 数 W | put 延迟 p50 / p99 | 1000 个 put 写完用时 | 全部 watcher 收齐 1000 个事件用时 | 收到的事件总数 | etcd 内存变化
  ------------+------------------+-----------------+--------------------------+---------+----------
  0           | 4.3 / 16.9 ms    | 0.64 s          | -                        | 0       | +8 MB    
  50          | 6.5 / 13.9 ms    | 0.86 s          | 0.86 s                   | 50,000  | +11 MB   
  200         | 11.7 / 27.8 ms   | 1.64 s          | 1.64 s                   | 200,000 | +20 MB   
  500         | 29.3 / 78.1 ms   | 4.15 s          | 4.15 s                   | 500,000 | +34 MB   
  事件总数 = 1000 × W:同样的 1000 次注册,watcher 越多,etcd 要推送的事件越多。put 的延迟和“所有 watcher 都收齐”的用时都随 W 增长(看上表),etcd 内存也涨;但要注意 watcher 的读线程(Python)、写入线程和 etcd 都在同一台机器上抢 CPU,延迟增长里有多少来自 etcd 的推送、多少来自测量环境本身,这个实验分不开

事件总数 = 变更数 × watcher 数;延迟、内存都随 watcher 数增长。

key 设计

实测 · 本机执行输出
  做法        | 收到的事件总数 | 收齐用时   | etcd 内存变化 | put 延迟 p99
  ----------+---------+--------+-----------+-----------
  (a) 全局前缀  | 200,000 | 1.65 s | +0 MB     | 30.2 ms   
  (b) 按服务前缀 | 10,000  | 0.59 s | +0 MB     | 11.8 ms   
  按服务前缀分片之后,每个 watcher 只收到它关心的那 50 个事件,事件总数是全局前缀的二十分之一;服务发现的 key 设计成 /服务名/实例 而不是平铺在一个前缀下,就是为了让 watcher 只订阅自己用到的服务

按服务分前缀,让每个 watcher 只收到自己关心的事件。

重连风暴

实测 · 本机执行输出
  情形                    | 补发的事件总数 | 全部补齐用时 | etcd 内存变化
  ----------------------+---------+--------+----------
  200 个 watcher 同时重连并补发 | 200,000 | 0.21 s | +110 MB  
  重连时从旧 revision 补发历史事件,每个 watcher 要补 1000 个事件,200 个同时重连就是 20 万个事件一起补(看上表的用时和内存变化)。注册中心恢复后所有客户端同时重连是 watch 压力最大的时刻之一,客户端重连要加随机退避,避免同时涌入(见第 3 篇)。第二节里“etcd 内存变化”几乎为 0,是因为第一节已经把 etcd 的内存撑起来了,该列只在第一、三节有参考意义

同时重连并补发历史事件占用较多内存;客户端重连要加随机退避。

分步演示

变更风暴与容量
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / Go / nginx / Envoy / etcd / Consul / Nacos / OpenTelemetry / Prometheus(按各篇注明用到哪些及版本);所有服务都是本机自己起的进程,端口 9150 以上,只监听 127.0.0.1,不连任何外部服务,不碰本机已有的实例;故障用 kill -9 进程、让连接断开或在代码里注入延迟与错误来制造;计时用 time.perf_counter,数字每次运行会有小幅抖动。etcd 3.7.2 单节点(自己起的进程,每次运行前清空数据目录);watcher 是 HTTP/JSON 网关上的流连接(每个一个 Python 线程,只数事件个数、不解析 JSON);写入线程、watcher 线程和 etcd 在同一台机器上争 CPU,延迟增长的来源分不开;etcd 内存用 ps 看 RSS,第二节的内存列因为第一节已经撑高了内存而没有参考意义;W=500 以上和真实 gRPC 客户端没有测
    • 没有验证:clientv3 的 watch(多个 watcher 共享一条 gRPC 流)的开销;etcd 集群(多节点)中 watch 对 leader 与 follower 的负担;客户端重连的随机退避对峰值的削平效果;etcd 的 watch 事件合并(同一 revision 的多个事件);proxy(grpc-proxy)对 watch 的合并
    京ICP备2026067502号-1京公网安备11010502063487号