gin + endless 实现服务平滑重启

在 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")
}

⚙️ 二、平滑重启操作流程

  1. ​编译并启动服务​

    bash 复制代码
    go build -o app && ./app
    # 输出示例:Server started with PID 12345
  2. ​修改代码后重新编译​

    更新业务逻辑(如将 "pong" 改为 "pong v2"),重新编译:

    go 复制代码
    go build -o app
  3. ​触发平滑重启​

    向父进程发送 SIGHUP 信号(kill -1):

    bash 复制代码
    kill -1 12345
    • ​新进程​(如 PID 45678)启动并监听相同端口,处理新请求。
    • ​旧进程​ 继续处理存量请求(如休眠中的 /ping 请求),完成后自动退出。
  4. ​验证效果​

    • 重启前已发送的请求返回 "pong"
    • 重启后新请求返回 "pong v2",服务无中断。

📌 三、关键机制说明

​特性​ ​原理​ ​信号​
​优雅重启​ fork 子进程继承监听套接字,新旧进程并行处理请求 SIGHUP (kill -1)
​优雅关闭​ 父进程处理完存量连接后退出 SIGTERM/SIGINT
​强制终止​ 立即终止进程(不等待请求完成) SIGKILL (kill -9)

💡 ​​信号说明​​:

  • SIGHUP(默认):触发平滑重启
  • SIGTERM/SIGINT:优雅关闭服务
  • SIGUSR2:强制重启(HammerTime,不推荐)

⚠️ 四、生产环境注意事项

  1. ​进程管理工具兼容性​

    • 避免与 supervisorsystemd 直接管理 PID 的工具共用(重启后 PID 变化会导致误判服务崩溃)。
    • 替代方案:通过脚本监听服务状态,而非依赖 PID。
  2. ​容器化部署限制​

    Docker 默认单进程模型需调整:

    bash 复制代码
    # 使用 init 进程管理子进程
    ENTRYPOINT ["/sbin/tini", "--", "./app"]
  3. ​超时配置优化​

    ini 复制代码
    server.ReadHeaderTimeout = 5 * time.Second  // 防止慢连接攻击
    server.IdleTimeout = 120 * time.Second      // 长连接超时控制
  4. ​资源泄漏排查​

    • 父进程退出前确保释放数据库连接、文件句柄等资源。

    • 使用 defer 或注册清理函数:

      scss 复制代码
      server.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 服务更新时用户请求零中断,提升生产环境可靠性。测试时建议使用 siegewrk 模拟并发流量验证稳定性。

相关推荐
swordbob2 小时前
CAP 定理:为什么不能同时实现 C、A、P?
开发语言·后端·spring
lazy H3 小时前
Spring Boot 项目如何连接 Redis?新手入门配置和常见错误总结
ide·spring boot·redis·后端·学习·intellij-idea
SXJR3 小时前
spring boot + langchain4j +milvus实现向量存储
java·spring boot·后端·大模型·milvus·rag·langchain4j
王木风3 小时前
Spring Boot + LLM 工程化:把短视频流水线拆成 16 个独立角色的踩坑记录
人工智能·spring boot·后端·开源·新媒体运营·音视频·agent
武子康3 小时前
Java-27 深入浅出 Spring - 实现简易Ioc-03 在上节的业务下手动实现IoC 从 XML 配置到 BeanFactory 反射注入
java·后端·mybatis
月光刺眼3 小时前
Bun + TypeScript 后端入门:从类型约束到 LLM API 调用
后端·typescript
万岳科技3 小时前
教育培训系统开发流程详解:平台建设关键环节解析
数据库·后端·学习
Java编程爱好者3 小时前
服务里的 Redis 锁惊群问题:一次本地合流优化实践
后端
Nturmoils3 小时前
线上修一批脏数据,先别急着全量重来
数据库·后端
飞天狗1113 小时前
零基础JavaWeb入门——第五课第一小节:九大内置对象 · 第1个:request(请求对象)
java·开发语言·前端·后端·servlet