热重启

热重启(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 := <-ch
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 的测试。

参考资料

如何用 Go 实现热重启的更多相关文章

  1. Golang服务器热重启、热升级、热更新(safe and graceful hot-restart/reload http server)详解

    服务端代码经常需要升级,对于线上系统的升级常用的做法是,通过前端的负载均衡(如nginx)来保证升级时至少有一个服务可用,依次(灰度)升级. 而另一种更方便的方法是在应用上做热重启,直接更新源码.配置 ...

  2. prometheus热重启

    prometheus启动命令添加参数 --web.enable-lifecycle 然后热重启:curl -XPOST http://localhost:9090/-/reload

  3. [4G]4G模块的热重启

    最近在调试4G模块,发现在开机启动时执行的AT指令会概率性的出现返回杂乱字符串的问题.想尽了各种办法还是行不通,在系统中使用minicom敲AT指令就不会有问题,开始怀疑是串口初始化的问题,修改了很多 ...

  4. Go 如何实现热重启

    https://mp.weixin.qq.com/s/UVZKFmv8p4ghm8ICdz85wQ Go 如何实现热重启 原创 zhijiezhang 腾讯技术工程 2020-09-09  

  5. 说一下我认识的*nix下的服务器热重启

    步骤: 第一: 收到SIGTERM以后现在的服务器监听socket停止accept 但是并没有停止listen,这个很关键.(所以客户端发起的tcp连接的syn得不到synack,只是继续等待,而不会 ...

  6. Windows7添加SSD硬盘后重启卡住正在启动

    楼主办公电脑,原来只配置了一块机械硬盘,用着总很不顺心,于是说服领导给加了块SSD固态硬盘. 操作如下: 1.在PE下分区格式化新固态硬盘,将原来机械硬盘的C盘GHOST备份后还原到新固态硬盘: 2. ...

  7. Golang学习--平滑重启

    在上一篇博客介绍TOML配置的时候,讲到了通过信号通知重载配置.我们在这一篇中介绍下如何的平滑重启server. 与重载配置相同的是我们也需要通过信号来通知server重启,但关键在于平滑重启,如果只 ...

  8. Windows 7 添加SSD硬盘后重启卡住正在启动

    楼主办公电脑,原来只配置了一块机械硬盘,用着总很不顺心,于是说服领导给加了块SSD固态硬盘. 操作如下: 1.在PE下分区格式化新固态硬盘,将原来机械硬盘的C盘GHOST备份后还原到新固态硬盘: 2. ...

  9. SpringBoot 在IDEA中实现热部署(实用版)(引入)

    SpringBoot 在IDEA中实现热部署(实用版) 引用:https://www.jianshu.com/p/f658fed35786 好的热部署让开发调试事半功倍,这样的“神技能”怎么能错过呢, ...

随机推荐

  1. Django 之restfromwork 源码分析以及使用之--视图组件

    restframework 源码分析以及使用 mixins 中的五种类方法 from rest_framework import mixins # mixins 中一种有五种类 # 第一种:用户保存数 ...

  2. ElasticSearch(十三):Spring Data ElasticSearch 的使用(三)——NativeSearchQuery 高亮查询

    在Elasticsearch的实际应用中,经常需要将匹配到的结果字符进行高亮显示,此处采取NativeSearchQuery原生查询的方法,实现查询结果的高亮显示. /** * 高亮查询 */ @Te ...

  3. geany写C语言,printf打印中文时显示乱码

    原因 geany设置了编码格式为utf8 运行时显示出的cmd窗口编码格式为GBK 解决方法 要么修改geany编码,要么修改cmd窗口编码. 修改geany编辑器为GBK编码格式 编辑=>首选 ...

  4. excel打开提示 文件格式和扩展名不匹配。文件可能已损坏或不安全。除非您信任其来源,否则请勿打开。是否仍要打开它?

    有的时候打开xls文档时,会提示“文件格式和扩展名不匹配.文件可能已损坏或不安全.除非您信任其来源,否则请勿打开.是否仍要打开它?” 遇到这种情况,我们需要 打开“注册表编辑器” win键+R键,打开 ...

  5. 前端性能----从输入URL开始到返回数据的中间经历过程

    这是一个古老的问题,即我们输入URL后按下回车到网页测呈现都发生了什么? 首先来看一张图: 表示了数据从源端到目的端的封装和拆解过程 预处理URL URL格式:  以http为例: http://ww ...

  6. 数组的filter方法处理数组内对象元素时,会改变原值

    arr = [{n:11},{n:22},{n:33}] arr.filter(v=>v.n=8) console.log(arr)  //  [{n:8},{n:8},{n:8}]

  7. mysql导出PDM表结构并带有注释

    分享链接:https://www.cnblogs.com/dennyzhangdd/p/5550868.html

  8. 准确率(Accuracy) 精确率(Precision) 与 召回率(Recall)

    准确率(Accuracy)—— 针对整个模型 精确率(Precision) 灵敏度(Sensitivity):就是召回率(Recall) 参考:https://blog.csdn.net/Orange ...

  9. Spring核心模块解析

    Spring框架是一个轻量级的集成式开发框架,可以和任何一种框架集成在一起使用,可以说是一个大的全家桶.Spring从1.x发展到现在的5.x可以说是越来越强大,下面来看看Spring都包含哪些核心的 ...

  10. XXE(外部实体注入攻击)

    利用XXE漏洞可以进行拒绝服务攻击.文件读取.命令代码执行.SQL(XSS)注入.内外扫描端口和入侵内网站点等,内网探测和入侵是利用XXE中支持的协议进行内网主机和端口的发现,可以理解为使用XXE进行 ...