PHP 教你使用 Swoole-Tracker 秒级定位 PHP 卡死问题
PHPer 肯定收到过这样的投诉:小菊花一直在转!你们网站怎么这么卡!当我们线上业务遇到这种卡住(阻塞)的情况,大部分 PHPer 会两眼一抹黑,随后想起那句名言:性能瓶颈都在数据库然后把锅甩给DBA,赶紧找找慢sql,但这是非常错误的做法,因为有太多因素能导致业务卡住,下面列举几种常见的卡住问题。
1.死循环
最常见的就是写出了死循环代码
<?php
while(1){
//do something
if($condition){
//满足条件后退出循环
break;
}
}
上述代码通过$condition控制循环退出,如果程序验证不严格,某些情况$condition永远为真就会导致请求卡死。
2.sesstion_start函数导致卡死
PHP的session锁等待(ps:很多地方叫做session死锁,这不太符合死锁定义),这个相信大部分PHPer都遇到过,PHP默认会把session信息存储在/tmp/sess_下面的session文件里面,调用session_start()函数的时候会调用flock系统调用给session文件加锁,如果前一个请求没有结束或者手动释放session就会导致后面的请求无法获得锁,卡死在session_start()这个地方。下面举个例子,比如这种代码:
setInterval(function () {
$.post("/ajax/doSomething", {}, function (result) {//1s进行一次ajax
});
}, 1000)//1000ms == 1s
前端js定时通过ajax请求一下后端PHP的接口(/ajax/doSomething)做一些比较耗时的事情,写代码的人可能想当然的认为第一次的请求即使没有处理完,也不会影响第二次的请求,因为有很多的FPM进程每次请求会分发到不通的进程,但殊不知第二次请求会卡死在session_start()。
3.flock函数导致卡死
最常见的场景就是写日志,在PHP代码中确保每次fwrite写的日志内容小于8k的情况下我们可以利用append原子追加方式写日志,但是如果保证不了小于8k我们就需要在每次写日志前给文件加文件锁来避免两次日志间产生穿插的情况,代码如下:
<?php
$fp = fopen("/home/guoxinhua/php.log", "a+");
if (flock($fp, LOCK_EX)) { //给日志文件加锁
//do something
fwrite($fp, "the huge string\n");
flock($fp, LOCK_UN); // 释放锁定
}
如果在A进程获得锁后由于某种问题阻塞了那么B进程就会卡死在第三行flock的位置,除非A进程被kill掉,系统会自动释放这个文件锁
注意还有很多其他类型的锁即使进程被kill也不会自动被释放。
这个8k是可以改的,和glibc中的fwrite很多细节也不一样.
4. 网络客户端未设置超时时间
MySQL、CURL、Swoole\Client 等网络客户端未设置超时可能会导致进程阻塞。Swoole\Client 建立 TCP 连接的时候connect方法的最后一个参数是超时时间,-1即为永不超时,注意这里设置不是单指这次connect方法,而是后面所有的send,recv都永不超时,在同步阻塞的编程模式下,如果此时对端机器直接宕机等原因导致网络不通,那么本端业务的表现就是卡死状态,所有的send,recv方法都将被阻塞,代码如下:
<?php
$cli = new Swoole\Client(SWOOLE_SOCK_TCP);
if ($cli->connect('127.0.0.1', 9501,-1)) {
$cli->send("data");
$cli->recv();
} else {
echo "connect failed.";
}
5. Swoole协程的lock
在 Swoole 协程模式下,不正确的使用lock也会导致所有协程大面积卡死,如下代码,通过go方法创建2个协程(不理解协程的同学可以理解为创建了2个线程),第一个协程lock获得锁后在co::sleep位置让出了cpu此时开始执行第二个协程,第二个协程会卡死在第6行获得锁的位置,同时第一个协程也永远无法恢复继续执行。
<?php
$lock = new Swoole\Lock();
$c = 2;//创建2个协程
while ($c--) {
go(function () use ($lock) {//创建协程
$lock->lock();//获得锁
Co::sleep(1);//让出cpu
$lock->unlock();//释放锁
});
}
如何发现卡死
上述只是举了一些例子,真实业务中还有各种姿势的卡死,遇到这种问题有经验的PHPer会用strace -p命令查看当前PHP进程到底阻塞在哪个系统调用上面来定位问题,但这种方式有几个问题:
- 定位问题不清晰
比如死锁这种问题strace的时候只能看到类似futex(0x7f4c8d567128, FUTEX_WAIT, 2, NULL)这种信息,非常的不直观,很多人根本不知道哪些PHP代码会触发futex系统调用,还有前文提到session_start那个问题,很多人根本不知道这里会触发flock,也就说很难根据一个系统调用定位到具体问题。 - 不知道-p哪一个进程
我们线上环境通常会启动几十个甚至上百个PHP进程,在有些请求卡死,有些请求正常的情况下,你到底该strace -p哪个进程呢?貌似只能碰碰运气了。 - 发现不了死循环的问题
由于strace命令的原理是追踪所有的系统调用,如果是前文提到的第一种情况,也就是死循环的卡死,strace根本无法获得任何有用的信息。此时我们只能用gdb工具来获取当前死循环在哪里具体,具体做法如下:首先:gdb attach后面接个进程id。
然后:p (char *)executor_globals.current_execute_data.func.op_array.filename.val打印当前执行的PHP文件。p (char *)executor_globals.current_execute_data.func.op_array.function_name.val打印当前执行的函数名。p executor_globals.current_execute_data.opline.lineno打印当前执行的行数。
进一步也可以获取调用堆栈这里就不展开了。
但这明显太底层了,很多细节要注意,不精通PHP内核的人很难这样找问题(ps:通过.gdbinit能稍微减少点难度,但是也有很多其他问题)。
使用 Swoole Tracker 发现卡死问题
针对上述问题,Swoole官方出了一个解决方案 Swoole Tracker 的堆栈工具,同时支持FPM和Swoole。
使用方法很简单:
- 首先点击上面的连接注册个账户。
- 然后装上
swoole_tracker扩展。 - 最后登陆后台,在
调试器=>进程列表中点击堆栈按钮就能获得当前卡在哪了,如图: 
结尾
除了上面的卡死问题,还有一种情况是调用变慢,比如原来一个系统调用5ms,但是由于网络等等原因,这个调用100ms才返回,业务的表现是变慢了而不是卡死在那里,这种情况通过tracker的抓堆栈工具是无法定位问题的,因为卡住时间很短,很难抓到调用堆栈,此时需要Swoole工具链中的另外一个工具阻塞IO检测工具我们会在后面给大家介绍。phper在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货需要的可以免费分享给大家,需要的(点击→)我的官方群677079770
PHP 教你使用 Swoole-Tracker 秒级定位 PHP 卡死问题的更多相关文章
- 教你使用 Swoole-Tracker 秒级定位 PHP 卡死问题
PHPer 肯定收到过这样的投诉:小菊花一直在转!你们网站怎么这么卡!当我们线上业务遇到这种卡住(阻塞)的情况,大部分 PHPer 会两眼一抹黑,随后想起那句名言:性能瓶颈都在数据库然后把锅甩给DBA ...
- php Swoole实现毫秒级定时任务
项目开发中,如果有定时任务的业务要求,我们会使用linux的crontab来解决,但是它的最小粒度是分钟级别,如果要求粒度是秒级别的,甚至毫秒级别的,crontab就无法满足,值得庆幸的是swoole ...
- Swoft 2.0.5 更新,新增高效秒级定时任务、异常管理组件
什么是 Swoft ? Swoft 是一款基于 Swoole 扩展实现的 PHP 微服务协程框架.Swoft 能像 Go 一样,内置协程网络服务器及常用的协程客户端且常驻内存,不依赖传统的 PHP-F ...
- Swoole实现毫秒级定时任务
项目开发中,如果有定时任务的业务要求,我们会使用linux的crontab来解决,但是它的最小粒度是分钟级别,如果要求粒度是秒级别的,甚至毫秒级别的,crontab就无法满足,值得庆幸的是swoole ...
- Linux下实现秒级的crontab定时任务
crontab的格式如下 * * * * * command 分 时 日 月 周 命令 第1列表示分钟1-59 每分钟用*或者 */1表示 第2列表示小时1-23(0表示0点) 第3列表示日期1-31 ...
- Android 秒级编译 Freeline
http://mp.weixin.qq.com/s?__biz=MzA4NTQwNDcyMA==&mid=2650662410&idx=1&sn=c654fa7b0cc8c91 ...
- redis+Keepalived主从热备秒级切换
一 简介 安装使用centos 5.10 Master 192.168.235.135 Slave 192.168.235.152 Vip 192.168.235.200 编译环境 yum -y in ...
- 第四章 电商云化,4.1 17.5W秒级交易峰值下的混合云弹性架构之路(作者:唐三 乐竹 锐晟 潇谦)
4.1 17.5W秒级交易峰值下的混合云弹性架构之路 前言 每年的双11都是一个全球狂欢的节日,随着每年交易逐年创造奇迹的背后,按照传统的方式,我们的成本也在逐年上升.双11当天的秒级交易峰值平时的近 ...
- 百亿级别数据量,又需要秒级响应的案例,需要什么系统支持呢?下面介绍下大数据实时分析工具Yonghong Z-Suite
Yonghong Z-Suite 除了提供优秀的前端BI工具之外,Yonghong Z-Suite让用户可以选购分布式数据集市来支持实时大数据分析. 对于这种百亿级的大数据案例,Yonghong Z- ...
随机推荐
- SVN部署(基于Linux)
第一步:通过yum命令安装svnserve,命令如下: yum -y install subversion 此命令会全自动安装svn服务器相关服务和依赖,安装完成会自动停止命令运行 若需查看svn安装 ...
- js中submit和button的区别
今天写一个js验证 遇到点小坑 记录一下 button-普通按钮,submit-提交按钮. submit是button的一个特例,也是button的一种,它把提交这个动作自动集成了,submit和bu ...
- 后台模板引擎ejs与前台模板引擎artTemplate的简单介绍
动态网页是指前端页面当中的数据内容来源于后台数据库,前端的html代码会随着后台数据的变化而变化,是动态生成的.制作动态网页有两种方式,一种方式是在后台拿到前端的html模板,利用后台模板引擎(如ej ...
- spring cloud alibaba 简介
### Spring Cloud Alibaba [官方github地址](https://github.com/alibaba/spring-cloud-alibaba) Spring Cloud ...
- Java中String类的特殊性
java中特殊的String类型 Java中String是一个特殊的包装类数据有两种创建形式: String s = "abc"; String s = new String(&q ...
- python问题:IndentationError:expected an indented block
Python语言是一款对缩进非常敏感的语言,给很多初学者带来了困惑,即便是很有经验的Python程序员,也可能陷入陷阱当中.最常见的情况是tab和空格的混用会导致错误,或者缩进不对,而这是用肉眼无法分 ...
- 小白学 Python(8):基础流程控制(下)
人生苦短,我选Python 前文传送门 小白学 Python(1):开篇 小白学 Python(2):基础数据类型(上) 小白学 Python(3):基础数据类型(下) 小白学 Python(4):变 ...
- <编译原理 - 函数绘图语言解释器(1)词法分析器 - python>
<编译原理 - 函数绘图语言解释器(1)词法分析器 - python> 背景 编译原理上机实现一个对函数绘图语言的解释器 - 用除C外的不同种语言实现 解释器分为三个实现块: 词法分析器: ...
- 部署acfs笔记
acfs问题分析 环境描述 某电力项目创建了两个磁盘组,分别是OGGEXT和OGGREP,利用这两个磁盘组划分了两个acfs文件系统,之后,cloud监控就一直在报磁盘空间不足,但是这两个文件系统的使 ...
- ASP.NET Core如何使用压缩中间件提高Web应用程序性能
前言 压缩可以大大的降低我们Web服务器的响应速度,压缩从而提高我们网页的加载速度,以及节省一定的带宽. 何时使用相应压缩中间件 在IIS,Apache,Nginx中使用基于服务端的响应压缩技术.中间 ...