bigywater.tech
微服务 · 40

最小注册中心:数据结构、租约、心跳与过期

最小的注册中心要有什么?租约、心跳和过期怎么写?崩溃的实例多久才会被摘掉?ttl 该是心跳间隔的几倍?实例很多时过期扫描怎么做?这篇自己写一个 40 行的注册中心,把这些量出来。

2.05 到 2.93 s
ttl 2 s、后台每 1 s 扫描一次时,崩溃的实例从注册中心消失用的时间(惰性检查是 2.00 s,主动 deregister 是 59 µs)
2652 对 3
心跳丢包 10%、1000 个实例 5 分钟里被误摘除的次数:ttl = 2 个心跳间隔 对 5 个心跳间隔
25.58 对 0.822 ms
100 万个实例、100 个到期时,扫描全部实例与最小堆的单次过期扫描耗时

租约与过期

实测 · 本机执行输出
  过期实现         | 最短     | 中位数    | 最长(崩溃后消失用了多久,20 次)
  -------------+--------+--------+-------------------
  惰性(查询时检查)    | 2.00 s | 2.01 s | 2.01 s            
  后台每 1 s 扫描   | 2.05 s | 2.51 s | 2.93 s            
  后台每 0.2 s 扫描 | 2.03 s | 2.11 s | 2.20 s            
  对照:主动 deregister 在调用返回时就已经消失(59 µs)。崩溃的实例要靠租约到期:消失时间 = ttl(2 s)再加上一个 0 到扫描间隔之间的延迟(最短和最长之差就是这个延迟的范围);惰性检查没有扫描延迟,但 lookup 之外别的地方(例如统计实例数、通知订阅者)仍然看到旧状态

崩溃的实例靠租约到期消失,延迟是 ttl 加扫描间隔内的随机量;主动注销立刻生效。

ttl 与误摘除

实测 · 本机执行输出
  ttl   | 需要连续丢几个心跳才会过期 | 5 分钟内误摘除次数(1000 个实例) | 理论期望(1000 × 300 × 丢包率^k × 0.9)
  ------+---------------+----------------------+-------------------------------
  1.5 s | 2             | 2652                 | 2700.0                        
  2.0 s | 2             | 2652                 | 2700.0                        
  3.0 s | 3             | 263                  | 270.0                         
  5.0 s | 5             | 3                    | 2.7                           
  理论期望是近似值(每个时刻“刚好连续丢了 k 个心跳”的概率)。ttl 是心跳间隔的 1.5 倍和 2 倍时,都只能容忍一次丢包(连续丢两次就过期),每个实例 5 分钟里平均被误摘几次;每多容忍一次连续丢包,误摘除大约降到十分之一(丢包率 10%)

ttl 是心跳间隔的 k 倍,就要连续丢 k 个心跳才误摘除;每多一倍容忍,误摘除降到约十分之一。

扫描的代价

实测 · 本机执行输出
  实例数 N     | 扫描全部     | 最小堆      | 删掉的(扫描) | 删掉的(堆)
  ----------+----------+----------+---------+-------
  10,000    | 0.25 ms  | 0.156 ms | 100     | 100   
  100,000   | 2.58 ms  | 0.385 ms | 100     | 100   
  1,000,000 | 25.58 ms | 0.822 ms | 100     | 100   
  扫描的代价随 N 线性增长,实例多了就不能每秒全扫一遍;最小堆只处理到期的那几个,代价取决于到期的数量。续约会往堆里推新的条目,旧条目留在堆里直到弹出时被发现已经过期失效(惰性删除),所以堆会比实例数大一些

全扫随实例数线性增长;最小堆只处理到期的。

分步演示

最小注册中心
本步的对象

    本文用到的工具与边界

    • 本机 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,数字每次运行会有小幅抖动。注册中心是我写的 Python 类(没有网络,方法调用),过期有惰性检查、后台线程扫描、最小堆三种写法;第二节用虚拟时钟(1000 个实例、5 分钟、10% 独立丢包,带种子);第一节是真实时间,每种实现 20 次;第三节是单次测量
    • 没有验证:网络上心跳的延迟与乱序;注册中心集群的多副本复制(见后面的 etcd、Consul 篇);心跳丢包不独立(突发丢包)时的误摘除;租约到期后实例自动重新注册的竞争;服务端处理心跳的并发上限
    京ICP备2026067502号-1京公网安备11010502063487号