第 11 篇 的结论是:多数 Listener/Cluster/Route/Secret 变更应走 xDS + warming,而不是重启进程。仍有一类变更必须动二进制或无法热替换的进程状态——这时进入 Hot restart:新进程起来、旧进程 drain,现有连接不迁移。
本文钉 Hot restart 架构、drain / parent shutdown 时间线,以及它与 LDS drain、纯 xDS 更新的边界。扩展过滤器如何挂在请求路径上见 第 13 篇。
本文是「Envoy / 数据面代理内核」系列第 12 篇(共 16 篇)。→ 系列目录
篇目 核心内容 第 11 篇 · Warming / SDS xDS 热更新边界 第 12 篇 · Hot restart / drain 共享内存、交接、排空 第 13 篇 · 扩展边界 Wasm / Lua / ext_authz / ratelimit
版本锚定:Envoy v1.39.0 Hot restart、Command line options(
--drain-time-s/--parent-shutdown-time-s/--restart-epoch)。Windows 不支持 Hot restart。无伪造重启日志。
一、Hot restart 解决什么问题
官方表述:Envoy 可以在 drain 过程中完整重载自身(代码与配置),且不在排空期间主动丢掉仍可优雅关闭的连接。但:
现有连接不会转移到新进程;它们必须在 drain 内结束,否则被终止。
因此 Hot restart
不是「连接热迁移」,而是「新进程接
listen,旧进程排空」。与 Nginx reload
的父子模型同族,但 Envoy 额外用共享内存对齐多进程统计,并用
UDS 上的 RPC 协调。
相对 第 11 篇 的 xDS 路径:
| xDS 热更新 | Hot restart | |
|---|---|---|
| 进程 | 单进程内对象替换 | 父子双进程重叠 |
| 连接 | 通常保留;LDS 变更触发 listener drain | 不迁移;旧连接在父进程排空 |
| 典型触发 | LDS/RDS/CDS/EDS/SDS | 二进制升级、无法热替换项 |
| 统计 | 同进程连续 | 经共享内存 / UDS 交接对齐 |
二、架构:UDS RPC 与共享内存
v1.39.0 Hot restart 文档给出的骨架:
- 两个活跃进程通过 Unix domain socket 上的基础 RPC 通信。
- 旧进程的 counter 经 UDS
发给新进程;gauge 也会传输,但标记
NeverImport的除外。重启结束后,从旧进程导入的 gauge 会被清理;server.hot_restart_generation这类特殊 gauge 保留。 - 新进程先完成初始化(加载配置、初始服务发现与健康检查等),再向旧进程索取 listen socket 副本。
- 新进程开始 listen,然后通知旧进程 开始 drain。
- drain 结束后(通常),新进程通知旧进程 关闭;剩余连到旧进程的连接被关掉。
Matt Klein 的设计文(B 级,机制与文档一致)补充:共享内存区承载版本信息、原始 stat 存储与少量跨进程锁;epoch 0 创建区域,后续 epoch attach。布局或 RPC 协议不兼容时热重启失败并给出明确错误——这是「不能跨任意大版本无脑 hot restart」的物理原因。
sequenceDiagram
participant Parent as ParentEnvoy
participant Child as ChildEnvoy
Note over Child: Full init: config, SDS/EDS, HC
Child->>Parent: Request listen sockets
Parent-->>Child: Pass sockets by worker index
Child->>Child: Start accepting
Child->>Parent: Begin drain
Note over Parent: Graceful close; more aggressive over time
Child->>Parent: Shutdown parent
Parent->>Parent: Exit; remaining conns closed
容器边界:文档明确 Hot restart
设计为父子可在不同容器,只要 UDS
可达。同一宿主机跑多套独立 Envoy 时,用
--base-id 或 --use-dynamic-base-id
隔离共享内存,避免冲突。
三、时间线:epoch、drain、parent shutdown
| 参数 | 默认(文档) | 含义 |
|---|---|---|
--restart-epoch |
0(首次) | 第几次热重启;决定创建还是 attach 共享内存。包装器常设
RESTART_EPOCH |
--drain-time-s |
600s | Hot restart 或 LDS 修改/删除 Listener 时的连接排空窗口 |
--parent-shutdown-time-s |
900s | 子进程等待后强制父进程退出;应 大于 drain 时间 |
--base-id |
(默认单套) | 多实例共享内存命名空间 |
Drain 行为要点:
- 旧进程尝试优雅关闭已有连接;具体策略依赖已配置的
filter(例如 HTTP/2 GOAWAY 等,随
--drain-strategy等选项变化)。 - 随时间推移,drain 更激进,以便在窗口内收敛。
- 连接不移交:长活连接要么在窗口内结束并在子进程重建,要么被切断。
Socket 处理(Linux):默认 reuse_port;Hot
restart 时按 worker index 把 socket
传给新进程,避免 drain 进程 accept
队列里的连接被丢。注意:若热重启时 concurrency
降低,旧 worker 的 accept
队列上可能丢连接;concurrency
升高则不会因此丢。
限制:Hot restart 期间更新 Listener 的
socket_options
不受支持,子进程沿用父进程选项;要改则全量重启或走
LDS 更新路径。
3.1 连接排空在数据面上意味着什么
「不丢连接」在文档里的精确含义是:drain
窗口内尽量优雅结束,而不是把 TCP 会话 splice
到子进程。对
HTTP/1,常见路径是不再接受新请求并关闭空闲连接;对
HTTP/2,可配合 GOAWAY
让客户端把新流转到已在听的子进程。WebSocket / 长轮询若超过
--drain-time-s,仍会被父进程关闭——这是 Hot
restart 相对纯 RDS 权重切换的硬代价。
边缘代理往往把 drain 拉长(文档举例可达数分钟到默认 10 分钟量级);服务网格 sidecar 则可把 drain 与 parent shutdown 缩到分钟内,因为实例可被编排层并行替换。口径必须以本环境连接时长分布为准,本篇不写未测的「推荐秒数」。
四、与 xDS-only、LDS drain 的对照
三者共享「排空」一词,层级不同:
xDS object swap → 同进程;Cluster 原子替换;Route 立即对新建请求可见
LDS listener drain → 同进程;旧 listener 连接排空(--drain-time-s)
Hot restart drain → 跨进程;父进程排空;listen 已在子进程
| 变更类型 | 推荐路径 | 为何 |
|---|---|---|
| 路由权重、endpoint、多数 filter 配置 | xDS | 无双进程;warming 可控 |
| 监听器增删改 | LDS(含 drain) | 仍同进程;无需换二进制 |
| Envoy 版本升级、无法热替换的 admin/tracing 等 | Hot restart 或滚动重建 | 必须新代码/新进程状态 |
| 仅换证书且已接 SDS | SDS | 见第 11 篇;不必为换证 hot restart |
动态配置总览的原话意图:引入 LDS 后,几乎所有方面都可动态配置;Hot restart 应留给极少配置变更、证书场景(未走 SDS 时)或二进制更新。
五、失败模式与运维含义
| 模式 | 现象 | 处理方向 |
|---|---|---|
| epoch / 共享内存不匹配 | 子进程起不来或协议错误 | 对齐版本;必要时冷启动 |
parent-shutdown-time ≤
drain-time |
父进程被过早杀掉,连接粗暴断开 | 拉大 parent shutdown |
| 长连接 > drain 窗口 | 业务中断 | 调窗口,或先切流量再重启 |
| concurrency 下调 | accept 队列丢连接 | 避免在热重启时降并发 |
| 多实例共用 base-id | 共享内存冲突 | 每套实例独立 base-id |
源码发行版带 restarter/hot-restarter.py
示例,可与 monit/runit
等进程管理配合;生产上常见的是编排层滚动 + 或发行版自带的
hot restart wrapper,本篇不展开具体发行包装。
六、争论与开放问题
| 争论 | A | B |
|---|---|---|
| Hot restart vs 滚动换 Pod | 单机原地升级、统计连续 | 编排更简单、失败域清晰 |
| 长 drain vs 短 drain | 边缘长连接友好 | 网格中东占用双倍内存窗口 |
开放问题:在 sidecar 密集节点上,Hot restart 双进程重叠的内存峰值与「直接杀旧 Pod」相比,哪条对节点水位更友好——取决于连接时长分布与编排并行度,需按环境实测。
七、参考资料
规范 / 官方文档(A)
- Envoy Proxy, Hot restart, docs v1.39.0。
- Envoy Proxy, Command line options —
--drain-time-s、--parent-shutdown-time-s、--restart-epoch、--base-id,v1.39.0。 - Envoy Proxy, xDS configuration API overview — 静态配置与 hot restart 关系,v1.39.0。
源码 / 设计(A/B)
envoyproxy/envoytag v1.39.0:source/server/hot_restart*.cc、restarter/hot-restarter.py。- Matt Klein, Envoy hot restart(Envoy Proxy 官方博客)——共享内存与 epoch 动机(B)。
站内对照
实验台账
- 本篇无重启实测输出;不写 drain 收敛曲线数字。
八、小结
- Hot restart = 子进程接 listen + 父进程 drain,连接不迁移。
- UDS RPC +
共享内存对齐协调与统计;
--restart-epoch/--base-id决定 attach 语义。 --drain-time-s<--parent-shutdown-time-s;LDS 变更与热重启共用 drain 时间旋语义。- 能 xDS 解决的不要 hot restart;二进制与不可热替换项才上进程级交接。
→ 系列目录 · 上一篇:Warming、SDS 与热更新 · 下一篇:扩展边界
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【Envoy 数据面】数据面全景:从 Listener 到 xDS 的可编程代理内核
定位 Envoy 相对 Nginx/HAProxy 静态配置代理与 Mesh 选型叙事的生态位;钉住请求路径、配置快照、FilterChainMatch、xDS warming、上游资源五条坐标系,并给出与 network/57 的分工及 16 篇阅读路线。
【Envoy 数据面】Warming、SDS 与热更新:ACK 为何不等于新路由上量
拆解 Listener/Cluster warming、SDS 证书轮转与 xDS 热更新边界;说明为何协议 ACK 之后流量仍可能走旧路由或 503,并区分 xDS 热更新与 Hot restart。
【Envoy 数据面】生产排障:五条坐标系上的失败清单
按第 1 篇五条坐标系拆解无上游、路由未生效、证书/SDS、warming 黑洞、hot restart 与连接泄漏;给出 admin 核对顺序与观测信号入口,不虚构延迟数字。
Envoy / 数据面代理内核:从 Listener 到 xDS
补齐站内 Envoy 单篇地图与 Mesh/网关选型之间的数据面内核层:Main/Worker、FilterChainMatch、HCM、Cluster/连接池、xDS warming 与 Hot restart,并以排障与选型收束。