exec go 重启_如何用 Go 实现热重启
热重启
热重启(Zero Downtime),指新老进程无缝切换,在替换过程中可保持对 client 的服务。
原理
父进程监听重启信号
在收到重启信号后,父进程调用 fork ,同时传递 socket 描述符给子进程
子进程接收并监听父进程传递的 socket 描述符
在子进程启动成功之后,父进程停止接收新连接,同时等待旧连接处理完成(或超时)
父进程退出,热重启完成
实现
package main
import (
"context"
"errors"
"flag"
"log"
"net"
"net/http"
"os"
"os/exec"
"os/signal"
"syscall"
"time"
)
var (
server *http.Server
listener net.Listener = nil
graceful = flag.Bool("graceful", false, "listen on fd open 3 (internal use only)")
message = flag.String("message", "Hello World", "message to send")
)
func handler(w http.ResponseWriter, r *http.Request) {
time.Sleep(5 * time.Second)
w.Write([]byte(*message))
}
func main() {
var err error
// 解析参数
flag.Parse()
http.HandleFunc("/test", handler)
server = &http.Server{Addr: ":3000"}
// 设置监听器的监听对象(新建的或已存在的 socket 描述符)
if *graceful {
// 子进程监听父进程传递的 socket 描述符
log.Println("listening on the existing file descriptor 3")
// 子进程的 0, 1, 2 是预留给标准输入、标准输出、错误输出,故传递的 socket 描述符
// 应放在子进程的 3
f := os.NewFile(3, "")
listener, err = net.FileListener(f)
} else {
// 父进程监听新建的 socket 描述符
log.Println("listening on a new file descriptor")
listener, err = net.Listen("tcp", server.Addr)
}
if err != nil {
log.Fatalf("listener error: %v", err)
}
go func() {
err = server.Serve(listener)
log.Printf("server.Serve err: %v\n", err)
}()
// 监听信号
handleSignal()
log.Println("signal end")
}
func handleSignal() {
ch := make(chan os.Signal, 1)
// 监听信号
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR2)
for {
sig :=
log.Printf("signal receive: %v\n", sig)
ctx, _ := context.WithTimeout(context.Background(), 20*time.Second)
switch sig {
case syscall.SIGINT, syscall.SIGTERM: // 终止进程执行
log.Println("shutdown")
signal.Stop(ch)
server.Shutdown(ctx)
log.Println("graceful shutdown")
return
case syscall.SIGUSR2: // 进程热重启
log.Println("reload")
err := reload() // 执行热重启函数
if err != nil {
log.Fatalf("graceful reload error: %v", err)
}
server.Shutdown(ctx)
log.Println("graceful reload")
return
}
}
}
func reload() error {
tl, ok := listener.(*net.TCPListener)
if !ok {
return errors.New("listener is not tcp listener")
}
// 获取 socket 描述符
f, err := tl.File()
if err != nil {
return err
}
// 设置传递给子进程的参数(包含 socket 描述符)
args := []string{"-graceful"}
cmd := exec.Command(os.Args[0], args...)
cmd.Stdout = os.Stdout // 标准输出
cmd.Stderr = os.Stderr // 错误输出
cmd.ExtraFiles = []*os.File{f} // 文件描述符
// 新建并执行子进程
return cmd.Start()
}
我们在父进程执行 cmd.ExtraFiles = []*os.File{f} 来传递 socket 描述符给子进程,子进程通过执行 f := os.NewFile(3, "") 来获取该描述符。值得注意的是,子进程的 0 、1 和 2 分别预留给标准输入、标准输出和错误输出,所以父进程传递的 socket 描述符在子进程的顺序是从 3 开始。
测试
编译上述程序为 main ,执行 ./main -message "Graceful Reload" ,访问 http://localhost:3000/test ,等待 5 秒后,我们可以看到 Graceful Reload 的响应。
通过执行 kill -USR2 [PID] ,我们即可进行 Graceful Reload 的测试。
通过执行 kill -INT [PID] ,我们即可进行 Graceful Shutdown 的测试。
参考资料
有疑问加站长微信联系(非本文作者)
exec go 重启_如何用 Go 实现热重启相关推荐
- 无限重启_三星蓝光播放器出现无限自动重启BUG,涉及不少用户及不同型号
三星的蓝光播放器似乎遇到了一个挺严重的BUG,使得不少用户都开机后播放器会自动不停重启. 从reddit.ZDNet以及三星技术支持论坛上面的情况来看,这次的问题波及不同型号的播放器,大部分用户遇到的 ...
- exec go 重启_[译]Golang中的优雅重启
声明:本文目的仅仅作为个人mark,所以在翻译的过程中参杂了自己的思想甚至改变了部分内容,其中有下划线的文字为译者添加.但由于译者水平有限,所写文字或者代码可能会误导读者,如发现文章有问题,请尽快告知 ...
- linux 重启_四步见证linux系统重启过程,小心操作,防止后悔!
linux小白到大神的成长之路:四步见证linux系统重启过程,小心操作,防止后悔! 本经验由宗龙龙原创,全文共880多字,阅读需要14分钟! 记得上篇文章给大家讲述linux系统的重启与关机操作,但 ...
- 导致集群重启_园区网核心交换机S7706异常重启导致无线网络故障
问题现象 园区网核心交换机S7706设备异常重启,重启完成后其中一个无线信号故障,其它无线信号正常. 问题分析 1.问题现象分析 检查交换机上的重启时间点记录如下: 从该记录来看,重启原因是由于交换机 ...
- origin做相关性分析图_如何用Origin绘制热图?
常见的绘制热图的方法有很多,如可用R包,OmicSare tools的热图工具,Heml等绘制.那么常规的科研作图软件 Origin 能不能绘制热图呢?今天就用Origin尝试下绘制热图. 数据准备 ...
- Go 如何实现热重启
作者:zhijiezhang,腾讯 PCG 后台开发工程师 最近在优化公司框架 trpc 时发现了一个热重启相关的问题,优化之余也总结沉淀下,对 go 如何实现热重启这方面的内容做一个简单的梳理. 1 ...
- 服务器重启oracle数据库服务器,oracle数据库怎么重启_网站服务器运行维护,oracle,数据库,重启...
linux操作系统好学吗_网站服务器运行维护 学习大多类似鹿丁解牛,对事物的认识一般都是由浅入深.由表及里的过程,循序才能渐进.学习Linux同样要有一定的顺序和方法,这样学起来就不会感觉到难了. o ...
- 通过服务重启oracle数据库,oracle数据库怎么重启_网站服务器运行维护
linux操作系统好学吗_网站服务器运行维护 学习大多类似鹿丁解牛,对事物的认识一般都是由浅入深.由表及里的过程,循序才能渐进.学习Linux同样要有一定的顺序和方法,这样学起来就不会感觉到难了. o ...
- mysql数据库服务器重启_重启mysql数据库服务器
Mysql错误代码大全 1016错误:文件无法打开,使用后台修复或者使用phpmyadmin进行修复. 1044错误:数据库用户权限不足,请联系空间商解决 1045错误:数据库服务器/数据库用户名/数 ...
最新文章
- 程序员毕业两年,三年工作经验是怎么来的? | 每日趣闻
- cocos2dx 运动+旋转动画 CCSequence CCAnimation CCAnimate CCMoveTo CCCallFuncN
- 怎么用linux的HDD存储,Linux学习的正确姿势12:Linux存储概览
- 潘越云《面朝海子》:诗里的人都会终成眷属
- ERP与SCM之区别
- MFC初步教程(三):菜单
- tensorflow精进之路(二十四)——Object Detection API目标检测(中)(COCO数据集训练的模型—ssd_mobilenet_v1_coco模型)
- latex \textsuperscript{\dagger} 报错
- Intel 的 micro-architecture 发展历程
- python实时语音转写_使用实时语音转写_语音交互服务 SIS_SDK参考_Python SDK_华为云...
- 贵州:科技创新促高质量发展
- TFLite Interpreter
- Linux移动光标指令hkjl,使用 HPC Pack 在 Linux VM 上執行 OpenFOAM - Azure Virtual Machines | Microsoft Docs...
- 计算机配置高低怎么看,电脑配置的高低怎么查看
- 网站加入滚动字幕或公告说明
- 谷歌云| 5 个 GKE 功能可帮助您优化集群
- datanode无法启动Block pool ID needed, but service not yet registered with NN
- 为什么选择Tomcat及Tomcat下载
- 剑指offer第62题 圆圈中最后剩下的数字(约瑟夫问题)
- java处理中文字符串_java中文字符串处理方法
热门文章
- bfs+优先队列(hdu1242)
- 关于ORA-01187: cannot read from file because it failed verification tests 的处理方法
- linux 调优 网络调优
- 32位单精度浮点乘法器的FPGA实现
- MPLS-L3×××中的公网访问
- 如何使编译的EXE程序能多个运行?
- python xml转换键值对_Python 提取dict转换为xml/json/table并输出
- Mybatis源码阅读(二):动态节点解析2.2 —— SqlSourceBuilder与三种SqlSource
- vue设置输入框输入长度_Vue实现input宽度随文字长度自适应操作
- excel切片器_如何在Excel表格中使用切片器