endless 如何实现不停机重启 Go 程序? (4)

shutdown 这里会先将连接关闭,因为这个时候子进程已经启动了,所以不再处理请求,需要把端口的监听关了。这里还会异步调用 srv.hammerTime 方法等待60秒把父进程的请求处理完毕才关闭父进程。

getListener 获取端口监听 func (srv *endlessServer) getListener(laddr string) (l net.Listener, err error) { // 如果是子进程 if srv.isChild { var ptrOffset uint = 0 runningServerReg.RLock() defer runningServerReg.RUnlock() // 这里还是处理多个 server 的情况 if len(socketPtrOffsetMap) > 0 { // 根据server 的顺序来获取 listen fd 的序号 ptrOffset = socketPtrOffsetMap[laddr] } // fd 0,1,2是预留给 标准输入、输出和错误的,所以从3开始 f := os.NewFile(uintptr(3+ptrOffset), "") l, err = net.FileListener(f) if err != nil { err = fmt.Errorf("net.FileListener error: %v", err) return } } else { // 父进程 直接返回 listener l, err = net.Listen("tcp", laddr) if err != nil { err = fmt.Errorf("net.Listen error: %v", err) return } } return }

这里如果是父进程没什么好说的,直接创建一个端口监听并返回就好了。

但是对于子进程来说是有一些绕,首先说一下 os.NewFile 的参数为什么要从3开始。因为子进程在继承父进程的 fd 的时候0,1,2是预留给 标准输入、输出和错误的,所以父进程给的第一个fd在子进程里顺序排就是从3开始了,又因为 fork 的时候cmd.ExtraFiles 参数传入的是一个 files,如果有多个 server 那么会依次从3开始递增。

如下图,前三个 fd 是预留给 标准输入、输出和错误的,fd 3 是根据传入 ExtraFiles 的数组列表依次递增的。

graceful_restart3

其实这里我们也可以用开头的例子做一下试验:

# 第一次构建项目 go build main.go # 运行项目,这时就可以做内容修改了 ./endless & # 这个时候我们看看父进程打开的文件 lsof -P -p 17116 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME ... main 18942 root 0u CHR 136,2 0t0 5 /dev/pts/2 main 18942 root 1u CHR 136,2 0t0 5 /dev/pts/2 main 18942 root 2u CHR 136,2 0t0 5 /dev/pts/2 main 18942 root 3u IPv4 2223979 0t0 TCP localhost:5003 (LISTEN) # 请求项目,60s后返回 curl "http://127.0.0.1:5003/sleep?duration=60s" & # 重启,17116为父进程pid kill -1 17116 # 然后我们看一下 main 程序的进程应该有两个 ps -ef |grep ./main root 17116 80539 0 04:19 pts/2 00:00:00 ./main root 18110 17116 0 04:21 pts/2 00:00:00 ./main # 可以看到子进程pid 为18110,我们看看该进程打开的文件 lsof -P -p 18110 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME ... main 19073 root 0r CHR 1,3 0t0 1028 /dev/null main 19073 root 1u CHR 136,2 0t0 5 /dev/pts/2 main 19073 root 2u CHR 136,2 0t0 5 /dev/pts/2 main 19073 root 3u IPv4 2223979 0t0 TCP localhost:5003 (LISTEN) main 19073 root 4u IPv4 2223979 0t0 TCP localhost:5003 (LISTEN) # 新API请求 curl "http://127.0.0.1:5003/sleep?duration=1s" 总结

通过上面的介绍,我们通过 endless 学习了在 Go 服务中如何做到不停机也可以重启服务,相信这个功能在很多场景下都会用到,没用到的同学也可以尝试在自己的系统上玩一下。

热重启总的来说它允许服务重启期间,不中断已经建立的连接,老服务进程不再接受新连接请求,新连接请求将在新服务进程中受理。对于原服务进程中已经建立的连接,也可以将其设为读关闭,等待平滑处理完连接上的请求及连接空闲后再行退出。

通过这种方式,可以保证已建立的连接不中断,新的服务进程也可以正常接受连接请求。

Reference

https://goteleport.com/blog/golang-ssh-bastion-graceful-restarts/

https://grisha.org/blog/2014/06/03/graceful-restart-in-golang/

https://xixiliguo.github.io/post/golang-exec/

https://github.com/fvbock/endless

https://golang.org/pkg/os/signal/

https://stackoverflow.com/questions/11635219/dup2-dup-why-would-i-need-to-duplicate-a-file-descriptor

程序如何实现热重启/

扫码_搜索联合传播样式-白色版 1

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyspwy.html