如何在Nginx+PHP-FPM环境下实现原子化部署且不丢失任何请求?
如何在Nginx+PHP-FPM环境下实现原子化部署且不丢失任何请求?
我太懂你这种不想让用户看到503、不想错过任何请求的部署焦虑了!之前维护PHP应用的时候也踩过类似的坑,咱们直接说两个简单可行的方案,不用搞复杂的多服务器集群:
方案一:符号链接原子替换 + PHP-FPM优雅重载
这个方案核心是用原子性的目录切换配合PHP-FPM的平滑重载,完全避免代码切换的中间状态,同时让旧进程处理完所有 pending 请求:
- 第一步:把新版本的代码上传到一个临时目录,比如
/var/www/app_v2,确保所有文件都上传完毕、权限设置正确 - 第二步:用原子操作替换代码的主目录。如果你的应用目录是
/var/www/app,建议把它做成软链接,执行:
这个ln -sfn /var/www/app_v2 /var/www/appln -sf操作是原子的,系统不会出现“一半旧代码一半新代码”的状态 - 第三步:给PHP-FPM发送
USR2信号执行优雅重载:
这时候旧的PHP-FPM进程会继续处理当前所有请求,不会接受新请求;新的进程会加载新目录下的代码(包括重新初始化OPcache),待旧进程处理完所有请求后自动退出。kill -USR2 $(cat /var/run/php-fpm.pid)
为啥之前你觉得USR2不行?其实是OPcache的缓存问题——但用原子目录替换后,新进程加载的是全新的目录,OPcache会重新缓存新代码,旧进程处理完请求就消失,完全不会有新旧代码混合的情况,也不需要调整opcache.revalidate_freq。
方案二:Graceful Stop + 原子代码替换 + 快速启动
如果你的PHP-FPM版本对USR2支持不太友好,或者你更倾向于先停旧进程再启新的,那可以用这个零空窗的操作流程:
- 第一步:提前把新代码准备好,比如放在
/var/www/app_new - 第二步:给PHP-FPM发送
SIGQUIT信号执行优雅停止:
这个信号会让PHP-FPM立即停止接受新请求,但会把当前所有pending请求处理完再退出,不会中途中断请求kill -SIGQUIT $(cat /var/run/php-fpm.pid) - 第三步:原子替换代码目录(这一步一定要快,用mv操作也是原子的):
mv /var/www/app_new /var/www/app - 第四步:立刻启动PHP-FPM:
因为第三步是原子操作,而且旧进程是处理完请求才退出的,新进程启动的间隙极短,Nginx几乎不会碰到后端不可用的情况。如果还是担心,可以给Nginx加个简单的配置:在systemctl start php-fpmfastcgi_pass所在的location块里加上:
让Nginx在碰到临时错误时自动重试一次,彻底避免503。fastcgi_next_upstream error timeout;
为啥不用直接stop/start?
你之前试的stop→更新→start流程之所以会出503,是因为stop会强制杀死所有PHP-FPM进程,不管有没有正在处理的请求,而且中间的更新步骤有明显的空窗期,Nginx找不到后端就返回503。而上面两个方案要么让旧进程处理完请求再退出,要么把空窗期压缩到几乎可以忽略的程度。
如果你的业务量极大,单服务器确实可能需要多节点的上游集群来做无缝部署,但大部分中小应用用上面的简单方案就完全能满足“100%处理请求”的需求啦!
备注:内容来源于stack exchange,提问作者LiBorecek
相关产品推荐
相关产品推荐

