热重启

热重启(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 实现热重启相关推荐

  1. 无限重启_三星蓝光播放器出现无限自动重启BUG,涉及不少用户及不同型号

    三星的蓝光播放器似乎遇到了一个挺严重的BUG,使得不少用户都开机后播放器会自动不停重启. 从reddit.ZDNet以及三星技术支持论坛上面的情况来看,这次的问题波及不同型号的播放器,大部分用户遇到的 ...

  2. exec go 重启_[译]Golang中的优雅重启

    声明:本文目的仅仅作为个人mark,所以在翻译的过程中参杂了自己的思想甚至改变了部分内容,其中有下划线的文字为译者添加.但由于译者水平有限,所写文字或者代码可能会误导读者,如发现文章有问题,请尽快告知 ...

  3. linux 重启_四步见证linux系统重启过程,小心操作,防止后悔!

    linux小白到大神的成长之路:四步见证linux系统重启过程,小心操作,防止后悔! 本经验由宗龙龙原创,全文共880多字,阅读需要14分钟! 记得上篇文章给大家讲述linux系统的重启与关机操作,但 ...

  4. 导致集群重启_园区网核心交换机S7706异常重启导致无线网络故障

    问题现象 园区网核心交换机S7706设备异常重启,重启完成后其中一个无线信号故障,其它无线信号正常. 问题分析 1.问题现象分析 检查交换机上的重启时间点记录如下: 从该记录来看,重启原因是由于交换机 ...

  5. origin做相关性分析图_如何用Origin绘制热图?

    常见的绘制热图的方法有很多,如可用R包,OmicSare tools的热图工具,Heml等绘制.那么常规的科研作图软件 Origin 能不能绘制热图呢?今天就用Origin尝试下绘制热图. 数据准备 ...

  6. Go 如何实现热重启

    作者:zhijiezhang,腾讯 PCG 后台开发工程师 最近在优化公司框架 trpc 时发现了一个热重启相关的问题,优化之余也总结沉淀下,对 go 如何实现热重启这方面的内容做一个简单的梳理. 1 ...

  7. 服务器重启oracle数据库服务器,oracle数据库怎么重启_网站服务器运行维护,oracle,数据库,重启...

    linux操作系统好学吗_网站服务器运行维护 学习大多类似鹿丁解牛,对事物的认识一般都是由浅入深.由表及里的过程,循序才能渐进.学习Linux同样要有一定的顺序和方法,这样学起来就不会感觉到难了. o ...

  8. 通过服务重启oracle数据库,oracle数据库怎么重启_网站服务器运行维护

    linux操作系统好学吗_网站服务器运行维护 学习大多类似鹿丁解牛,对事物的认识一般都是由浅入深.由表及里的过程,循序才能渐进.学习Linux同样要有一定的顺序和方法,这样学起来就不会感觉到难了. o ...

  9. mysql数据库服务器重启_重启mysql数据库服务器

    Mysql错误代码大全 1016错误:文件无法打开,使用后台修复或者使用phpmyadmin进行修复. 1044错误:数据库用户权限不足,请联系空间商解决 1045错误:数据库服务器/数据库用户名/数 ...

最新文章

  1. 程序员毕业两年,三年工作经验是怎么来的? | 每日趣闻
  2. cocos2dx 运动+旋转动画 CCSequence CCAnimation CCAnimate CCMoveTo CCCallFuncN
  3. 怎么用linux的HDD存储,Linux学习的正确姿势12:Linux存储概览
  4. 潘越云《面朝海子》:诗里的人都会终成眷属
  5. ERP与SCM之区别
  6. MFC初步教程(三):菜单
  7. tensorflow精进之路(二十四)——Object Detection API目标检测(中)(COCO数据集训练的模型—ssd_mobilenet_v1_coco模型)
  8. latex \textsuperscript{\dagger} 报错
  9. Intel 的 micro-architecture 发展历程
  10. python实时语音转写_使用实时语音转写_语音交互服务 SIS_SDK参考_Python SDK_华为云...
  11. 贵州:科技创新促高质量发展
  12. TFLite Interpreter
  13. Linux移动光标指令hkjl,使用 HPC Pack 在 Linux VM 上執行 OpenFOAM - Azure Virtual Machines | Microsoft Docs...
  14. 计算机配置高低怎么看,电脑配置的高低怎么查看
  15. 网站加入滚动字幕或公告说明
  16. 谷歌云| 5 个 GKE 功能可帮助您优化集群
  17. datanode无法启动Block pool ID needed, but service not yet registered with NN
  18. 为什么选择Tomcat及Tomcat下载
  19. 剑指offer第62题 圆圈中最后剩下的数字(约瑟夫问题)
  20. java处理中文字符串_java中文字符串处理方法

热门文章

  1. bfs+优先队列(hdu1242)
  2. 关于ORA-01187: cannot read from file because it failed verification tests 的处理方法
  3. linux 调优 网络调优
  4. 32位单精度浮点乘法器的FPGA实现
  5. MPLS-L3×××中的公网访问
  6. 如何使编译的EXE程序能多个运行?
  7. python xml转换键值对_Python 提取dict转换为xml/json/table并输出
  8. Mybatis源码阅读(二):动态节点解析2.2 —— SqlSourceBuilder与三种SqlSource
  9. vue设置输入框输入长度_Vue实现input宽度随文字长度自适应操作
  10. excel切片器_如何在Excel表格中使用切片器