声明:本人原创文章,详细内容已发布在我的微信个人技术公众号---网络技术修炼,公众号总结普及网络基础知识,包括基础原理、网络方案、开发经验和问题定位案例等,欢迎关注。
软件工程中持续迭代和更新是必不可少的,在服务端软件更新时,保持服务的连续性是一项关键任务。本文将从技术角度解析服务端软件更新过程如何实现不停止服务的重要功能。
在进行热升级时,进程的代码和数据都是非常重要的。为了实现代码的更新,同时又不丢失有用的数据,需要采取一些措施。有用的数据包括内存中的数据和文件描述符。对于内存中的数据,例如配置信息,可以通过将其落盘到配置文件中来实现保留。这样,在升级过程中,新的进程可以读取配置文件并继续使用之前的配置。而对于文件描述符,可以采用一种叫做UNIX域套接字的机制,在进程之间进行迁移。通过这种方式,新进程可以接管原来进程的文件描述符,从而保持之前打开的文件和网络连接的状态。在某些情况下,项目可能会选择不迁移文件描述符,而是通过让新旧进程共同处理一段时间的请求来逐步过渡。这样,新进程可以逐渐接收和处理新的请求,而老进程则继续处理旧的请求,直到所有请求都由新进程处理完毕。
另外,为了减轻对客户端的影响,还可以采用一些HTTP协议的特性。例如,在HTTP1中可以使用"Connection: Close"头部字段,告知客户端断开连接并重新连接。而在HTTP2中,可以使用Goaway帧来类似地通知客户端断开连接。这样一来,客户端就能够及时与新进程建立新的连接,以继续进行请求和响应的处理。
通过这些措施和优化方法,可以实现热升级过程中代码更新和数据保留的目标,并尽可能减少对系统和客户端的影响。
nginx
官方文档:http://nginx.org/en/docs/control.html
nginx中master进程为管理进程,woker进程为master进程fork出的子进程,是处理网络的进程。
master进程支持的信号
TERM,INT | 快速退出 |
---|---|
QUIT | 优雅退出master+worker进程(worker进程处理完存量请求再退出) |
KILL | 强子终止进程 |
HUP | 使用新的的配置启动worker进程,并优雅退出老的worker进程 |
USR1 | 重新打开日志文件 |
USR2 | 升级可执行文件(即启动新的master进程) |
WINCH | 优雅退出woker进程 |
worker进程支持的信号:
TERM,INT | 快速退出 |
---|---|
QUIT | 优雅退出(处理完存量请求再退出) |
USR1 | 重新打开日志文件 |
#ps -ef | grep nginx
root 82556 1 0 11:58 ? 00:00:00 nginx: master process ./sbin/nginx
nginx 82562 82556 0 11:58 ? 00:00:00 nginx: worker process
nginx 82563 82556 0 11:58 ? 00:00:00 nginx: worker process
nginx 82564 82556 0 11:58 ? 00:00:00 nginx: worker process
nginx 82565 82556 0 11:58 ? 00:00:00 nginx: worker process
nginx 82566 82556 0 11:58 ? 00:00:00 nginx: worker process
nginx 82567 82556 0 11:58 ? 00:00:01 nginx: worker process
nginx 82569 82556 2 11:58 ? 00:00:03 nginx: worker process
nginx 82570 82556 14 11:58 ? 00:00:24 nginx: worker process
#cat /app/nginx/logs/nginx.pid
82556
可以看出nginx.pid记录的是当前master的进程号。
kill -USR2 `cat /app/nginx/logs/nginx.pid`
执行后结果
#ps -ef | grep nginx
root 82556 1 0 11:58 ? 00:00:00 nginx: master process ./sbin/nginx
nginx 82562 82556 0 11:58 ? 00:00:01 nginx: worker process
nginx 82563 82556 0 11:58 ? 00:00:01 nginx: worker process
nginx 82564 82556 0 11:58 ? 00:00:01 nginx: worker process
nginx 82565 82556 0 11:58 ? 00:00:01 nginx: worker process
nginx 82566 82556 0 11:58 ? 00:00:01 nginx: worker process
nginx 82567 82556 0 11:58 ? 00:00:02 nginx: worker process
nginx 82569 82556 2 11:58 ? 00:00:06 nginx: worker process
nginx 82570 82556 13 11:58 ? 00:00:43 nginx: worker process
root 85710 82556 0 12:04 ? 00:00:00 nginx: master process ./sbin/nginx
nginx 85716 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85717 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85718 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85719 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85720 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85721 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85723 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85724 85710 0 12:04 ? 00:00:00 nginx: worker process
#cat /app/nginx/logs/nginx.pid
85710
可以看出nginx.pid已经变成新master进程号
#cat /app/nginx/logs/nginx.pid.oldbin
82556
nginx.pid.oldbin存放老master进程号。
kill -WINCH `cat /app/nginx/logs/nginx.pid.oldbin`
#ps -ef | grep nginx
root 82556 1 0 11:58 ? 00:00:00 nginx: master process ./sbin/nginx
nginx 82569 82556 1 11:58 ? 00:00:06 nginx: worker process is shutting down
nginx 82570 82556 11 11:58 ? 00:00:43 nginx: worker process is shutting down
root 85710 82556 0 12:04 ? 00:00:00 nginx: master process ./sbin/nginx
nginx 85716 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85717 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85718 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85719 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85720 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85721 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85723 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85724 85710 0 12:04 ? 00:00:00 nginx: worker process
此过程要不停有请求访问到nginx才能看到worker优雅退出过程,一段时间后存量请求全部处理完毕。
#ps -ef | grep nginx
root 82556 1 0 11:58 ? 00:00:00 nginx: master process ./sbin/nginx
root 85710 82556 0 12:04 ? 00:00:00 nginx: master process ./sbin/nginx
nginx 85716 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85717 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85718 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85719 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85720 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85721 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85723 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85724 85710 0 12:04 ? 00:00:00 nginx: worker process
kill -QUIT `cat /app/nginx/logs/nginx.pid.oldbin`
#ps -ef | grep nginx
root 85710 1 0 12:04 ? 00:00:00 nginx: master process ./sbin/nginx
nginx 85716 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85717 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85718 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85719 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85720 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85721 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85723 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx 85724 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx信号处理函数:ngx_signal_handler
envoy
mosn
linux环境可以使用下面函数在进程间传递fd。
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ps:下面代码均以v1.5.0版本为例。
涉及Domain Socket:
reconfig.sock记录老进程的监听
listen.sock记录新进程的监听
流程:
ReconfigureHandler
sendInheritListeners:老进程将已经存在的 fd 通过 listen.sock 发送给新进程。
shutdownServers:老进程不再接收新连接,并优雅关闭。
WaitConnectionsDone:处理完存量请求后退出。
涉及Domain Socket:conn.sock
流程:
nginx官方文档:http://nginx.org/en/docs/control.html
MOSN 平滑升级原理解析:https://mosn.io/docs/products/structure/smooth-upgrade