在 Gin 框架中结合 endless
库实现平滑重启(零停机更新),核心原理是通过 fork
子进程接管新请求,父进程处理完存量连接后退出。以下是完整实现方案及注意事项:
🛠️ 一、核心代码实现
go
package main
import (
"log"
"net/http"
"time"
"github.com/fvbock/endless"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/ping", func(c *gin.Context) {
// 模拟耗时操作(如数据库查询)
time.Sleep(5 * time.Second)
c.JSON(http.StatusOK, gin.H{"message": "pong"})
})
// 配置 endless 服务器
server := endless.NewServer(":8080", router)
server.ReadHeaderTimeout = 10 * time.Second
server.WriteTimeout = 30 * time.Second
// 监听信号并触发重启
if err := server.ListenAndServe(); err != nil {
if err != http.ErrServerClosed { // 非正常关闭时记录错误
log.Fatalf("Server failed: %v", err)
}
}
log.Println("Server exited gracefully")
}
⚙️ 二、平滑重启操作流程
-
编译并启动服务
bashgo build -o app && ./app # 输出示例:Server started with PID 12345
-
修改代码后重新编译
更新业务逻辑(如将
"pong"
改为"pong v2"
),重新编译:gogo build -o app
-
触发平滑重启
向父进程发送
SIGHUP
信号(kill -1
):bashkill -1 12345
- ✅ 新进程(如 PID 45678)启动并监听相同端口,处理新请求。
- ⏳ 旧进程 继续处理存量请求(如休眠中的
/ping
请求),完成后自动退出。
-
验证效果
- 重启前已发送的请求返回
"pong"
。 - 重启后新请求返回
"pong v2"
,服务无中断。
- 重启前已发送的请求返回
📌 三、关键机制说明
特性 | 原理 | 信号 |
---|---|---|
优雅重启 | fork 子进程继承监听套接字,新旧进程并行处理请求 |
SIGHUP (kill -1 ) |
优雅关闭 | 父进程处理完存量连接后退出 | SIGTERM /SIGINT |
强制终止 | 立即终止进程(不等待请求完成) | SIGKILL (kill -9 ) |
💡 信号说明:
SIGHUP
(默认):触发平滑重启SIGTERM
/SIGINT
:优雅关闭服务SIGUSR2
:强制重启(HammerTime
,不推荐)
⚠️ 四、生产环境注意事项
-
进程管理工具兼容性
- 避免与
supervisor
或systemd
直接管理 PID 的工具共用(重启后 PID 变化会导致误判服务崩溃)。 - 替代方案:通过脚本监听服务状态,而非依赖 PID。
- 避免与
-
容器化部署限制
Docker 默认单进程模型需调整:
bash# 使用 init 进程管理子进程 ENTRYPOINT ["/sbin/tini", "--", "./app"]
-
超时配置优化
iniserver.ReadHeaderTimeout = 5 * time.Second // 防止慢连接攻击 server.IdleTimeout = 120 * time.Second // 长连接超时控制
-
资源泄漏排查
-
父进程退出前确保释放数据库连接、文件句柄等资源。
-
使用
defer
或注册清理函数:scssserver.BeforeShutdown = func() { db.Close() }
-
🔄 五、替代方案(不依赖 endless)
若需避免 PID 变化问题,可使用标准库实现:
scss
// 参考网页8:通过 net.FileListener 传递套接字
l, _ := net.FileListener(os.NewFile(3, ""))
if os.Getenv("GRACEFUL_RESTART") == "true" {
syscall.Kill(syscall.Getppid(), syscall.SIGTERM) // 终止父进程
}
http.Serve(l, router)
缺点:需自行处理信号和进程同步,复杂度较高。
💎 总结
- 推荐场景 :
endless
+ Gin 适合快速实现平滑重启,适合非容器化或自定义进程管理的环境。 - 规避风险 :容器中需搭配
tini
,避免与 PID 敏感的工具集成。 - 验证要点 :通过
time.Sleep
模拟长请求,观察新旧进程交替是否阻塞。
通过合理配置和测试,可确保 Go 服务更新时用户请求零中断,提升生产环境可靠性。测试时建议使用 siege 或
wrk
模拟并发流量验证稳定性。