php-fpm重启导致的程序执行中断问题详解


Posted in PHP onApril 29, 2019

背景和初步排查

  • 订单业务对账时报警了,有笔订单在我们自己的mongo库里没有找到
  • 业务接口  /3/xx/vgift/send 调用礼物系统  sendPresent 接口完成送礼, 之后写mongo,但是php error log 里却查不到任何mongo异常日志
  • 写mongo没有异常,但是库里却没记录,推断只有2个可能

1是error log 丢日志了
2是程序执行过程中操作完sendPresent后down掉了,导致没写入mongo
-第一个情况工作多年的经验来看应该不至于,那就先根据第二种情况继续查吧

  • 那就去看下php-fpm 的日志,看对应的时间点有没有什么异常
[wu.daolin@web001.m6~]$ grep "2017 05:28" /var/log/php-fpm.log
[25-Jun-2017 05:28:01] NOTICE: Terminating ...

跟订单时间刚好吻合,那肯定有必要研究下了

熟悉下 php-fpm 的管理

php-fpm 是通过 php-fpm这个命令进行管理的,我们先看下这个命令

man php-fpm

这里有提到,php-fpm then responds to several POSIX signals php-fpm 会对下面几个信号作(自己的)处理

  • SIGINT, SIGTERM: immediate termination
  • SIGQUIT: graceful stop
  • SIGUSR1: re-open log file
  • SIGUSR2: graceful reload of all workers + reload of fpm conf/binary

动手验证下

sudo kill -QUIT {php-fpm-pid}

[26-Jun-2017 13:58:22] NOTICE: Finishing ...            
[26-Jun-2017 13:58:22] NOTICE: exiting, bye-bye!

sudo kill -TERM {php-fpm-pid}

[26-Jun-2017 13:59:21] NOTICE: Terminating ...            
[26-Jun-2017 13:59:21] NOTICE: exiting, bye-bye!

sudo kill -USR2 12583

[26-Jun-2017 14:00:48] NOTICE: Reloading in progress ...          
[26-Jun-2017 14:00:48] NOTICE: reloading: execvp("/usr/sbin/php-fpm", {"/usr/sbin/php-fpm", "--daemonize"})    
[26-Jun-2017 14:00:48] NOTICE: using inherited socket fd=8, "10.30.60.87:9000"        
[26-Jun-2017 14:00:48] NOTICE: using inherited socket fd=8, "10.30.60.87:9000"        
[26-Jun-2017 14:00:48] NOTICE: fpm is running, pid 12696          
[26-Jun-2017 14:00:48] NOTICE: ready to handle connections

从验证结果推断

在 05:28:01这个时间有人给php-fpm 发送了SIGTERM信号,在这个点发生很可能是个定时任务, 确认果然是这样 28 5 * * * root /etc/init.d/php-fpm restart> /dev/null

我们的 php-fpm 管理

  • init script 是  /etc/init.d/php-fpm
  • 其中stop 是  killproc -p ${pidfile} php-fpm, 显然从日志结果来个是kill -TERM  . 文档里也说了默认信号就是TERMkillproc sends signals to all processes that use the spec­ified executable. If no signal name is specified, the signal SIGTERM is sent.

看下这个情况下nginx的反应

php-fpm重启导致的程序执行中断问题详解

总结原因

  • 业务请求时执行完 sendPresent这个动作后 , 还没来得及写mongo库, php-fpm就刚好被 terminate 了,....  刚好赶上了

替代方案

  • 虽然php-fpm 没有解释 terminate 跟 graceful stop 的具体含义, 但猜的话前者是直接就终止程序的执行了,后者可能是温柔点,把处理中的请求里的所有操作都执行完再杀死。。。
  • 总之 SIGTERM terminate 调php 工作进程太粗暴了,应该要改一下比较好
  • 改成 SIGUSER2  reload 方式
  • 改成 SIGQUIT方式 ,把killproc -p ${pidfile} php-fpm 这句 改成 killproc -p ${pidfile} php-fpm -QUIT
  • php-fpm 的worker 是计数n次后就会杀掉重新拉一个,如果用reload感觉功能重复了,根本没必要定时重启了, 我还是选 graceful stop(SIGQUIT) 吧
  • 当然还有个问题时,为啥要配置个定时重启,将上面的内容发给sa看了

与sa 的问答

sa 说了3点意见

  • 建议看下 -QUIT 时,Nginx的状态码是否正常?另外在某种情况下,可能会造成 PHP-FPM 进程退出时间比较长,会影响部署吗?
  • 用 reload(SIGUSER2) 而不是用SIGTERM停掉再启动.
    我们之前的测试结果看 reload 之后,nginx会报 502,并不 graceful stop。建议做好测试确认,包括部署php代码时是不是 reload?Bug #60961 Graceful Restart (USR2) isn't very graceful
  • php-fpm每天定时重启脚本 这个定时脚本大概是在2012年部署的,当时是担心 PHP-FPM 存在内存泄漏的情况而添加的。到现在是不是还适用?建议找一台机器关掉定时脚本观察一段较长时间看看。

我回复

  • SIGQUIT 是否正常还不清楚,但现在的默认 SIGTERM 是立即停掉php 进程是肯定不正常的 --  从nginx error log 看,对于nginx 和 php-fpm已经建立好的连接,错误是 “104: Connection reset by peer”; 准备去连的是“111: Connection refused”;
  • “111: Connection refused” 是还可以接受的,连不上而已,用户稍后重试就可以;“104: Connection reset by peer” 这个就很难接受,这个错我理解的意思是连接已经建好了,php突然terminate了,然后发了个RST分节给nginx;背后就表示当前请求可能只执行了一半动作,还有动作没执行完,这可能就造成丢数据了。。。比如文章开头说的这个问题
  • reload 那个其实就是 -USR2信号,这个bug看起来还没解决。。。不过-USR2 应该说是偶现terminate,但 -TERM 肯定是必现terminate
  • 现在代码部署逻辑是同步代码+清理opcache和yac缓存, 不对php-fpm进程做操作
  • php-fpm 会自己对worker进程处理的请求数计数,达到一定数量就干掉再重新拉一个; 所以worker进程应该没有什么内存泄露的问题; manager 进程就不清楚了,但我想概率应该是极其低的。这个适不适用感觉很难去证伪啊。。。
  • 所以要不找3台机器, 一台用 -QUIT, 一台用 -USR2,  一台去掉这个定时任务;先观察下
  • sa 回复可以,我们自己看着办

尾声

改成 SIGQUIT 信号nginx里还是有 104: Connection reset by peer, 看来手册里说SIGQUIT: graceful stop 也不能保证一次请求里的所有动作都执行完啊

最终结果 去掉这个定时重启php-fpm 的任务, 已经3个多月了,没发现问题,oh yeah~

参考文档

  • php-fpm signal handler
  • php-fpm init script
  • killproc man page

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

PHP 相关文章推荐
第二节 对象模型 [2]
Oct 09 PHP
fleaphp常用方法分页之Pager使用方法
Apr 23 PHP
php&mysql 日期操作小记
Feb 27 PHP
PHP cdata 处理(详细介绍)
Jul 05 PHP
一致性哈希算法以及其PHP实现详细解析
Aug 24 PHP
ECMall支持SSL连接邮件服务器的配置方法详解
May 19 PHP
PHP中的多行字符串传递给JavaScript的两种方法
Jun 19 PHP
最常用的8款PHP调试工具
Jul 06 PHP
smarty内置函数section的用法
Jan 22 PHP
mac系统下为 php 添加 pcntl 扩展
Aug 28 PHP
PHP实现财务审核通过后返现金额到客户的功能
Jul 04 PHP
TP5框架请求响应参数实例分析
Oct 17 PHP
详解提高使用Java反射的效率方法
Apr 29 #PHP
Thinkphp整合阿里云OSS图片上传实例代码
Apr 28 #PHP
详解php中生成标准uuid(guid)的方法
Apr 28 #PHP
PHP写API输出的时用echo的原因详解
Apr 28 #PHP
PHP+mysql防止SQL注入的方法小结
Apr 27 #PHP
PHP常用工具函数小结【移除XSS攻击、UTF8与GBK编码转换等】
Apr 27 #PHP
PHP操作路由器实现方法示例
Apr 27 #PHP
You might like
php生成WAP页面
2006/10/09 PHP
深入理解PHP之require/include顺序 推荐
2011/01/02 PHP
php单例模式实现(对象只被创建一次)
2012/12/05 PHP
利用curl 多线程 模拟 并发的详解
2013/06/14 PHP
关于ob_get_contents(),ob_end_clean(),ob_start(),的具体用法详解
2013/06/24 PHP
PHP error_log()将错误信息写入一个文件(定义和用法)
2013/10/25 PHP
php删除文本文件中重复行的方法
2015/04/28 PHP
对YUI扩展的Gird组件 Part-2
2007/03/10 Javascript
js本身的局限性 别让javascript做太多事
2010/03/23 Javascript
js计算任意值之间随机数的方法
2015/01/16 Javascript
提交按钮的name='submit'引起的js失效问题及原因
2015/02/25 Javascript
JavaScript获取当前网页标题(title)的方法
2015/04/03 Javascript
mvc中form表单提交的三种方式(推荐)
2016/08/10 Javascript
AngularJS模块详解及示例代码
2016/08/17 Javascript
移动端基础事件总结与应用
2017/01/12 Javascript
jQuery简单绑定单个事件的方法示例
2017/06/10 jQuery
关于vue-router的beforeEach无限循环的问题解决
2017/09/09 Javascript
JavaScript多线程运行库Nexus.js详解
2017/12/22 Javascript
javaScript产生随机数的用法小结
2018/04/21 Javascript
快速了解Node中的Stream流是什么
2019/02/13 Javascript
Node.js 多线程完全指南总结
2019/03/27 Javascript
小程序表单认证布局及验证详解
2020/06/19 Javascript
vue中解决微信html5原生ios虚拟键返回不刷新问题
2020/10/20 Javascript
[00:15]TI9观赛名额抽取
2019/07/10 DOTA
python中pandas.DataFrame对行与列求和及添加新行与列示例
2017/03/12 Python
Python爬虫之正则表达式基本用法实例分析
2018/08/08 Python
python set集合使用方法解析
2019/11/05 Python
Python基于read(size)方法读取超大文件
2020/03/12 Python
城野医生官方海外旗舰店:风靡亚洲毛孔收敛水
2018/04/26 全球购物
美国校服网上商店:French Toast
2019/10/08 全球购物
毕业生的求职信范文分享
2013/12/04 职场文书
办公室秘书岗位职责范本
2014/02/11 职场文书
北京奥运会口号
2014/06/21 职场文书
员工年终自我评价
2014/09/14 职场文书
超市创业计划书
2014/09/15 职场文书
2014年营销工作总结
2014/11/22 职场文书