什么是优雅停机?
优雅停机(Graceful Shutdown)是指服务在停止时能够以一种有序、安全的方式处理完当前正在进行的请求,释放资源,然后才真正退出的过程。与之相对的是强制终止(Kill),即立即停止服务,不考虑当前请求的状态。
为什么需要优雅停机?
这里我们结合一个创建用户信息的接口来说明,示例如下:
go
r := gin.Default()
// 路由:模拟存储用户数据的接口
r.POST("/users", func(c *gin.Context) {
// 模拟用户数据
type User struct {
Name string `json:"name"`
}
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input"})
return
}
time.Sleep(5 * time.Second)
// 插入数据到 PostgreSQL
query := "INSERT INTO users (name) VALUES ($1)"
_, err := db.Exec(query, user.Name)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to insert data"})
return
}
fmt.Println("User added")
c.JSON(http.StatusOK, gin.H{"message": "User added"})
})
// 启动服务(没有优雅停机机制)
if err := r.Run(":8080"); err != nil {
log.Fatalf("Failed to run server: %v", err)
}
在上述代码中,如果用户在请求处理过程中服务被中断,会立刻停止,对应的接口调用中也会立即停止,这可能会导致数据丢失。
我们的服务没有实现优雅停机的情况下是如何保证数据丢失的呢,一般部署在K8S集群中的服务由k8s来兜底实现。
Kubernetes 中的优雅停机
在 Kubernetes 中,preStop
钩子和 terminationGracePeriodSeconds
是实现 Pod 优雅停机的两个重要机制。它们通常结合使用,以确保容器在被终止时能够安全地完成清理工作。
K8S控制器执行删除 POD 流程图:
- 从负载均衡摘除
- 通知其他服务
- 清理临时文件) App-->>Pod: preHook执行完成 Pod->>App: 4. 发送SIGTERM信号 Note right of App: 应用开始优雅停机:
- 停止接受新请求
- 完成进行中的事务 alt 在grace period内完成 App-->>Pod: 5. 进程正常退出 Pod->>K8s Control Plane: 报告成功终止 else 超时未完成 K8s Control Plane->>Pod: 6. 发送SIGKILL(9) Pod->>App: 强制终止进程 endrol Plane->>Pod: 发送 SIGKILL end
1. preStop
钩子
preStop 钩子在容器被终止之前执行,用于执行清理操作或优雅关闭服务。它可以在容器收到 SIGTERM
信号之前运行自定义的命令或脚本。
2. terminationGracePeriodSeconds
terminationGracePeriodSeconds 是 Pod 的一个属性,用于指定 Kubernetes 在强制终止 Pod 之前等待的时间。默认值为 30 秒,但你可以根据需要调整它。
3. 配置示例
以下是一个 Pod 的 YAML 配置示例,展示了如何结合使用 preStop 钩子和 terminationGracePeriodSeconds:
yaml
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
terminationGracePeriodSeconds: 30 # 设置优雅关闭时间为 30 秒
containers:
- name: my-container
image: my-image
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "echo 'Stopping container...'; sleep 10"]
关键点
- terminationGracePeriodSeconds:控制容器关闭时的最大等待时间。
- preStop 钩子:容器关闭前执行的命令,帮助在容器退出前完成清理工作。
通过合理配置这两个参数,可以确保 Kubernetes Pod 在被终止时能够优雅地完成清理工作,避免数据丢失或资源泄漏。
GIN Web服务中的优雅停机实现
虽然 Kubernetes 提供了 preStop 钩子和 terminationGracePeriodSeconds 来帮助实现优雅停机,但这些机制并不能完全替代应用程序内部的优雅停机逻辑。为了确保应用程序能够优雅地关闭,还是需要在应用程序代码中处理 SIGTERM 信号,并根据需要合理配置 。
以下是一个使用Gin实现优雅停机的完整示例:
go
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
)
func main() {
// 初始化Gin路由
router := gin.Default()
// 添加一个模拟长时间运行的路由
router.GET("/long-task", func(c *gin.Context) {
time.Sleep(10 * time.Second) // 模拟长时间任务
c.JSON(http.StatusOK, gin.H{"message": "long task completed"})
})
// 添加健康检查路由
router.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "ok"})
})
// 创建HTTP服务器
srv := &http.Server{
Addr: ":8080",
Handler: router,
}
// 在goroutine中启动服务器
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
// 等待中断信号以优雅地关闭服务器
quit := make(chan os.Signal, 1)
// 捕获SIGINT(ctrl+c)和SIGTERM(kill命令)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
// 创建一个5秒超时的context
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 优雅关闭服务器
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
log.Println("Server exiting")
}
优雅停机示例:

可以看出服务强制关机的时候,请求还是正常执行了。
优雅停机流程
- 启动服务:正常启动HTTP服务器监听请求
- 信号监听:监听操作系统信号(SIGINT, SIGTERM)
- 停止接收新请求:当收到信号时,服务器停止接收新请求
- 处理进行中的请求:等待正在处理的请求完成(有超时限制)
- 资源清理:关闭所有打开的连接和资源
- 服务退出:最后退出程序
超时控制
在上面的代码中,我们使用了5秒的超时控制:
go
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
这是为了防止某些请求无限期挂起,导致服务无法正常退出。超过这个时间后,无论请求是否完成,服务都会强制退出。
实际应用中的考虑
- Kubernetes中的优雅停机:Kubernetes在删除Pod时会先发送SIGTERM信号,然后等待terminationGracePeriodSeconds(默认30秒)后才发送SIGKILL
- 数据库连接池处理:确保在关闭前所有数据库连接被正确释放
- 分布式锁释放:如果使用了分布式锁,确保在关闭前释放
- 消息队列消费者:确保消息处理完成并正确ack
优雅停机是现代Web服务开发中必不可少的一部分,特别是在微服务和云原生架构中,它确保了服务的可靠性和数据的一致性。