golang的httpserver优雅重启方法详解
作者:helight 发布时间:2023-08-06 00:22:41
前言
去年在做golangserver的时候,内部比较头疼的就是在线服务发布的时候,大量用户的请求在发布时候会被重连,在那时候也想了n多的方法,最后还是落在一个github上的项目,facebook的一个golang项目grace,那时候简单研究测试了一下可以就直接在内部使用了起来,这段时间突然想起来,又想仔细研究一下这个项目了。
从原理上来说是这样一个过程:
1)发布新的bin文件去覆盖老的bin文件
2)发送一个信号量,告诉正在运行的进程,进行重启
3)正在运行的进程收到信号后,会以子进程的方式启动新的bin文件
4)新进程接受新请求,并处理
5)老进程不再接受请求,但是要等正在处理的请求处理完成,所有在处理的请求处理完之后,便自动退出
6)新进程在老进程退出之后,由init进程收养,但是会继续服务。
所以一步一步来看,关键是从第2步开始之后怎么做,所以我们先来看看第2步的实现,这个应该说很简单,发送信号量到一个进程,使用kill命令即可,在facebook这个项目中发送的信号量有3个:SIGINT,SIGTERM,SIGUSR2,前面两个信号收到后程序会直接退出,后面一个信号SIGUSR2才会执行所谓的优雅重启。
第3步,正在运行的进程收到SIGUSR2信号后,会以子进程的方式启动新的bin文件。先直接上代码看:https://github.com/facebookgo/grace/blob/master/gracehttp/http.go
func (a *app) signalHandler(wg *sync.WaitGroup) {
ch := make(chan os.Signal, 10)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR2)
for {
sig := <-ch
switch sig {
case syscall.SIGINT, syscall.SIGTERM:
// this ensures a subsequent INT/TERM will trigger standard go behaviour of
// terminating. 执行标准的go终止行为,程序就结束了
signal.Stop(ch)
a.term(wg)
return
case syscall.SIGUSR2: // 这里开始执行优雅重启
err := a.preStartProcess()
// 这个函数在源代码中没有具体实现功能,只是预留了一个钩子函数,用户可以注册自己的函数,可以在重启之前做些自定义的事情。一般情况下也没有什么可以做的,除非有些特殊的服务环境或是状态保存之类的,至少目前,我们的server还没有遇到
if err != nil {
a.errors <- err
}
// we only return here if there's an error, otherwise the new process
// will send us a TERM when it's ready to trigger the actual shutdown.
if _, err := a.net.StartProcess(); err != nil { // 这里开始正式所谓的优雅重启
a.errors <- err
}
}
}
}
a.net.StartProcess的过程我们来看看基本过程:
func (n *Net) StartProcess() (int, error) {
listeners, err := n.activeListeners() // 获取目前在监听的端口,这块也是重点,下面重点介绍
if err != nil {
return 0, err
}
// Extract the fds from the listeners. 从监听端口中把文件描述符取出来
files := make([]*os.File, len(listeners))
for i, l := range listeners {
files[i], err = l.(filer).File()
if err != nil {
return 0, err
}
defer files[i].Close()
}
// Use the original binary location. This works with symlinks such that if
// the file it points to has been changed we will use the updated symlink.
// 获取可执行bin文件的路劲,也可以是链接路劲,会使用最新的链接路径作为启动文件路劲的
argv0, err := exec.LookPath(os.Args[0])
if err != nil {
return 0, err
}
// Pass on the environment and replace the old count key with the new one.
// 获取 LISTEN_FDS 换进变量值
var env []string
for _, v := range os.Environ() {
if !strings.HasPrefix(v, envCountKeyPrefix) {
env = append(env, v)
}
}
env = append(env, fmt.Sprintf("%s%d", envCountKeyPrefix, len(listeners)))
allFiles := append([]*os.File{os.Stdin, os.Stdout, os.Stderr}, files...)
// 这里调用一个golang底层的进程启动函数,来指定,上面获取的参数来启动进程
process, err := os.StartProcess(argv0, os.Args, &os.ProcAttr{
Dir: originalWD,
Env: env,
Files: allFiles,
})
if err != nil {
return 0, err
}
// 返回新进程id。
return process.Pid, nil
}
以上是启动新进程,并且接管监听端口的过程, 一般情况下端口是不可以重复监听的,所以这里就要需要使用比较特别的办法,从上面的代码来看就是读取监听端口的文件描述符,并且把监听端口的文件描述符传递给子进程,子进程里从这个文件描述符实现对端口的监听
另外还有一个比较特别的地方就是老的接口怎么关闭的问题,关闭必须要把已经收到的请求处理完成之后再关闭。为此facebook的同学另外开了一个项目httpdown,继承了原始的httpserver,但是多了对各种链接状态的维护和处理,这部分后面在分析。
来源:http://helight.info/2018-01-24/golang的httpserver优雅重启/


猜你喜欢
- 创建一个优秀的可视化图表的关键在于引导读者,让他们能理解图表所讲述的故事。在一些情况下,这个故事可以通过纯图像的方式表达,不需要额外添加文字
- pip是一个很好用的第三方库安装方式,但是默认的源没法连接,就算有时候可以成功率也很低,所以换成国内镜像源比较方便。将pip源更换到国内镜像
- 这个格式是我自创的,经常有人问我为什么,这里做个简单总结:1、分类,一个模块或者同类功能定义为一类定义,每类定义之间用段落隔开。2、分级,每
- Object 类型的对象虽然有 toString 方法,但结果却是 [Object Object] 让人没法理解的字符。比如简单的对象:{n
- 前言PyQt是Python语言环境的GUI编程解决方案之一,另外还有PyGTK、wxPython等也较为常用。PyQt作为Qt语言的Pyth
- 前言:饼状图是用来呈现一个数据系列中各项的大小与各项占项总和的百分比,Matplotlib 提供了plt.pie()方法绘制柱状图,语法格式
- pytorch常用函数torch.randn()torch.randn(*sizes, out=None) → Tensor功能:从标准正态
- 本文实例为大家分享了mysql 8.0.27 安装配置图文教程的具体代码,供大家参考,具体内容如下下载官网下载安装包:>MySQL :
- 1、如果同时从同一个客户端插入很多行,使用含多个 VALUE的INSERT语句同时插入几行。这比使用单行INSERT语句快(在某些情况下快几
- 对numpy 矩阵进行通道间求均值给定一个3x2x2得矩阵,得到一个2x2的矩阵,其中对应元素是通道间的均值import numpy as
- 如下所示:import numpy as npimport pandas as pdfrom pandas import Series,Da
- 目录引言环境代码实现准备工作获取并存储好友头像信息生成微信好友墙后记引言前段时间,微信朋友圈开始出现了一种晒照片新形式,微信好友墙,即在一张
- 前言平时我们在接收后端返回的json对象通常是一个字符串类型的object,所以一般我们要对这个object进行类型转化后,我们才能使用ob
- 如今,互联网越来越走近我们的生活,网上冲浪也渐渐成为我们生活不可缺少的一部分。网络世界五彩缤纷,涌现出大量优秀精美的网页。大量网络信息的呈现
- 前言在做项目中,网站前台或者后台有些数据需要在多个视图页面使用,例如用户基本信息数据,菜单展示数据。首先想到的是在每个控制器里传入这些需要的
- 如下所示:#ltp_data 字符串 写进777.txt1、def save(filename, contents): fh = open(
- SET @SQL = 'SELECT * FROM Comment with(nolock) WHERE 1=1 &nbs
- 撰写时间:2017.5.23一维数组1.numpy初始化一维数组a = np.array([1,2,3]);print a.shape输出的
- 使用说明:1. 将PHP.INI文件里面的"extension=php_gd2.dll"一行前面的;号去掉,因为我们要用
- 很久没写blog,太忙了。没什么时间写复杂的东西,重新把颜色渐变效果写一遍。关于颜色的效果一般就两个,颜色梯度变化和样式的颜色渐变,前者在i