静态语言在服务器编程时都会遇到这样的问题:如何保证已有的连接服务不中断同时又升级版本?最近花了点时间看了下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。
UIDPIDPPIDC STIME TTYTIME CMD root4584 10 Oct17 ?00:00:00 nginx: master process /usr/local/apigw/apigw_nginx/nginx root 1293645840 Oct26 ?00:03:24 nginx: worker process root 1293745840 Oct26 ?00:00:04 nginx: worker process root 1293845840 Oct26 ?00:00:04 nginx: worker process root 2369245840 21:28 ?00:00:00 nginx: master process /usr/local/apigw/apigw_nginx/nginx root 23693 236923 21:28 ?00:00:00 nginx: worker process root 23694 236923 21:28 ?00:00:00 nginx: worker process root 23695 236923 21:28 ?00:00:00 nginx: worker process步骤2、在此之后,所有工作进程(包括旧进程和新进程)将会继续接受请求。这时候,需要发送WINCH信号给nginx master进程,master进程将会向worker进程发送消息,告知其需要进行graceful shutdown,worker进程会在连接处理完之后进行退出。
UIDPIDPPIDC STIME TTYTIME CMD root4584 10 Oct17 ?00:00:00 nginx: master process /usr/local/apigw/apigw_nginx/nginx root 1293645840 Oct26 ?00:03:24 nginx: worker process root 1293745840 Oct26 ?00:00:04 nginx: worker process root 1293845840 Oct26 ?00:00:04 nginx: worker process root 2369245840 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,接受连接。
也就是此时这里实现了端口复用的技术,也就是REUSEPORT2、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 binarys process is still * running.Or ignore the signal in the old binarys * process if the new binarys 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 文件描述符重新监听socket3、在此过程中,不会对用户请求造成任何中断。nginx的热升级流程也是类似,只不过由于nginx工作是多进程,故它会先启动新版本的一组master/worker进程;
然后停止老的worker进程,让其不处理连接,由新的worker进程来处理连接;
升级完毕后,即可退出老的master进程,热升级完成。
6.参考
http://tengine.taobao.org/nginx_docs/cn/docs/control.html