Gin优雅关停与平滑重启从零停机到热更新
文章导语
线上服务的"零停机部署"是现代运维的基本要求。Go的http.Server.Shutdown()为优雅关停提供了原生支持,但结合Gin框架时,需要正确管理中间件、数据库连接、消息队列等资源的生命周期。本文深入优雅关停的实现机制和平滑重启方案。
一、优雅关停的核心原理
go
func GracefulShutdown(router *gin.Engine, addr string) {
srv := &http.Server{
Addr: addr,
Handler: router,
}
// 在goroutine中启动服务
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("启动失败: %v", err)
}
}()
// 等待中断信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("正在关闭服务器...")
// 30秒的超时context------超过此时间强制退出
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Shutdown:不再接受新请求,等待现有请求完成
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("强制关闭: %v", err)
}
log.Println("服务器已安全退出")
}
Shutdown的内部过程:
- 关闭所有监听器(listener)
- 等待所有活跃连接完成
- 超时后强制关闭所有连接
二、完整生命周期的优雅关停
go
type App struct {
router *gin.Engine
srv *http.Server
db *sql.DB
redis *redis.Client
kafka sarama.AsyncProducer
}
func (a *App) Start() error {
// 启动HTTP服务
go func() {
if err := a.srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("HTTP服务异常: %v", err)
}
}()
return a.waitForShutdown()
}
func (a *App) waitForShutdown() error {
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("开始优雅关停...")
// 1. 停止接收新请求
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := a.srv.Shutdown(ctx); err != nil {
log.Printf("HTTP关停异常: %v", err)
}
// 2. 关闭数据库连接池
log.Println("关闭数据库...")
a.db.Close()
// 3. 关闭Redis
log.Println("关闭Redis...")
a.redis.Close()
// 4. 关闭Kafka生产者
log.Println("关闭Kafka...")
a.kafka.AsyncClose()
log.Println("关停完成")
return nil
}
三、请求级别的优雅处理
go
// 中间件:为每个请求设置超时
func RequestTimeout(timeout time.Duration) gin.HandlerFunc {
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), timeout)
defer cancel()
c.Request = c.Request.WithContext(ctx)
finished := make(chan struct{})
go func() {
c.Next()
close(finished)
}()
select {
case <-finished:
case <-ctx.Done():
c.AbortWithStatusJSON(504, gin.H{"msg": "请求超时"})
}
}
}
四、平滑重启方案
4.1 基于endless的零停机重启
go
import "github.com/fvbock/endless"
func main() {
router := gin.Default()
router.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{"msg": "pong"})
})
// endless接管信号处理
server := endless.NewServer(":8080", router)
server.BeforeBegin = func(add string) {
log.Printf("进程pid: %d", syscall.Getpid())
}
if err := server.ListenAndServe(); err != nil {
log.Printf("服务器错误: %v", err)
}
}
4.2 K8S环境下的优雅重启
在Kubernetes中,不需要应用自身支持热重启。利用K8S的滚动更新:
yaml
apiVersion: apps/v1
kind: Deployment
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
spec:
terminationGracePeriodSeconds: 30
containers:
- name: app
lifecycle:
preStop:
exec:
command: ["/bin/sleep", "15"] # 给LB更新路由的时间
五、全文总结
- **Shutdown()**是Go标准库提供的优雅关停机制
- 按依赖顺序关闭资源:HTTP → DB → Cache → MQ
- endless提供零停机的平滑重启能力
- K8S rolling update是生产环境最推荐的部署方案
- 合理配置terminationGracePeriodSeconds
六、技术进阶展望
- Go 1.8+的Server.Shutdown实现
- 基于fd传递的零停机重启(类似Nginx)
- 蓝绿部署与金丝雀发布
参考文献
- Go net/http Shutdown文档
- endless: https://github.com/fvbock/endless
- Kubernetes Pod Lifecycle文档
- Gin官方示例 - Graceful restart
- 《云原生Go》优雅关停章节