Go 服务平滑升级时怎么处理正在运行的并发协程?

文章导读
Go 服务平滑升级处理并发协程的核心是在收到退出信号后,通过 context.Context 通知业务协程停止,并利用 sync.WaitGroup 等待任务完成。适用场景为 Web 服务或后台任务服务,风险边界在于必须设置强制退出超时时间,防止个别协程阻塞导致进程无法结束。
📋 目录
  1. 命令速用版
  2. 为什么会这样
  3. 分步处理
  4. 怎么验证是否生效
  5. 常见坑
  6. 常见问题
  7. 参考来源
A A

Go 服务平滑升级处理并发协程的核心是在收到退出信号后,通过 context.Context 通知业务协程停止,并利用 sync.WaitGroup 等待任务完成。适用场景为 Web 服务或后台任务服务,风险边界在于必须设置强制退出超时时间,防止个别协程阻塞导致进程无法结束。

先说结论:平滑升级依赖信号捕获与上下文取消机制,需配合外部流量摘除策略。

  • 适合:基于 http.Server 的 Web 服务、含长连接或后台任务的 Go 进程。
  • 先准备:代码中埋入信号监听、上下文取消通道及超时强制退出逻辑。
  • 验收:验证 SIGTERM 信号发送后,正在处理的请求能完成且新请求被拒绝。

命令速用版

若使用 Kubernetes 部署,配置 preStop 钩子配合 terminationGracePeriodSeconds 是标准做法;若手动管理进程,使用 kill -TERM 触发平滑退出。

# 发送终止信号触发平滑关闭
kill -TERM <pid>

# Kubernetes 部署片段示例
lifecycle:
  preStop:
    exec:
      command: ["sleep", "10"]
terminationGracePeriodSeconds: 30

为什么会这样

操作系统发送信号到进程退出之间存在时间差,Go 运行时默认不会等待协程结束。当父进程收到 SIGTERM 信号时,若直接退出,所有子协程会被强制中断,导致数据不一致或请求失败。平滑升级要求进程在退出前主动停止接收新流量,并等待现有协程执行完毕或达到超时阈值。

Go 服务平滑升级时怎么处理正在运行的并发协程?

分步处理

第一步是捕获退出信号,使用 signal.Notify 监听 syscall.SIGINT 和 syscall.SIGTERM。第二步是关闭监听器,调用 http.Server.Shutdown 停止接受新连接。第三步是等待协程,业务协程需监听 context.Done() 退出,主协程使用 sync.WaitGroup 等待。第四步是设置超时,使用 time.After 防止无限等待。

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

// 等待协程完成
wg.Wait()

// 强制关闭服务器
server.Shutdown(ctx)

怎么验证是否生效

查看应用日志确认收到 shutdown 信号且正在处理的请求日志显示完成。使用 curl 连续请求服务,观察信号发送后新请求是否返回 502 或连接拒绝,而旧请求是否成功返回。检查进程退出码,正常平滑退出通常为 0,强制杀死为 137 。

常见坑

部分协程未监听 context 取消信号,导致 WaitGroup 永远无法归零。数据库事务在协程退出时未回滚,产生脏数据。依赖的外部服务超时时间大于服务关闭超时时间,导致关闭过程卡死。全局变量状态未在退出时清理,影响下次启动。

Go 服务平滑升级时怎么处理正在运行的并发协程?

常见问题

协程卡住导致无法退出怎么办?

设置强制超时时间,超时后直接终止进程。代码中需确保所有阻塞操作都带有 context 超时控制,避免单个协程无限阻塞。

Kubernetes 环境下如何配合?

配置 terminationGracePeriodSeconds 大于服务最大处理耗时,并在 preStop 中先摘除流量再发送信号。确保探针检测失败后流量完全切断再开始关闭流程。

后台定时任务怎么处理?

定时任务执行前检查全局退出标志位,若已收到退出信号则跳过本次执行。长任务需支持断点续传或补偿机制,避免中途退出导致数据不一致。

参考来源

  • Go Blog, "Graceful shutdown with Go", https://go.dev/blog/graceful-shutdown
  • Kubernetes Docs, "Container lifecycle hooks", https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-hooks