Nginx如何升级而不会丢失任何请求?

根据Nginx的文档 :

如果您需要用新的nginx二进制文件(升级到新版本或添加/删除服务器模块)replacenginx二进制文件,则可以在没有任何服务停机的情况下执行 – 不会有传入的请求丢失。

我的同事和我试图弄清楚: 这是如何工作的? 。 我们知道(我们认为):

  • 一次只能有一个进程在端口80上进行监听
  • Nginx创build一个套接字并将其连接到端口80
  • 父进程及其任何subprocess都可以绑定到同一个套接字,这就是Nginx可以让多个工作subprocess响应请求

我们也对Nginx做了一些实验,像这样:

  • 发送kill -USR2到当前主进程
  • 反复运行ps -ef | grep unicorn ps -ef | grep unicorn看到任何独angular兽进程,与他们自己的pid和他们的父母pid
  • 注意到新的主进程首先是旧主进程的一个subprocess,但是当旧的主进程退出时,新进程进程的ppid为1。

所以很显然,新的主进程在运行时可以像旧进程一样监听同一个进程,因为当时新主进程是老主进程的一个subprocess。 但不知何故,新的主进程可以成为…呃…没有人的孩子?

我认为这是标准的Unix的东西,但我的过程和端口和套接字的理解是相当模糊的 。 有人可以更详细地解释这一点吗? 我们的假设是否有错? 有没有一本我可以阅读的书来真正理解这些东西?

具体说明: http : //www.csc.villanova.edu/~mdamian/Sockets/TcpSockets.htm描述了TCP套接字的C库。

我认为关键是在一个进程分支持有一个套接字文件描述符后,父进程和子进程都能够调用accept()。

所以这是流程。 Nginx,正常启动:

  1. 调用socket()和bind()和listen()来设置一个由文件描述符(整数)引用的套接字。
  2. 启动一个在循环中调用文件描述符上的accept()来处理传入连接的线程。

然后Nginx分叉。 父母像往常一样继续运行,但孩子立即执行新的二进制文件。 exec()清除旧程序,内存和正在运行的线程,但继承了打开的文件描述符:请参阅http://linux.die.net/man/2/execve 。 我怀疑exec()调用将打开的文件描述符的编号作为命令行参数传递。

这个孩子是作为升级的一部分而开始的:

  1. 从命令行读取打开的文件描述符的编号。
  2. 启动一个在循环中调用文件描述符上的accept()来处理传入连接的线程。
  3. 告诉父母流失(停止接受(),并完成现有的连接),并死亡。

我不知道nginx是怎么做的,但基本上,它可以只是exec新的二进制文件,携带新的进程监听套接字(实际上,它仍然是相同的进程,它只是取代执行中的程序)。 监听套接字有传入连接的积压,只要启动速度足够快,它应该能够在溢出之前开始处理它们。 如果不是,它可能会先fork,然后等待它启动到准备好处理传入请求的地步,然后移交监听套接字的命令(文件描述符在分派时被继承,都可以访问它)通过一些内部机制,在退出之前。 注意到你的观察,这看起来像是在做什么(如果你的父进程死了,你的ppid被重新分配给init,即pid 1)

如果它有多个进程在相同的侦听套接字上竞争接受(同样,我不知道nginx是怎么做的,也许它有一个调度过程?),那么你可以一个接一个地替换它们,命令它们执行新的程序,如上所述,但一次一个,永不放弃球。 请注意,在这样的过程中,永远不会有任何新的pid或父母/子女关系的变化。

至少,我认为这可能是我如何做到的,从头到尾。