Go 服务如何优雅地重启或停止
如何优雅退出
kubernetes 中,平滑退出应用的顺序如下:
- 监听并捕获 SIGTERM 信号
- k8s pod 应用的主进程 PID 为 1,至少要能接收的信号量
- 停止接受新的连接
- k8s 中存在两种 healthcheck 探针,参考
LivenessProbe
判断容器是否存活,若不健康,则 kubelet 将杀死容器,并根据容器的启动策略做相应的处理ReadinessProbe
用于判断容器是否启动完成(ready 状态),若 ready 则接受请求,若失败则不
- 完成已经存在的活跃请求
- 关闭keepalive
- 退出服务
原生实现方式
http 实现
如果你使用的是 Go 1.8 可以使用 http.Server
内置的 Shutdown()
方法优雅地关机
signal + context 实现
通过 signal + context 实现,参考
- 使用
signal.Notify
监听 SIGINT
和 SIGTERM
信号,这两个信号通常用于请求程序终止 srv.Shutdown(ctx);
主动关闭服务
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
)
var health bool
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
time.Sleep(5 * time.Second)
c.String(http.StatusOK, "Welcome Gin Server")
})
router.GET("/healthz", func(c *gin.Context) {
if health {
c.String(http.StatusOK, "ok")
} else {
c.String(http.StatusInternalServerError, "not ready")
}
})
srv := &http.Server{
Addr: ":8080",
Handler: router,
}
go func() {
health = true
// 服务连接
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
// 等待中断信号以优雅地关闭服务器(设置 5 秒的超时时间)
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutdown Server ...")
health = false
// 关闭长链接 https://pkg.go.dev/net/http#Server.SetKeepAlivesEnabled
srv.SetKeepAlivesEnabled(false)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 优雅关闭服务器,不再接受新的连接,但会等待所有当前请求处理完成
if err := srv.Shutdown(ctx); err != nil {
// 关闭失败
log.Fatal("Server Shutdown:", err)
}
log.Println("Server exiting")
}
第三方实现方式