bigywater.tech
微服务 · 60

预热:新实例的权重逐渐提高

新实例一加入就按满额权重接流量会怎样?slow start 的流量怎么爬升、能省掉多少慢请求?客户端侧自己做预热权重行不行?实例重启后会重新预热吗?这篇用 Envoy 1.39.3 和一个冷启动的后端逐项量出来。

25% → 6%
新实例加入后第 0 秒落在它上面的流量比例:没有 slow start 与 window 10 s
200 → 102
冷启动期间慢请求(超过 50 ms)的个数:没有 slow start 与 window 10 s
131.9 → 131.1 ms
同样两种配置下的 p99,几乎没变,因为慢请求仍然超过总数的 1%

流量怎么爬升

实测 · 本机执行输出
  配置               | 加入后第 0 秒 | 加入后第 2 秒 | 加入后第 4 秒 | 加入后第 6 秒 | 加入后第 8 秒 | 加入后第 10 秒 | 加入后第 12 秒 | 加入后第 15 秒
  -----------------+----------+----------+----------+----------+----------+-----------+-----------+----------
  (a)没有 slow start | 25%      | 25%      | 25%      | 25%      | 25%      | 25%       | 25%       | 25%      
  (b)window 10 s   | 6%       | 10%      | 14%      | 18%      | 22%      | 25%       | 25%       | 25%      
  (c)window 20 s   | 11%      | 10%      | 14%      | 16%      | 18%      | 19%       | 20%       | 21%      
  每个格子是那一秒里落在新实例的请求占比;没有 slow start 时新实例一加入就接近 1/4(4 个实例轮询);有 slow start 时从很低的比例按窗口爬升,窗口走完后回到 1/4

slow start 让新实例的流量比例按窗口逐渐升到满额,而不是一加入就是满额;窗口越长爬得越慢。

冷启动的代价

实测 · 本机执行输出
  配置               | 加入后 16 s 的请求数 | 落在新实例上 | 慢请求(>50 ms) | 延迟 p50(ms) | 延迟 p99(ms) | 失败
  -----------------+---------------+--------+-------------+------------+------------+---
  (a)没有 slow start | 1600          | 400    | 200         | 8.6        | 131.9      | 0 
  (b)window 10 s   | 1600          | 299    | 102         | 8.8        | 131.1      | 0 
  (c)window 20 s   | 1600          | 260    | 106         | 8.6        | 131.3      | 0 
  慢请求几乎都是落在冷实例上的请求;slow start 减少的是冷的那 8 s 里落在新实例上的请求数。预热窗口比冷启动时间长得多时,冷实例接到的请求更少,但新实例也要更久才能分担满额的流量

slow start 把落在冷实例上的请求减少了大约一半,但 p99 不一定变好,取决于慢请求占总数的比例。

客户端侧线性预热

实测 · 本机执行输出
  配置           | 请求数  | 落在新实例上 | 慢请求(>50 ms) | p50(ms) | p99(ms) | 第 0 秒 | 第 4 秒 | 第 8 秒 | 第 12 秒 | 第 15 秒
  -------------+------+--------+-------------+---------+---------+-------+-------+-------+--------+-------
  客户端线性预热 10 s | 1600 | 283    | 82          | 8.3     | 131.2   | 0%    | 10%   | 20%   | 26%    | 23%   
  客户端线性预热 20 s | 1600 | 180    | 41          | 8.3     | 129.6   | 0%    | 6%    | 11%   | 16%    | 20%   
  线性预热从权重 1 起步,所以起初几乎不分流量;Envoy 的 slow start 用 min_weight_percent 做下限,起步比例更高

从接近 0 的权重起步,冷期间落在新实例上的请求更少,代价是新实例更久才分担满额流量。

重启之后

实测 · 本机执行输出
  配置               | 重启后第 0 秒 | 重启后第 2 秒 | 重启后第 4 秒 | 重启后第 6 秒 | 重启后第 8 秒 | 重启后第 10 秒 | 重启后第 12 秒 | 重启后第 15 秒
  -----------------+----------+----------+----------+----------+----------+-----------+-----------+----------
  (a)没有 slow start | 23%      | 25%      | 25%      | 25%      | 25%      | 25%       | 25%       | 25%      
  (b)window 10 s   | 2%       | 6%       | 12%      | 17%      | 20%      | 24%       | 25%       | 25%      
  配置               | 重启后 16 s 的请求数 | 落在重启的实例上 | 失败 | 慢请求(>50 ms) | p99(ms)
  -----------------+---------------+----------+----+-------------+--------
  (a)没有 slow start | 1600          | 398      | 0  | 198         | 131.6  
  (b)window 10 s   | 1600          | 275      | 0  | 82          | 130.8  
  “重启后第 0 秒”从新进程开始监听同一个端口的时刻算起;健康检查把它重新标成健康很快(上表第 0 秒一格里没有 slow start 的已经接近满额),所以重启之后是否重新预热,看的是 slow start 是否对“重新变健康的实例”再次生效
ERRORS {}

重新变健康的实例会被再次当作新实例预热。

分步演示

新实例预热
本步的对象

    本文用到的工具与边界

    • 本机 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,数字每次运行会有小幅抖动。Envoy 1.39.3,新实例通过文件型 EDS 加入;后端是本进程里的 Python 服务,冷启动用前 8 s 每个请求 120 ms 模拟(不是真实的 JIT 或缓存冷启动);客户端侧线性预热是我写的几行,不是 Dubbo;每种配置只测了 1 次(100 个/秒,22 s 或 30 s),占比是每秒一格的数字,有几个百分点的波动
    • 没有验证:真实应用的冷启动特征(JIT、连接池、本地缓存预热);aggression 参数对爬升曲线的影响(只用了默认的 1.0);Dubbo、Spring Cloud LoadBalancer、K8s 就绪探针(readiness)与预热的配合;nginx 的 slow_start(我印象里是商业版功能,没有验证);Nacos、Consul 里手动调整权重来预热;最小请求数负载均衡下 slow start 的行为
    京ICP备2026067502号-1京公网安备11010502063487号