静态语言在服务器编程时都会遇到这样的问题:如何保证已有的连接服务不中断同时又升级版本?
最近花了点时间看了下nginx热更新代码流程,想了下结合之前的经验一并总结下热更新
1. 热更新是什么?
简单翻译成人类可读的实例是如下这个样子:
举个例子,你现在在坐卡车,卡车开到了150KM/H
然后,有个轮胎,爆了
然后,司机说,你就直接换吧,我不停车。你小心点换
嗯。就这个意思
2.网关中的热更新
服务程序热更新
这个问题在层7网关中尤其严重,网关中承载着大量的请求,包括HTTP/HTTPS短连接
、HTTP/HTTPS长连接
、甚至是websocket
这种超长连接(websocket通常连接时间会很长,十几分钟到几天不等)。这样的话我们势必不能讲服务程序停止再启动的冷更新
,服务进程热更新
是非常有必要的。
网关作为一个基础组件,需要保证高可用,是很难将其先停下来再更新的;
有人说可以使用负载均衡将需要更新的组件先隔离,再停机更新,但是如果是一个很小的集群没有负载均衡呢,又或者这样手动一台一台升级也着实麻烦,部分情况下就算隔离了也不过是不会有新的连接过来,旧的连接/请求依旧需要处理完成,否则就会造成部分服务不可用。
不过实际上线上操作是集群隔离加热更新一起操作
3.nginx热更新(Upgrading Executable on the Fly)
nginx [engine x]
是Igor Sysoev
编写的一个HTTP和反向代理服务器
,另外它也可以作为邮件代理服务器。 它已经在众多流量很大的俄罗斯网站上使用了很长时间,这些网站包括Yandex
、Mail.Ru
、VKontakte
,以及Rambler
。据Netcraft
统计,在2012年8月份,世界上最繁忙的网站中有11.48%
使用Nginx
作为其服务器或者代理服务器。
NginX采用
Master/Worker
的多进程模型,Master
进程负责整个NginX
进程的管理。Nginx
的模块化
、热更新
、Http处理流程
、日志
等机制都非常经典。这里将会简要介绍一下热更新的机制
3.1 nginx热升级流程
步骤1、升级nginx二进制文件
,需要先将新的nginx可执行文件
替换原有旧的nginx文件
,然后给nginx master进程
发送USR2信号
,告知其开始升级可执行文件;nginx master进程
会将老的pid
文件增加.oldbin后缀
,然后拉起新的master
和worker进程
,并写入新的master进程的pid
。
UID PID PPID C STIME TTY TIME CMD
root 4584 1 0 Oct17 ? 00:00:00 nginx: master process /usr/local/apigw/apigw_nginx/nginx
root 12936 4584 0 Oct26 ? 00:03:24 nginx: worker process
root 12937 4584 0 Oct26 ? 00:00:04 nginx: worker process
root 12938 4584 0 Oct26 ? 00:00:04 nginx: worker process
root 23692 4584 0 21:28 ? 00:00:00 nginx: master process /usr/local/apigw/apigw_nginx/nginx
root 23693 23692 3 21:28 ? 00:00:00 nginx: worker process
root 23694 23692 3 21:28 ? 00:00:00 nginx: worker process
root 23695 23692 3 21:28 ? 00:00:00 nginx: worker process
步骤2、在此之后,所有工作进程(包括旧进程和新进程)
将会继续接受请求。这时候,需要发送WINCH
信号给nginx master进程
,master进程
将会向worker进程
发送消息,告知其需要进行graceful shutdown
,worker进程
会在连接处理完之后进行退出。
UID PID PPID C STIME TTY TIME CMD
root 4584 1 0 Oct17 ? 00:00:00 nginx: master process /usr/local/apigw/apigw_nginx/nginx
root 12936 4584 0 Oct26 ? 00:03:24 nginx: worker process
root 12937 4584 0 Oct26 ? 00:00:04 nginx: worker process
root 12938 4584 0 Oct26 ? 00:00:04 nginx: worker process
root 23692 4584 0 21:28 ? 00:00:00 nginx: master process /usr/local/apigw/apigw_nginx/nginx
#若旧的worker进程还需要处理连接,则worker进程不会立即退出,需要待消息处理完后再退出
步骤3、经过一段时间之后,将会只会有新的worker进程处理新的连接。
注意,
旧master进程
并不会关闭它的listen socket
;因为如果出问题后,需要回滚,master进程需要法重新启动它的worker进程。
步骤4、如果升级成功,则可以向旧master进程
发送QUIT信号,停止老的master进程
;如果新的master进程
(意外)退出,那么旧master进程
将会去掉自己的pid文件的.oldbin后缀。
3.2 nginx热更新相关信号
master进程相关信号
USR2 升级可执行文件
WINCH 优雅停止worker进程
QUIT 优雅停止master进程
worker进程相关信号
TERM, INT 快速退出进程
QUIT 优雅停止进程
3.3 nginx相关代码走读
1、USR2流程
master
收到USR2信号
后,会拉起新的master nginx进程
;
新的master进程
拉起新的worker进程
;
最终,老的worker进程
和新的worker进程
共用一个listen socket
,接受连接。
也就是此时这里实现了端口复用的技术,也就是
REUSEPORT
2、WINCH流程
master进程
收到WINCH信号
后,会给各个worker进程
发送QUIT信号
,让其优雅退出;master进程
并不再处理新的连接。
worker graceful shutdown
流程,关闭listen socket
,不再处理新的连接;待已有连接处理完后,清理连接,退出进程。
3、QUIT流程
master graceful shutdown
流程,没什么好说的
3.4 nginx热升级 QA
1、如何防止多次可执行文件触发热更新?
相关代码
ngx_signal_handler
-->
case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
if (ngx_getppid() == ngx_parent || ngx_new_binary > 0) {
/*
* Ignore the signal in the new binary if its parent is
* not changed, i.e. the old binary's process is still
* running. Or ignore the signal in the old binary's
* process if the new binary's process is already running.
*/
action = ", ignoring";
ignore = 1;
break;
}
ngx_change_binary = 1;
action = ", changing binary";
break;
若老的nginx还在,nginx无法进行热更新二进制文件
2、nginx升级过程中,发现新的可执行文件出现问题该如何回滚?
- a、向
旧master进程
发送HUP信号
。旧进程将启动新的worker进程
,而且不会重新读取配置。之后,通过向新的主master进程
发送QUIT信号
,可以优雅地关闭新的master
和worker进程
。 - b、将
TERM信号
发送到新的master进程
,然后新的master进程
将向其worker进程
发送一条消息,让它们立即退出,这种退出不是graceful shutdown
。当新的master进程
退出时,旧的master进程
将启动新的worker进程
。 - c、如果
新的进程
没有退出,则应该向它们发送终止KILL信号
。当新的master进程
退出时,旧的master进程
将启动新的工作进程。
3、什么是graceful shutdown
本文中的graceful shutdown
是指server
不再处理新的连接,但是进程不会立即退出,待所有连接断开后再退出进程。
4. 总结
总结一下个人在nginx
二进制文件热升级
时用的命令
cd /usr/local/nginx
cp nginx nginx_bak
mv /data/nginx/nginx ./nginx #需要使用mv来更新二进制文件
./nginx -t #尝试启动,查看其加载配置文件等初始化功能是否正常
netstat -anp | grep -E "80|443" | grep nginx #检查连接状态
kill -USR2 `cat /usr/local/nginx/nginx.pid` #升级nginx可执行文件,此时会有两组nginx master和worker进程
kill -WINCH `cat /usr/local/nginx/nginx.pid.oldbin` #新的可执行文件启动ok,且能够正常处理数据流,告知老的master进程去通知其worker进程进行优雅退出
...
kill -QUIT `cat /usr/local/nginx/nginx.pid.oldbin` #待所有的老的nginx worker进程优雅退出后(处理完连接),停止老的master进程
TODO:nginx还会有依赖的so文件的热升级–其实更应该属于后台进程的so文件热升级流程,我在使用它的时候也踩过坑–主要原因还是操作不规范,对so其加载运行原理不够熟悉导致
5.额外
实际上,静态语言后端server
有一套固定的热升级(单进程)流程,其基本流程如下:
若需要支持热升级的是多进程,那么
nginx的热升级
过程是最值得参考的
1、通过调用
fork/exec
启动新的版本的进程
,2、
子进程
调用接口获取从父进程
继承的socket 文件
描述符重新监听socket
3、在此过程中,不会对用户请求造成任何中断。
nginx的热升级
流程也是类似,只不过由于nginx
工作是多进程
,故它会先启动新版本的一组master/worker进程
;
然后停止老的worker进程
,让其不处理连接,由新的worker进程来
处理连接;
升级完毕后,即可退出老的master进程,热升级完成。
6.参考
http://tengine.taobao.org/nginx_docs/cn/docs/control.html