PHPer 确定收到过这样的投诉:小菊花一直在转!大家网站怎么这么卡!当咱们线上业务遇到这种卡住(阻塞)的状况,大部分 PHPer 会两眼一抹黑,随后想起那句名言:性能瓶颈都在数据库
而后把锅甩给DBA,赶忙找找慢sql,但这是很是错误的作法,由于有太多因素能致使业务卡住,下面列举几种常见的卡住问题。php
最多见的就是写出了死循环代码html
<?php while(1){ //do something if($condition){ //知足条件后退出循环 break; } }
上述代码经过$condition
控制循环退出,若是程序验证不严格,某些状况$condition
永远为真就会致使请求卡死。前端
PHP的session锁等待(ps:不少地方叫作session死锁,这不太符合死锁定义),这个相信大部分PHPer都遇到过,PHP默认会把session信息存储在/tmp/sess_
下面的session文件里面,调用session_start()
函数的时候会调用flock
系统调用给session文件加锁,若是前一个请求没有结束或者手动释放session就会致使后面的请求没法得到锁,卡死在session_start()
这个地方。下面举个例子,好比这种代码:laravel
setInterval(function () { $.post("/ajax/doSomething", {}, function (result) {//1s进行一次ajax }); }, 1000)//1000ms == 1s
前端js定时经过ajax请求一下后端PHP的接口(/ajax/doSomething
)作一些比较耗时的事情,写代码的人可能想固然的认为第一次的请求即便没有处理完,也不会影响第二次的请求,由于有不少的FPM进程每次请求会分发到不通的进程,但却不知第二次请求会卡死在session_start()
。web
最多见的场景就是写日志,在PHP代码中确保每次fwrite
写的日志内容小于8k的状况下咱们能够利用append原子追加方式写日志,可是若是保证不了小于8k咱们就须要在每次写日志前给文件加文件锁来避免两第二天志间产生穿插的状况,代码以下:ajax
<?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掉,系统会自动释放这个文件锁sql
注意还有不少其余类型的锁即便进程被kill也不会自动被释放。
这个8k是能够改的,和glibc中的fwrite不少细节也不同.这个在咱们的swoole课堂上会补充,11年架构师授课的TP五、laravel、swoole、swoft、高并发、,官方群:677079770 ,大牛带你飞 ,PHP/web从入门到架构 722584796
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."; }
在 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进程到底阻塞在哪一个系统调用上面来定位问题,但这种方式有几个问题:后端
futex(0x7f4c8d567128, FUTEX_WAIT, 2, NULL)
这种信息,很是的不直观,不少人根本不知道哪些PHP代码会触发futex
系统调用,还有前文提到session_start
那个问题,不少人根本不知道这里会触发flock
,也就说很难根据一个系统调用定位到具体问题。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
打印当前执行的行数。 .gdbinit
能稍微减小点难度,可是也有不少其余问题)。针对上述问题,Swoole官方出了一个解决方案 Swoole Tracker 的堆栈工具,同时支持FPM和Swoole。
使用方法很简单:
swoole_tracker
扩展。调试器
=>进程列表
中点击堆栈
按钮就能得到当前卡在哪了,如图:除了上面的卡死问题,还有一种状况是调用变慢,好比原来一个系统调用5ms,可是因为网络等等缘由,这个调用100ms才返回,业务的表现是变慢了而不是卡死在那里,这种状况经过tracker的抓堆栈工具是没法定位问题的,由于卡住时间很短,很难抓到调用堆栈,此时须要Swoole工具链中的另一个工具阻塞IO检测工具
咱们会在后面给你们介绍。