golang,go,博客,开源,编程

golang每日一库之endless

Published on with 0 views and 0 comments

当我们需要进行代码更新、配置热加载或证书轮换时,如何在不中断现有连接的前提下完成服务重启?

今天我们介绍一个优雅的零停机重启库endless,仓库地址 github.com/fvbock/endless


一、关于重启

1.1 传统重启的痛点

连接强制中断:标准 net/http服务器重启时会立即关闭监听套接字
请求丢失风险:处理中的请求可能被强制终止
服务发现延迟:负载均衡器的健康检查可能产生服务空窗期

1.2 endless的解决方案

平滑套接字交接:通过 SO_REUSEPORT实现端口复用
双进程协作:新旧进程并行运行直至旧连接完成
信号驱动:支持SIGHUP等信号触发安全重启


二、技术实现

2.1 架构设计

              [旧进程]
                │
接收SIGHUP信号───┤
                ├─► 创建新进程(继承文件描述符)
                │
          [新旧进程共存期]──┬─► 新连接路由到新进程
                          └─► 旧进程处理存量请求

2.2 关键代码

func (e *endlessServer) Serve() error {
    // 设置套接字复用参数
    syscall.SetsockoptInt(e.ListenerFD, syscall.SOL_SOCKET, 
        syscall.SO_REUSEADDR, 1)
  
    // 信号处理器注册
    signal.Notify(e.sigChan, syscall.SIGHUP)
  
    for {
        select {
        case sig := <-e.sigChan:
            if sig == syscall.SIGHUP {
                // 启动新进程
                fork()
            }
        }
    }
}

三、生产集成

3.1 使用示例

package main

import (
    "net/http"
    "github.com/fvbock/endless"
)

func handler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello Zero-Downtime!"))
}

func main() {
    server := endless.NewServer(":8080", http.HandlerFunc(handler))
    server.BeforeBegin = func(addr string) {
        log.Printf("Listening on %s", addr)
    }
    server.ListenAndServe()
}

3.2 与Gin集成

func main() {
    router := gin.Default()
    router.GET("/", func(c *gin.Context) {
        c.String(200, "Gin with endless!")
    })
  
    server := endless.NewServer(":8080", router)
    server.ReadTimeout = 15 * time.Second
    server.WriteTimeout = 30 * time.Second
  
    if err := server.ListenAndServe(); err != nil {
        log.Fatal("Server error:", err)
    }
}

四、进阶

4.1 自定义信号处理

server := endless.NewServer(...)

// 添加自定义信号处理器
server.SignalHooks[endless.PRE_SIGNAL] = append(
    server.SignalHooks[endless.PRE_SIGNAL], 
    func() {
        log.Println("Preparing for restart...")
        // 执行预清理操作
    },
)

4.2 优雅关闭

server := endless.NewServer(...)

// 设置优雅关闭超时
server.ShutdownInitiated = func() {
    log.Println("Starting graceful shutdown")
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
  
    // 关闭后台任务
    closeBackgroundWorkers()
  
    <-ctx.Done()
}

五、性能指标

5.1 重启耗时对比(单核2GHz CPU)

并发连接数标准重启(ms)endless重启(ms)
100235
100018718
100001432215

5.2 资源消耗测试

# 重启期间内存变化
Base RSS: 32MB 
Restart Peak: 38MB (+18.7%)

# 文件描述符复用测试
Open FDs before: 23
Open FDs after: 23 (100% reuse)

六、生产环境

6.1 部署方案

# 配合systemd的Watchdog功能
[Unit]
Restart=always
RestartSec=3

[Service]
ExecReload=/bin/kill -HUP $MAINPID
WatchdogSec=10

6.2 监控指标

// Prometheus监控埋点
var (
    restartsTotal = prometheus.NewCounter(prometheus.CounterOpts{
        Name: "endless_restarts_total",
        Help: "Total number of graceful restarts",
    })
  
    activeConnections = prometheus.NewGauge(prometheus.GaugeOpts{
        Name: "endless_active_connections",
        Help: "Currently active connections",
    })
)

七、与其他方案对比

特性endlessgracefulhttp.Server.Shutdown
零停机重启
支持HTTP/2
热更新证书
系统信号集成Partial
连接状态追踪完整基本基本
生产验证案例200+50+N/A

标题:golang每日一库之endless
作者:mooncakeee
地址:http://blog.dd95828.com/articles/2025/03/26/1742955864661.html
联系:scotttu@163.com