Go 服务优雅退出:三步不断掉请求

Go 服务怎么优雅退出,我最近一次重启 admin 服务时又踩了一遍。上线时最容易被忽略的不是业务代码,是进程退出那一小段时间。

结论前置:Go 服务怎么优雅退出,就三步——先停止接新请求,再等手上的活干完,最后才释放资源。三步顺序错了,前面两步都白做。

前提:Go 服务怎么优雅退出,什么时候才需要

不是所有服务都要操心这件事。

如果服务只对内、调用方自己会重试,短暂的连接失败没人管。但只要有浏览器在等响应、或者有第三方在回调你,进程被直接掐掉就是用户看到报错。

我的判断很简单:这个接口有没有人在等结果,有,就得做。

第一步:让进程能收到退出信号

第一步不是写逻辑,是让进程知道「该收工了」。

我一开始的写法是 main 里 ListenAndServe 一直阻塞,除了 Ctrl+C 没有第二个出口。部署脚本杀掉进程,请求当场断。

改法是在 main 里起一个通知通道,收到系统信号以后不再阻塞。

这一步做完,你只是拿到了「可以收工了」这个事件,服务该断还是断。

第二步:停止接新请求,把旧的放完

第二步是这段时间里最需要耐心的一段。

收到信号以后,监听要停掉,新连接不再进来;已经在处理里的请求走完再退。Go 的服务端结构体自带的方法就是干这个的,关键是给它一个超时。

不给超时的等待,上线那天会变成一直不退出。我现在都配一个宽限期,超过时间还没结束就硬退,至少能保住重启流程。

第三步:收尾资源,注意顺序

第三步容易被漏掉:数据库连接、日志缓冲、后台起的定时任务。

顺序上,资源释放必须放在请求处理完之后。我踩过这个坑——先关了数据库,后面还在跑的请求直接报错,日志里一堆看不懂的失败。

后台任务要单独看。有的任务会自己跑一轮,退出时得通知它停下来,不然进程会卡在等待里。

验收:三步做完怎么确认

我自己的验收方式很朴素,不用工具:

一是重启过程中打开页面,看有没有报错;二是看日志里有没有半截的请求记录;三是看进程是不是在预期时间内退干净了。

三个都不出问题,这套退出就算可用了。优雅退出的标准不是代码写得多漂亮,是重启的时候没人发现。这也是我一直坚持做 Go 服务怎么优雅退出的原因。

判断和取舍

这套东西有成本:多几行代码,多一个超时值要调。

对内部服务我经常偷懒不做,因为改起来快。但只要是对外的,我会老老实实加上,理由是——重启是必然会发生的操作,不做就得每次都赌一把。

常见问题

Q:Go 服务怎么优雅退出,需要引第三方库吗?

A:一般不需要,标准库里负责 HTTP 服务的那个结构体自带停新请求、等旧请求的方法。要自己写的只是收信号和设超时这两段。

Q:等待超时设多久合适?

A:看最慢的那个接口要跑多久,比它长一点就行。设太长重启会拖,设太短等于没等。拿不准就先按最长接口的时间来定,跑几次再调。

Q:K8s 环境下还需要自己处理吗?

A:需要。容器平台发给你的只是同一个退出信号,怎么响应还是服务自己的事。另外要确认平台的宽限期比你的等待超时长,否则照样被强杀。

相关阅读


本文作者西米,37 岁程序员,一个人加五个 AI 员工在跑一家公司。踩过的坑和真实账本都写在公众号「西米分享」。

完整代码、踩坑清单与最新进展,都在公众号。

📱 关注公众号「西米分享」