当前位置: 首页 > news >正文

Golang 项目平滑重启

引言

平滑重启(Graceful Restart)技术作为一种常用的解决方案,通过允许新进程接管而不中断现有的请求,确保了系统的稳定运行和业务连续性。同时目前公司的服务重启绝大部分也都适用的 go 的平滑重启技术。

本部分将对平滑重启的概念、应用场景以及实现方式进行详细的介绍,帮助开发者理解如何在实际应用中实现平滑重启,保障服务的高可用性。

定义

  • 平滑重启:平滑重启是指在不中断现有服务的前提下,使用新进程替代现有进程的操作。具体来说,平滑重启包括以下步骤:
    • 启动新的进程(通常是同一个服务的升级版)。
    • 新进程接管当前的请求和连接。
    • 旧进程完成当前任务后,优雅地退出,避免未处理的请求丢失。
  • 重启信号:在类 Unix 操作系统中,信号是用来通知进程发生某些事件的一种机制。常用的重启信号包括:
    • SIGHUP:通常用于通知进程重新加载配置或进行平滑重启。在很多应用中,SIGHUP 被用来触发进程的平滑重启。
    • SIGTERM:表示请求程序终止进程,通常由操作系统或用户发起,用于平滑关闭进程。
    • SIGINT:通常是用户在终端输入 Ctrl+C 时发送的信号,用于终止进程。
  • **PID 文件:**PID 文件是用来存储正在运行的进程的进程 ID(PID)的文件。在平滑重启中,PID 文件非常重要,它允许新进程在启动时找到并与旧进程进行交互。新进程通常会读取 PID 文件,获取旧进程的 PID,并通过发送信号来请求旧进程退出。

平滑重启

示例代码

package main

import (
    "fmt"
    "net/http"
    "os"
    "os/signal"
    "syscall"

    "github.com/cloudflare/tableflip"
)

// 首页 handler
func homeHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/plain")
    fmt.Fprintln(w, "Welcome to the Home Page!")
}

func main() {
    // 创建 tableflip 管理器
    upg, err := tableflip.New(tableflip.Options{PIDFile: "/Users/wepie/Downloads/testGin.pid"})
    if err != nil {
       fmt.Printf("Error creating tableflip manager: %v\n", err)
       os.Exit(1)
    }
    defer upg.Stop()

    // 捕获 SIGHUP 信号并触发升级
    go func() {
       sig := make(chan os.Signal, 1)
       signal.Notify(sig, syscall.SIGHUP)
       for range sig {
          if err := upg.Upgrade(); err != nil {
             fmt.Printf("Error during upgrade: %v\n", err)
          } else {
             fmt.Println("Upgrade triggered: New process started!")
          }
       }
    }()

    // 创建一个新的 ServeMux 来管理多个路由
    mux := http.NewServeMux()
    mux.HandleFunc("/", homeHandler)           // 首页

    // 监听端口并启动 HTTP 服务
    ln, err := upg.Listen("tcp", "localhost:8081")
    if err != nil {
       fmt.Printf("Error starting listener: %v\n", err)
       os.Exit(1)
    }
    defer ln.Close()

    // 启动 HTTP 服务
    go func() {
       if err := http.Serve(ln, mux); err != nil {
          fmt.Printf("HTTP server error: %v\n", err)
       }
    }()

    // 等待进程准备好
    if err := upg.Ready(); err != nil {
       fmt.Printf("Error marking process as ready: %v\n", err)
       os.Exit(1)
    }
    fmt.Println("服务启动完成 pid: ", os.Getpid())

    // 等待退出信号
    <-upg.Exit()
    fmt.Println("服务退出")
}

上述是一个简单的平滑重启的案例,有想试验的同学可以直接用这段代码实现。

在命令行 kill -SIGHUP 操作该进程(PID 可以通过定义的 PID 文件位置查阅),可以看到最终的平滑重启

过程分析

创建 tableflip 管理器 (upg,upgrader)

upg, err := tableflip.New(tableflip.Options{PIDFile: "/Users/wepie/Downloads/testGin.pid"})
if err != nil {
    fmt.Printf("Error creating tableflip manager: %v\n", err)
    os.Exit(1)
}
defer upg.Stop()

做了什么?

  • 创建了一个 tableflip 管理器 upg,它负责控制进程的平滑重启。
  • 使用 tableflip.Options 配置选项来指定 PID 文件,它会记录当前进程的 PID,通常用于在后续重启中找到进程。
  • 如果 tableflip.New 返回错误,程序会打印错误信息并退出。
  • 使用 defer 确保当程序退出时,调用 upg.Stop() 来停止管理器,释放资源。

完成了什么?

  • 管理器 upg 被创建并准备好,后续的重启操作将通过它来进行。

信号捕获和进程升级

go func() {
    sig := make(chan os.Signal, 1)
    signal.Notify(sig, syscall.SIGHUP)
    for range sig {
        if err := upg.Upgrade(); err != nil {
            fmt.Printf("Error during upgrade: %v\n", err)
        } else {
            fmt.Println("Upgrade triggered: New process started!")
        }
    }
}()

做了什么?

  • 启动了一个新的 goroutine,负责监听 SIGHUP 信号(通常用于平滑重启)。当该信号到达时,触发进程的重启。
  • signal.Notify(sig, syscall.SIGHUP) 告诉程序捕获 SIGHUP 信号,并将其放入 sig 通道。(任何信号都可以作为放入通道的内容,取决于怎么设计)
  • 当 sig 通道接收到 SIGHUP 信号时,程序通过 upg.Upgrade() 来触发进程重启。
  • Upgrade() 会启动一个新的进程,并优雅地停止旧进程。Upgrade 方法会启动一个新的进程,新进程会从程序的入口点(即 main() 函数)重新开始执行。
  • 旧进程在调用 upg.Upgrade() 后并不会立即退出。它会继续运行,处理现有的请求,直到新进程完全准备就绪

完成了什么?

  • 新的进程会在接收到 SIGHUP 信号时被启动,而旧进程会在新的进程启动后退出,完成平滑重启。

创建 HTTP 路由和处理函数

这里只是此处服务的示例,期间想加任何其他逻辑都是可行的

mux := http.NewServeMux()
mux.HandleFunc("/", homeHandler)  // 首页

做了什么?

  • 创建了一个新的 HTTP 路由器 mux,并注册了 / 路径的处理函数 homeHandler,它会响应根路径的请求。

完成了什么?

  • 创建了基本的 HTTP 路由和处理器,为后续的服务启动做准备。

启动 HTTP 服务并监听端口

ln, err := upg.Listen("tcp", "localhost:8081")
if err != nil {
    fmt.Printf("Error starting listener: %v\n", err)
    os.Exit(1)
}
defer ln.Close()

做了什么?

  • 使用 upg.Listen() 方法来启动一个 TCP 监听器,监听 localhost:8081 端口。
  • upg.Listen() 会创建一个 listener,并确保即使进程重启,新的进程会继续监听该端口,确保平滑重启后服务不中断。
  • 如果发生错误,程序会打印错误并退出。
  • 使用 defer 来确保程序退出时关闭监听器,避免资源泄漏。

完成了什么?

  • 监听 localhost:8081 端口,并准备好接收 HTTP 请求。

启动 HTTP 服务的 goroutine

go func() {
    if err := http.Serve(ln, mux); err != nil {
        fmt.Printf("HTTP server error: %v\n", err)
    }
}()

做了什么?

  • 在一个新的 goroutine 中启动 HTTP 服务。http.Serve() 使用前面创建的 ln(TCP 监听器)和 mux(路由器)来启动 HTTP 服务。
  • Serve() 会持续运行,直到出现错误或者服务器被停止。

完成了什么?

  • 启动了一个 HTTP 服务器,监听 localhost:8081 端口并处理请求。

等待新进程准备

if err := upg.Ready(); err != nil {
    fmt.Printf("Error marking process as ready: %v\n", err)
    os.Exit(1)
}
fmt.Println("服务启动完成 pid: ", os.Getpid())

做了什么?

  • 调用 upg.Ready(),告诉 tableflip 管理器进程已经准备好,可以开始接受请求。
  • 如果 Ready() 返回错误,程序会打印错误并退出。

完成了什么?

  • 程序向 tableflip 发出通知,表明服务已经启动并准备好接收请求。此时会通知旧进程在完成当前其他任务后关闭。

老进程等待退出信号

<-upg.Exit()
fmt.Println("服务退出")

做了什么?

  • upg.Exit() 返回一个只读通道,<-upg.Exit() 会阻塞,直到进程退出信号到来。
  • 进程会一直等待,直到 tableflip 通知进程可以退出(即新进程启动并完成任务,会在 ready 后通过进程间通信找到旧进程的 PID,发送系统信号通知它退出)。
  • 当接收到退出信号时,程序会继续执行并打印 “服务退出”。

完成了什么?

  • 进程阻塞在这里,等待退出信号。upg.Exit() 会在新进程启动后通过 tableflip 触发进程退出。

总结

平滑重启适用于需要精确控制进程重启时机、避免中断服务的场景。相比容器化环境的重启机制,平滑重启提供了更高的控制性和灵活性。在 CICD 流程中,也是一个不错的选择。

引用

https://github.com/cloudflare/tableflip

相关文章:

  • Python小练习系列 Vol.13:用装饰器记录函数执行时间
  • 蓝桥杯2024年第十五届省赛真题-爬山
  • 刺杀大使--bfs还是比dfs快+二分
  • 专题|MATLAB-R语言Logistic逻辑回归增长模型在互联网金融共生及移动通信客户流失分析实例合集
  • 基于Python Flask快速构建网络安全工具资源库的Web应用实践
  • 【简历全景认知2】电子化时代对简历形式的降维打击:从A4纸到ATS的生存游戏
  • Python高阶函数-filter
  • es 原生linux部署集群
  • JS 中html的document
  • MySQL学习笔记六
  • Python爬虫生成CSV文件的完整流程
  • 谷歌洽谈租赁英伟达AI服务器:算力争夺战再升级
  • 过剩与稀缺:现代社会的思考与启示
  • 信息系统项目管理师-第十一章-项目成本管理
  • R语言:气象水文领域的数据分析与绘图利器
  • 属性修改器 (AttributeModifier)
  • 2024年已备案大模型发展趋势分析
  • spring boot + Prometheus + Grafana 实现项目监控
  • 2️⃣ Coze创建智能体教学(2025年全新版本)
  • 探索轻量高性能的 Rust HTTP 服务器框架 —— Hyperlane
  • 7天6板南京港:控股子公司没有直达美国外贸集装箱直达航线
  • 上海黄浦推动建设金融科技集聚区,对创新主体最高扶持1亿元
  • 济南维尔康:公司上届管理层个别人员拒不离岗,致多项业务难以推进
  • 巴西商业农场首次确诊高致病性禽流感,中国欧盟暂停进口巴西禽肉产品
  • AI创业者聊大模型应用趋势:可用性和用户需求是关键
  • 沧州盐碱地“逆天改命”:无用之地变良田,候鸟翔集水草丰美