GIN 服务如何实现优雅停机

什么是优雅停机?

优雅停机(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 流程图:

sequenceDiagram participant K8s Control Plane participant Pod participant App Note over K8s Control Plane,App: 1. 触发Pod终止(滚动更新/删除等) K8s Control Plane->>Pod: 标记Pod为Terminating Note right of Pod: 2. 开始terminationGracePeriod倒计时 Pod->>App: 3. 执行preStop钩子 Note right of App: 预关闭操作(例如:
- 从负载均衡摘除
- 通知其他服务
- 清理临时文件) 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")
}

优雅停机示例

可以看出服务强制关机的时候,请求还是正常执行了。

优雅停机流程

  1. 启动服务:正常启动HTTP服务器监听请求
  2. 信号监听:监听操作系统信号(SIGINT, SIGTERM)
  3. 停止接收新请求:当收到信号时,服务器停止接收新请求
  4. 处理进行中的请求:等待正在处理的请求完成(有超时限制)
  5. 资源清理:关闭所有打开的连接和资源
  6. 服务退出:最后退出程序

超时控制

在上面的代码中,我们使用了5秒的超时控制:

go 复制代码
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)

这是为了防止某些请求无限期挂起,导致服务无法正常退出。超过这个时间后,无论请求是否完成,服务都会强制退出。


实际应用中的考虑

  1. Kubernetes中的优雅停机:Kubernetes在删除Pod时会先发送SIGTERM信号,然后等待terminationGracePeriodSeconds(默认30秒)后才发送SIGKILL
  2. 数据库连接池处理:确保在关闭前所有数据库连接被正确释放
  3. 分布式锁释放:如果使用了分布式锁,确保在关闭前释放
  4. 消息队列消费者:确保消息处理完成并正确ack

优雅停机是现代Web服务开发中必不可少的一部分,特别是在微服务和云原生架构中,它确保了服务的可靠性和数据的一致性。

相关推荐
程序员爱钓鱼14 分钟前
Go语言实战案例-读取用户输入并打印
后端·google·go
{⌐■_■}7 小时前
【Kafka】登录日志处理的三次阶梯式优化实践:从同步写入到Kafka多分区批处理
数据库·分布式·mysql·kafka·go
高hongyuan8 小时前
Go语言教程-开发工具-Visual Studio
go·visual studio
考虑考虑12 小时前
go中的切片
后端·go
程序员爱钓鱼1 天前
限流、控并发、减GC!一文搞懂Go项目资源优化的正确姿势
后端·google·go
Jerry Lau1 天前
go go go 出发咯 - go web开发入门系列(二) Gin 框架实战指南
前端·golang·gin
叹人间,美中不足今方信1 天前
gRPC服务发现
rpc·go·服务发现
Code季风1 天前
将 gRPC 服务注册到 Consul:从配置到服务发现的完整实践(上)
数据库·微服务·go·json·服务发现·consul
Code季风2 天前
微服务分布式配置中心:Gin Web 服务层与 gRPC 服务层集成 Nacos 实战
分布式·微服务·rpc·架构·go·gin·consul
考虑考虑2 天前
go中的Map
后端·程序员·go