PHPer 肯定收到过这样的投诉:小菊花一直在转!你们网站怎么这么卡!当我们线上业务遇到这种卡住(阻塞)的情况,大部分 PHPer 会两眼一抹黑,随后想起那句名言:性能瓶颈都在数据库然后把锅甩给DBA,赶紧找找慢sql,但这是非常错误的做法,因为有太多因素能导致业务卡住,下面列举几种常见的卡住问题。

1.死循环

最常见的就是写出了死循环代码

  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()这个地方。下面举个例子,比如这种代码:

  1.  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检测工具我们会在后面给大家介绍。

教你使用 Swoole-Tracker 秒级定位 PHP 卡死问题的更多相关文章

  1. PHP 教你使用 Swoole-Tracker 秒级定位 PHP 卡死问题

    PHPer 肯定收到过这样的投诉:小菊花一直在转!你们网站怎么这么卡!当我们线上业务遇到这种卡住(阻塞)的情况,大部分 PHPer 会两眼一抹黑,随后想起那句名言:性能瓶颈都在数据库然后把锅甩给DBA ...

  2. 基于Flink秒级计算时CPU监控图表数据中断问题

     基于Flink进行秒级计算时,发现监控图表中CPU有数据中断现象,通过一段时间的跟踪定位,该问题目前已得到有效解决,以下是解决思路:   一.问题现象       以SQL02为例,发现本来10秒一 ...

  3. CCKiller:Linux轻量级CC攻击防御工具,秒级检查、自动拉黑和释放 《CCKiller:Linux轻量级CC攻击防御工具,秒级检查、自动拉黑和释放》来自张戈博客

    张戈博客很久以前分享过一个CC攻击的防御脚本,写得不怎么样,不过被51CTO意外转载了.博客从此走上了经常被人拿来练手的不归之路. 当然,还是有不少朋友在生产环境使用,并且会留言询问相关问题.根据这些 ...

  4. 软件架构自学笔记-- 转载“虎牙在全球 DNS 秒级生效上的实践”

    虎牙在全球 DNS 秒级生效上的实践 这次分享的是全球 DNS 秒级生效在虎牙的实践,以及由此产生的一些思考,整体上,分为以下 5 各部分: 背景介绍: 方案设计和对比: 高可用: 具体实践和落地: ...

  5. php Swoole实现毫秒级定时任务

    项目开发中,如果有定时任务的业务要求,我们会使用linux的crontab来解决,但是它的最小粒度是分钟级别,如果要求粒度是秒级别的,甚至毫秒级别的,crontab就无法满足,值得庆幸的是swoole ...

  6. Swoft 2.0.5 更新,新增高效秒级定时任务、异常管理组件

    什么是 Swoft ? Swoft 是一款基于 Swoole 扩展实现的 PHP 微服务协程框架.Swoft 能像 Go 一样,内置协程网络服务器及常用的协程客户端且常驻内存,不依赖传统的 PHP-F ...

  7. 《DNS稳定保障系列3--快如闪电,域名解析秒级生效》

    在刚刚过去的双十一,又是一个全民狂欢的盛宴,天猫双十一的成交量高达2684亿.无数小伙伴在淘宝.天猫里买买买,今年你又剁手了多少?言归正传,在你疯狂秒杀的时候,有没有发现,今年的购物体验一如既往的好, ...

  8. 虎牙在全球 DNS 秒级生效上的实践

    本文整理自虎牙中间件团队在 Nacos Meetup 的现场分享,阿里巴巴中间件受权发布. 这次分享的是全球 DNS 秒级生效在虎牙的实践,以及由此产生的一些思考,整体上,分为以下5各部分: 背景介绍 ...

  9. Swoole实现毫秒级定时任务

    项目开发中,如果有定时任务的业务要求,我们会使用linux的crontab来解决,但是它的最小粒度是分钟级别,如果要求粒度是秒级别的,甚至毫秒级别的,crontab就无法满足,值得庆幸的是swoole ...

随机推荐

  1. 权限管理——ACL权限

    权限管理 ACL权限 用于解决用户对文件身份不足 命令:[root@localhost ~]#dumpe2fs -h /dev/sd3 作用:查询指定分区详细的文件系统给信息 选项 -h:仅显示超级块 ...

  2. linux 在切换用户时出现:命令提示符-bash-4.1$错误解决

    有时候在使用用户登陆Linux系统时会发现,命令行提示符成了:-bash-4.1$,不显示用户名,路径信息. 原因:用户家目录里面与环境变量有关的文件被删除所导致的 也就是这俩文件:.bash_pro ...

  3. mssql的text字段中文乱码

    问题: 1.在页面存入中文后乱码,从前端从后台发现数据未发生异常,发现是存入数据库后乱码: 经查询该字段为text字段,存入中文会乱码 如图 解决办法: 1.将text转为varchar或nvarch ...

  4. MySQL数据库(三)锁机制

    MyISAM默认使用表级锁,不支持行级锁 InnoDB默认使用行级锁,也支持表级锁 表级锁:开销小,加锁快:不会出现死锁:锁定粒度大,发生锁冲突的概率最高,并发度最低. 行级锁:开销大,加锁慢:会出现 ...

  5. rabbitmq pika(python)订阅发布多客户端消费场景简单使用

    发布端: import pika import time credentials = pika.credentials.PlainCredentials('root', 'root',erase_on ...

  6. k8s资产清单(二)

    什么是清单 说白了清单是k8s当中用来定义pod的文件,语法格式遵循yaml语法,在yaml当中可以定义控制器类型,元数据,容器端口号等等等....,也可以针对于清单对pod进行删除等操作 为什么太学 ...

  7. react的路由权限控制

    在使用路由的时候,有的时候我们的界面只能够在登录之后才可以看的到,这个时候就需要使用路由权限控制了 找了资料发现一个就是我使用的方法,一个是高阶组件. 原谅菜鸟看不太懂不会使用高阶组件………… 首先在 ...

  8. 1.Java基础_Java核心机制简介

    Java的两种核心机制 Java虚拟机机制 Java垃圾回收机制 解释名词 J2SDK&JRE: J2SDK=JDK=Software Development Kit(软件开发包) JRE=J ...

  9. luoguP4343自动刷题机(二分标准题)

    https://www.luogu.org/problem/P4343 参考博客:https://www.luogu.org/blog/ofnoname/solution-p4343 这真是一语点醒梦 ...

  10. SpringCloud学习笔记(六、SpringCloud Netflix Feign)

    目录: feign简介 feign应用 feign简介: feign是一款Netflix开源的声明式.模板化的http客户端,它可以更加便捷.优雅的调用http api:SpringCloud对Net ...