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 相关文章推荐
一个好用的PHP验证码类实例分享
Dec 27 PHP
PHP FTP操作类代码( 上传、拷贝、移动、删除文件/创建目录)
May 10 PHP
php中call_user_func函数使用注意事项
Nov 21 PHP
PHP中$this和$that指针使用实例
Jan 06 PHP
Symfony页面的基本创建实例详解
Jan 26 PHP
PHP多线程编程之管道通信实例分析
Mar 07 PHP
PHP实现适用于文件内容操作的分页类
Jun 15 PHP
详谈PHP面向对象中常用的关键字和魔术方法
Feb 04 PHP
yii 2.0中表单小部件的使用方法示例
May 23 PHP
搜索附近的人PHP实现代码
Feb 11 PHP
Laravel5.5以下版本中如何自定义日志行为详解
Aug 01 PHP
PHP+Ajax实现的检测用户名功能简单示例
Feb 12 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
分享一个Laravel好用的Cache宏
2015/03/02 PHP
PHP 7.0.2 正式版发布
2016/01/08 PHP
laravel 执行迁移回滚示例
2019/10/23 PHP
css3实现背景模糊的三种方式
2021/03/09 HTML / CSS
javascript parseInt 大改造
2009/09/27 Javascript
JavaScript 组件之旅(一)分析和设计
2009/10/28 Javascript
js中opener与parent的区别详细解析
2014/01/14 Javascript
使用typeof方法判断undefined类型
2014/09/09 Javascript
jquery 实现两Select 标签项互调示例代码
2014/09/25 Javascript
Node.js开源应用框架HapiJS介绍
2015/01/14 Javascript
非常实用的js验证框架实现源码 附原理方法
2016/06/08 Javascript
微信小程序图表插件(wx-charts)实例代码
2017/01/17 Javascript
easyui 中的datagrid跨页勾选问题的实现方法
2017/01/18 Javascript
JSON与JS对象的区别与对比
2017/03/01 Javascript
jQuery复合事件结合toggle()方法的用法示例
2017/06/10 jQuery
vuejs 单文件组件.vue 文件的使用
2017/07/28 Javascript
vue使用iframe嵌入网页的示例代码
2020/06/09 Javascript
实例详解Node.js 函数
2018/06/10 Javascript
Vue中 key keep-alive的实现原理
2018/09/18 Javascript
swiper Scrollbar滚动条组件详解
2019/09/08 Javascript
使用python实现扫描端口示例
2014/03/29 Python
Python中set与frozenset方法和区别详解
2016/05/23 Python
浅谈Python基础之I/O模型
2017/05/11 Python
Python批量查询域名是否被注册过
2017/06/21 Python
Python2.7 实现引入自己写的类方法
2018/04/29 Python
Python 数值区间处理_对interval 库的快速入门详解
2018/11/16 Python
python2和python3在处理字符串上的区别详解
2019/05/29 Python
Django 模型类(models.py)的定义详解
2019/07/19 Python
python字符串分割及字符串的一些常规方法
2019/07/24 Python
使用phonegap创建联系人的实现方法
2017/03/30 HTML / CSS
Html5页面上如何禁止手机虚拟键盘弹出
2020/03/19 HTML / CSS
美国家喻户晓的保健品品牌:Vitamin World(维他命世界)
2016/08/19 全球购物
介绍一下linux的文件权限
2012/02/15 面试题
干部作风建设个人剖析材料
2014/10/11 职场文书
Spring Boot 启动、停止、重启、状态脚本
2021/06/26 Java/Android
六个好看实用的 HTML + CSS 后台登录入口页面
2022/04/28 HTML / CSS