平滑升级或者优雅升级或者优雅重启的目的是不影响当前正在执行请求的情况下实现升级或者重启.上一讲我们说了NGINX的平滑重启,本文以gracehttp说明GO的优雅升级.
两个关键步骤
我们以两个例子来说明平滑升级的两个关键节点:
- 如何让新的进程能够监听并且执行请求
- 如何让旧的进程不再接收请求
新进程监听接收请求
1 | #include <stdlib.h> |
1 | #include <stdio.h> |
1 | [root@localhost test-src]# cat helloworld.txt |
简单解释一下,helloworld.txt中文本为”hello,world”.第一个文件test.c读取了五个字符,”hello”,然后以exec系列函数启动第二个文件read.c,read.c中继续读取helloworld.txt,可以看到读出了”,world”6个字符
即A进程通过exec系列函数启动另一个B进程后,B进程会继承A进程的文件描述符,可以继续使用A进程的文件描述符读取数据.参考上节中NGINX的平滑升级,正是利用该特性使得新进程能够监听并且接收请求.
旧有进程不再接收请求并退出
1 | package main |
起一个程序监听8080端口,在一个for循环中进行accept,正常情况下如果没有请求会阻塞到accept.每次有新的请求时起一个goroutine处理,并且单独起一个goroutine5s钟之后将listener关闭.我们看下执行情况
1 | bogon:~ zhangshihua$ go run net.go |
可以看到,正常情况下阻塞到accept,当5s钟之后关闭listener之后,accept不再阻塞,输出错误,”accept tcp [::]:8080: use of closed network connection”.
所以只要关闭listener,旧的进程就不会再处理新来的请求.
gracehttp实现
代码分三部分,分别是gracehttp,gracenet以及httpdown,先看下gracenet中的StartProcess.
1 | func (n *Net) StartProcess() (int, error) { |
同样的处理方法,将所有listener中的文件描述符导出
接着看httpdown中的Stop方法:
1 | func (s *server) Stop() error { |
旧进程关闭了listener不再处理请求,新进程启动时导出了所有监听文件描述符,那么导出之后新进程启动时如何处理的呢
1 | // ListenTCP announces on the local network address laddr. The network net must |
调用net.ListenTCP时如果有从环境变量继承的listener,就会直接返回这些listener,否则生成新的listener.
小结
可以看到gracehttp与nginx平滑升级的原理基本一致.