平滑重启是在不中断现有连接、不丢弃处理中请求的前提下完成新旧进程切换,要求客户端无感知、TCP连接不断、长连接持续有效,依赖SO_REUSEPORT内核支持与进程协作机制。

什么是平滑重启(graceful restart)
平滑重启不是 kill -HUP 或 systemctl reload 的简单代名词,而是指服务在不中断已有连接、不丢弃正在处理请求的前提下完成新旧进程切换。关键判断标准是:客户端无感知,TCP 连接不断,HTTP 长连接、WebSocket、上传中请求等持续有效。
实现依赖两个核心能力:SO_REUSEPORT(内核支持)和进程间协作(如父进程监听 socket、子进程接管)。Nginx、OpenResty、Gunicorn 等成熟服务已内置该逻辑;自研服务需手动实现。
Linux 内核层面的关键支持:SO_REUSEPORT
没有 SO_REUSEPORT,多个进程无法同时 bind 同一端口,平滑重启只能靠「先启后停」或「socket 传递」。启用它后,新旧进程可共存并行接收新连接,旧进程只处理已有连接直至自然退出。
本课程在设计上本着懂方法,重应用的总体思路,突出体现职业教育的技能型、应用性特色,着重培养学生的实践应用技能,力求达到理论方法够用,技术技能过硬的目的。 通过本课程的学习,使学生具备Android平台应用开发相关知识、良好的编程习惯和手机应用软件开发的能力,能胜任基于Android平台的手机软件研发等工作任务。感兴趣的朋友可以过来看看
- 要求内核 ≥ 3.9(主流发行版默认开启)
- 需在 socket 创建时显式设置:
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) - 注意:
SO_REUSEADDR不等价于SO_REUSEPORT,后者才支持负载分发与平滑切换 - glibc 2.22+ 才完全支持该选项的可靠行为,低版本可能 fallback 到单进程绑定
应用层如何安全关闭旧进程
新进程启动后,旧进程不能立刻退出,必须等待所有活跃连接完成处理。常见做法是监听信号(如 SIGUSR2 或 SIGHUP),进入「拒绝新连接 + 等待存量连接关闭」状态。
- 监听 socket 应设为非阻塞,并在收到信号后
close()它,阻止 accept 新连接 - 对每个已建立连接,设置合理的 idle timeout(如 30s),超时后主动 close
- 若使用 epoll/kqueue,需从事件循环中移除监听 fd,但保留 client fd 直至其自然断开或超时
- 避免用
shutdown(SHUT_RDWR)强制终止连接——这会破坏 HTTP/2 流或 WebSocket 帧完整性
systemd 下的配合要点
直接用 systemctl reload 不一定触发平滑重启,取决于服务是否实现了 reload 逻辑。systemd 本身不理解「graceful」,它只负责发信号、等待进程退出。
- 服务 unit 文件中应配置:
Type=notify+NotifyAccess=all,允许进程通过sd_notify("RELOADING=1")告知 systemd 正在重载 -
Restart=on-failure不能替代平滑重启——它是崩溃恢复机制,会中断连接 - 避免在
ExecReload中写kill -HUP $MAINPID就完事;要确保目标进程真正支持该信号语义 - 调试时可用
journalctl -u your-service -f观察sd_notify日志,确认 reload 是否被正确识别









