Golang 项目平滑重启

引言

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

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

定义

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

平滑重启

示例代码

package mainimport ("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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/75908.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SQL SELECT DISTINCT 语句详解:精准去重的艺术

在数据驱动的时代&#xff0c;数据质量直接影响决策的准确性。面对海量数据时&#xff0c;重复记录如同沙砾中的金屑&#xff0c;既占用存储空间&#xff0c;又干扰分析结果。SELECT DISTINCT 语句便是那把高效的筛子&#xff0c;助您快速剔除冗余&#xff0c;提取唯一值。本文…

16-产品经理-需求的评审

在创建需求的时候&#xff0c;有一个"不需要评审"的复选框&#xff0c;如果选中该复选框的话&#xff0c;需求的创建成功后状态是激活的。 但大部分情况下面&#xff0c;需求还是需要评审的。 即使产品完全由一个人负责&#xff0c;也可以将一些不成熟的想法存为草…

计算机网络学习前言

前言 该部分说明计算机网络是什么&#xff1f;它有什么作用和功能&#xff1f;值不值得我们去学习&#xff1f;我们该如何学习&#xff1f;这几个部分去大概介绍计算机网络这门课程&#xff0c;往后会介绍计算机网络的具体知识点。 1.计算机网络是什么&#xff1f; 计算机网…

python全栈-JavaScript

python全栈-js 文章目录 js基础变量与常量JavaScript引入到HTML文件中JavaScript注释与常见输出方式 数据类型typeof 显示数据类型算数运算符之加法运算符运算符之算术运算符运算符之赋值运算符运算符之比较运算符运算符之布尔运算符运算符之位运算符运算符优先级类型转换 控制…

C语言一个偶数能表示为两个素数之和

我们可以先找到其中的一个素数&#xff0c;然后用这个偶数减去这个素数就可以求得了。 运行结果:

vue实现大转盘抽奖

用vue实现一个简单的大转盘抽奖案例 大转盘 一 转盘布局 <div class"lucky-wheel-content"><div class"lucky-wheel-prize" :style"wheelStyle" :class"isStart ? animated-icon : "transitionend"onWheelTransitionE…

Docker 核心组件

一、前言 Docker 已成为现代 DevOps 和微服务架构中的核心工具。为了更深入地理解它的工作机制&#xff0c;本文将系统介绍 Docker 的核心组件&#xff0c;配合结构图直观展示架构&#xff0c;同时拓展高级用法&#xff0c;帮助读者全面掌握容器化技术的内核。 二、Docker 核心…

ModuleNotFoundError: No module named ‘pandas‘

在使用Python绘制散点图表的时候&#xff0c;运行程序报错&#xff0c;如图&#xff1a; 报错显示Python 环境中可能没有安装 pandas 库&#xff0c;执行pip list命令查看&#xff0c;果然没有安装pandas 库&#xff0c;如图&#xff1a; 执行命令&#xff1a;python -m pip in…

(51单片机)矩阵按键密码锁表白(C语言代码编撰)(矩阵按键教程)(LCD1602浅教程)

目录 源代码 main.c MatrixKey.c MatrixKey.h LCD1602.c LCD1602.h Delay.c Delay.h 运行效果图&#xff1a; 第一步&#xff1a; 第二步&#xff1a; 第三步&#xff1a; 第四步&#xff1a; 代码解析与教程&#xff1a; 延时函数Delay LCD1602 MatrixKey模块 源代…

检测手机插入USB后,自动启动scrcpy的程序

博主写了一个小工具scrcpyAuto&#xff0c;检测手机插入电脑USB后&#xff0c;自动启动scrcpy。 这样只要程序运行&#xff0c;手机接入主机就会有scrcpy大屏出现&#xff0c;方便了很多。 1、程序会最小化到系统托盘中。 2、博主没有设计得太复杂&#xff0c;所以程序开机启动…

使用Scade实现神经网络算法

在ERTS2022中&#xff0c;ANSYS 发表了使用Scade实现神经网络AI算法的相关工作。论文题目为《Programming Neural Networks Inference in a Safety-Critical Simulation-based Framework》 背景与挑战 神经网络在安全关键系统中的应用&#xff1a;随着嵌入式系统中自主性的引入…

Next.js + SQLite 项目 Docker 生产环境部署方案

以下是完整的 Next.js SQLite 项目 Docker 生产环境部署方案&#xff1a; 1. 项目结构准备 your-project/ ├── prisma/ │ ├── schema.prisma │ └── migrations/ ├── app/ ├── lib/ ├── Dockerfile ├── docker-compose.yml ├── .dockerignore └…

MCU软件开发使用指针有哪些坑?

目录 1、空指针访问 2、野指针&#xff08;未初始化的指针&#xff09; 3、指针越界 4、内存泄漏 5、悬空指针 6、指针类型不匹配 7、多任务环境中的指针访问 8、对齐问题 在MCU软件开发中&#xff0c;使用指针虽然可以提高程序的灵活性和性能&#xff0c;但也存在许多…

【SPSS/EXCEl】主成分分析构建__综合评价指数

学习过程中实验操作的记录 1.数据准备和标准化&#xff1a; (1)区分正负相关性:判断每个因子是正向指标还是负向指标,计算每个的最大值和最小值 (2) 标准化: Min-Max标准化 Min-Max标准化&#xff08;最大最小值法&#xff09;&#xff1a; 将数据映射到指定的区间&#xff…

selenium安装,以及浏览器驱动下载详细步骤

1.下载谷歌浏览器Chromedriver 查看谷歌浏览器版本 2.去官网下载Chromedriver 114之前的版本链接chromedriver.storage.googleapis.com/index.html 选择和浏览器版本较接近的点击进行下载 125之后的版本链接Chrome for Testing availability (googlechromelabs.github.io)&a…

LabVIEW 油井动液面在线监测系统​

项目背景 传统油井动液面测量依赖人工现场操作&#xff0c;面临成本高、效率低、安全风险大等问题。尤其在偏远地区或复杂工况下&#xff0c;测量准确性与时效性难以保障。本系统通过LabVIEW虚拟仪器技术实现硬件与软件深度融合&#xff0c;为油田智能化转型提供实时连续监测解…

C++标准库 —— round 函数用法详解

round 是 C/C 标准库中的一个数学函数&#xff0c;用于对浮点数进行四舍五入取整。以下是它的详细用法说明&#xff1a; 目录 1. 基本语法 2. 功能描述 3. 使用示例 示例1&#xff1a;基本用法 示例2&#xff1a;保留小数位 4. 相关函数对比 5. 注意事项 6. 实际应用场景…

嵌入式C语言11(宏/程序的编译过程)

宏 ⦁ 基本概念 C语言中可以利用宏定义实现文本的快速替换&#xff0c;注意&#xff1a;宏定义是单纯的文本替换&#xff0c;不检查语法是否合法。 C语言标准中提供了很多的预处理指令&#xff0c;比如#include、#pragma…以#开头的都属于预处理指令。 预处理指令指的是在…

【湖南大学】2025我们该如何看待DeepSeek

大家好&#xff0c;我是樱木。 DeepSeek 官方网站&#xff1a;https://www.deepseek.com/ 一、DeepSeek 到底是什么&#xff1f; TA 到底厉害在哪里&#xff1f; 故事从 ChatGPT 说起 去年我们看到 Open AI 发布ChatGPT 后&#xff0c;全球的注意力到了 AI 身上。 我们来拆…

【区块链安全 | 第三十三篇】备忘单

文章目录 备忘单操作符优先级备忘单ABI 编码和解码函数bytes 和 string 的成员Address 的成员区块与交易属性校验和断言数学和加密函数合约相关类型信息函数可见性说明符修饰符备忘单 操作符优先级备忘单 以下是操作符的优先级顺序,按评估顺序列出: 优先级描述操作符1后缀递…