業務背景
##框架及對應環境
問題背景
#php已經開啟opcache, laravel也運行了optimize命令進行優化, composer也進行過dump-autoload命令.首先需要聲明的是, 系統的環境中是一定有小問題的(沒有問題也不可能能夠提升如此大的性能), 但是這些問題, 如果不通過使用合適的工具, 可能一輩子也發現不出來.本文關注的就是如何發現這些問題, 以及發現問題的思路.我們先找到系統中一個合適的API或函數, 用來放大問題.這個api設計之初是給nginx負載均衡做健康檢查的. 使用ab -n 100000 -c 1000 進行壓測, 發現qps只能到140個每秒.我們知道Laravel的性能是出了名的不好, 但是也不至於到這個程度, 從api的編寫來看不應該這麼低. 所以決定一探究竟.public function getActivateStatus() { try { $result = \DB::select('select 1'); $key = 1; if ($result[0]->$key !== 1) { throw new \Exception("mysql 检查失败"); } } catch (\Exception $exception) { \Log::critical("数据库连接失败: {$exception->getMessage()}", $exception->getTrace()); return \response(null, 500); } try { Cache::getRedis()->connection()->exists("1"); } catch (\Exception $exception) { \Log::critical("缓存连接失败: {$exception->getMessage()}", $exception->getTrace()); return \response(null, 500); } return \response(null, 204); }
%CPU -- CPU usage The task's share of the elapsed CPU time since the last screen update, expressed as a percentage of total CPU time.
#pidstat##先選出一個php-fpm行程, 然後使用pidstat查看進程詳細的運行情況
過程中也沒發現什麼異樣, 並且和top命令的運行結果也基本一致.##vmstat
保持壓測壓力, 運行vmstate查看, 除了context switch (上下文切換)有點高之外, 並沒有看到太多異常. 由於我們使用的docker, redis, mysql都運行在同一台機器上, 7000左右的CS還是一個合理的範圍, 但是這個IN(中斷)就有點太高了, 達到了1.4萬左右. 一定有什麼東西觸發中斷.
我們知道中斷有硬中斷和軟中斷, 硬中斷是由網卡, 滑鼠等硬體發出中斷訊號, cpu馬上停下在做的事情,處理中斷訊號. 軟中斷是由作業系統發出的, 常用於進程的強制調度.
不管是vmstat還是pidstat都只是新能探測工具, 我們無法看到具體的中斷是由誰發出的. 我們透過/proc/interrupts 這個只讀檔案中讀取系統的中斷訊息, 取得到底是什麼導致的中斷升高. 透過watch -d指令, 判斷變化最頻繁的中斷.watch -d cat /proc/interrupts
我們發現其中Rescheduling interrupts變化的最快, 這個是重調度中斷(RES),這個中斷類型表示,喚醒空閒狀態的CPU 來調度新的任務運行。這是多處理器系統(SMP)中,調度器用來分散任務到不同 CPU的機制,通常也稱為處理器間中斷(Inter-Processor Interrupts,IPI)。 結合vmstat中的命令, 我們可以確定造成qps不高的原因之一是過多的進程爭搶CPU導致的, 我們現在還不能確定具體是什麼, 所以還需要進一步的排查.
strace
strace可以查看系統呼叫, 我們知道, 當使用系統呼叫的時候, 系統陷入核心態, 這個過程是會產生軟中斷的, 透過查看php-fpm的系統呼叫, 驗證我們的猜想
果然, 发现大量的stat系统调用, 我们猜想, 是opcache在检查文件是否过期导致的. 我们通过修改opcache的配置, 让opcache更少的检查文件timestamp, 减少这种系统调用
opcache.validate_timestamps="60" opcache.revalidate_freq="0"
再次执行ab命令进行压测
果然qps直接涨到了205, 提升非常明显, 有接近 46% 的提升
perf
现在任然不满足这个性能, 希望在更多地方找到突破口. 通过
perf record -g perf report -g
看到系统的分析报告
我们看到, 好像这里面有太多tcp建立相关的系统调用(具体是不是我还不清楚, 请大神指正, 但是看到send, ip, tcp啥的我就怀疑可能是tcp/ip相关的问题).
我们怀疑两种情况
与mysql, redis重复大量的建立TCP连接, 消耗资源
大量请求带来的tcp连接
先说第一个, 经过检查, 发现数据库连接使用了php-fpm的连接池, 但是redis连接没有, redis用的predis, 这个是一个纯PHP实现, 性能不高, 换成了phpredis:
打开laravel的config/database.php文件, 修改redis的driver为phpredis, 确保本机已安装php的redis扩展. 另外由于Laravel自己封装了一个Redis门面, 而恰好redis扩展带来的对象名也叫Redis. 所以需要修改Laravel的Redis门面为其他名字, 如RedisL5.
再次进行压测
达到了喜人的286qps, 虽然和其他主打高性能的框架或者原生php比, 还有很高的提升空间(比如Swoole), 但是最终达到了104%的提升, 还是很有意义的
总结
我们通过top, 发现系统CPU占用高, 且发现确实是php-fpm进程占用了CPU资源, 判断系统瓶颈来自于PHP.
接着我们通过pidstat, vmstat发现压测过程中, 出现了大量的系统中断, 并通过 watch -d cat /proc/interrupts 发现主要的中断来自于重调度中断(RES)
通过strace查看具体的系统调用, 发现大量的系统调用来自于stat, 猜测可能是opcache频繁的检查时间戳, 判断文件修改. 通过修改配置项, 达到了46%的性能提升
最后再通过perf, 查看函数调用栈, 分析得到, 可能是大量的与redis的TCP连接带来不必要的资源消耗. 通过安装redis扩展, 以及使用phpredis来驱动Laravel的redis缓存, 提升性能, 达到了又一次近50%的性能提升.
最终我们完成了我们的性能提升104%的目标
推荐教程:网站高并发架设基础教程
以上是PHP並發效能調優實戰(效能提升104%)的詳細內容。更多資訊請關注PHP中文網其他相關文章!