記一次PHP併發性能調優實戰 -- 性能提高104%

文章編寫計劃

待完成: 詳細介紹用到的各個工具php

做者: 萬千鈞(祝星)java

適合閱讀人羣

文中的調優思路不管是php, java, 仍是其餘任何語言都是用. 若是你有php使用經驗, 那確定就更好了mysql

業務背景

框架及相應環境

  1. laravel5.7, mysql5.7, redis5, nginx1.15
  2. centos 7.5 bbr
  3. docker, docker-compose
  4. 阿里雲 4C和8G

問題背景

php已經開啓opcache, laravel也運行了optimize命令進行優化, composer也進行過dump-autoload命令.linux

首先須要聲明的是, 系統的環境中是必定有小問題的(沒有問題也不可能可以提高如此大的性能), 可是這些問題, 若是不經過使用合適的工具, 可能一生也發現不出來.nginx

本文關注的就是如何發現這些問題, 以及發現問題的思路.laravel

咱們首先找到系統中一個合適的API或函數, 用來放大問題.redis

這個api設計之初是給nginx負載均衡作健康檢查的. 使用ab -n 100000 -c 1000 進行壓測, 發現qps只能到140個每秒.sql

咱們知道Laravel的性能是出了名的很差, 可是也不至於到這個程度, 從api的編寫來看不該該這麼低. 因此決定一探究竟.docker

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);
    }
複製代碼

問題表現以及排查思路

top

top命令發現系統CPU佔用100% 其中用戶態佔80%, 內核態佔20%, 看起來沒什麼大問題. 有一個地方看起來很奇怪, top命令的運行結果數據庫

top命令運行結果
就是有一部分php-fpm進程處在Sleep狀態, 但CPU佔用仍是達到了近30%. 當一個進程處於Sleep狀態的時候, 任然佔用了很多CPU, 先不要懷疑是否是進程的問題, 咱們看一下Ttop命令的man page.

%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.
複製代碼

大體意思是這個佔用是最後一次屏幕刷新的時候, 進程CPU的佔用. 因爲top命令收集信息的時候, 可能linux把這個進程強制調度了 ( 好比用於top收集進程信息 ), 因此在這一瞬間(屏幕刷新的這一瞬間)某些php-fpm進程處於sleep狀態, 能夠理解, 因此應該不是php-fpm的問題.

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相關的問題). 咱們懷疑兩種狀況

  1. 與mysql, redis重複大量的創建TCP鏈接, 消耗資源
  2. 大量請求帶來的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%的目標

相關文章
相關標籤/搜索