記一次PHP并發(fā)性能調(diào)優(yōu)實戰(zhàn) -- 性能提升104%

文章編寫計劃

待完成: 詳細(xì)介紹用到的各個工具

作者: 萬千鈞(祝星)

適合閱讀人群

文中的調(diào)優(yōu)思路無論是php, java, 還是其他任何語言都是用. 如果你有php使用經(jīng)驗, 那肯定就更好了

業(yè)務(wù)背景

框架及相應(yīng)環(huán)境

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

問題背景

php已經(jīng)開啟opcache, laravel也運行了optimize命令進(jìn)行優(yōu)化, composer也進(jìn)行過dump-autoload命令.

首先需要聲明的是, 系統(tǒng)的環(huán)境中是一定有小問題的(沒有問題也不可能能夠提升如此大的性能), 但是這些問題, 如果不通過使用合適的工具, 可能一輩子也發(fā)現(xiàn)不出來.

本文關(guān)注的就是如何發(fā)現(xiàn)這些問題, 以及發(fā)現(xiàn)問題的思路.

我們首先找到系統(tǒng)中一個合適的API或函數(shù), 用來放大問題.

這個api設(shè)計之初是給nginx負(fù)載均衡做健康檢查的. 使用ab -n 100000 -c 1000 進(jìn)行壓測, 發(fā)現(xiàn)qps只能到140個每秒.

我們知道Laravel的性能是出了名的不好, 但是也不至于到這個程度, 從api的編寫來看不應(yīng)該這么低. 所以決定一探究竟.

 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("數(shù)據(jù)庫連接失敗: {$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);
    }

問題表現(xiàn)以及排查思路

top

top命令發(fā)現(xiàn)系統(tǒng)CPU占用100% 其中用戶態(tài)占80%, 內(nèi)核態(tài)占20%, 看起來沒什么大問題. 有一個地方看起來很奇怪, top命令的運行結(jié)果

top命令運行結(jié)果

<figcaption style="box-sizing: border-box; display: block; text-align: center; font-size: 0.8em; line-height: 2em; color: rgb(144, 144, 144);"></figcaption>

就是有一部分php-fpm進(jìn)程處在Sleep狀態(tài), 但CPU占用還是達(dá)到了近30%. 當(dāng)一個進(jìn)程處于Sleep狀態(tài)的時候, 任然占用了不少CPU, 先不要懷疑是不是進(jìn)程的問題, 我們看一下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.

大致意思是這個占用是最后一次屏幕刷新的時候, 進(jìn)程CPU的占用. 由于top命令收集信息的時候, 可能linux把這個進(jìn)程強(qiáng)制調(diào)度了 ( 比如用于top收集進(jìn)程信息 ), 所以在這一瞬間(屏幕刷新的這一瞬間)某些php-fpm進(jìn)程處于sleep狀態(tài), 可以理解, 所以應(yīng)該不是php-fpm的問題.

pidstat

首先選出一個php-fpm進(jìn)程, 然后使用pidstat查看進(jìn)程詳細(xì)的運行情況

image

<figcaption style="box-sizing: border-box; display: block; text-align: center; font-size: 0.8em; line-height: 2em; color: rgb(144, 144, 144);"></figcaption>

過程中也沒發(fā)現(xiàn)什么異樣, 并且和top命令的運行結(jié)果也基本一致.

vmstat

保持壓測壓力, 運行vmstate查看, 除了context switch (上下文切換)有點高之外, 并沒有看到太多異常. 由于我們使用的docker, redis, mysql都運行在同一臺機(jī)器上, 7000左右的CS還是一個合理的范圍, 但是這個IN(中斷)就有點太高了, 達(dá)到了1.4萬左右. 一定有什么東西觸發(fā)了中斷.

image

<figcaption style="box-sizing: border-box; display: block; text-align: center; font-size: 0.8em; line-height: 2em; color: rgb(144, 144, 144);"></figcaption>

我們知道中斷有硬中斷和軟中斷, 硬中斷是由網(wǎng)卡, 鼠標(biāo)等硬件發(fā)出中斷信號, cpu馬上停下在做的事情, 處理中斷信號. 軟中斷是由操作系統(tǒng)發(fā)出的, 常用于進(jìn)程的強(qiáng)制調(diào)度.

不管是vmstat還是pidstat都只是新能探測工具, 我們無法看到具體的中斷是由誰發(fā)出的. 我們通過/proc/interrupts 這個只讀文件中讀取系統(tǒng)的中斷信息, 獲取到底是什么導(dǎo)致的中斷升高. 通過watch -d命令, 判斷變化最頻繁的中斷.

watch -d cat /proc/interrupts

image

<figcaption style="box-sizing: border-box; display: block; text-align: center; font-size: 0.8em; line-height: 2em; color: rgb(144, 144, 144);"></figcaption>

我們發(fā)現(xiàn)其中Rescheduling interrupts變化的最快, 這個是重調(diào)度中斷(RES)雾鬼,這個中斷類型表示金刁,喚醒空閑狀態(tài)的CPU 來調(diào)度新的任務(wù)運行挤悉。這是多處理器系統(tǒng)(SMP)中,調(diào)度器用來分散任務(wù)到不同 CPU的機(jī)制撩穿,通常也被稱為處理器間中斷(Inter-Processor Interrupts,IPI)。 結(jié)合vmstat中的命令, 我們可以確定造成qps不高的原因之一是過多的進(jìn)程爭搶CPU導(dǎo)致的, 我們現(xiàn)在還不能確定具體是什么, 所以還需要進(jìn)一步的排查.

strace

strace可以查看系統(tǒng)調(diào)用, 我們知道, 當(dāng)使用系統(tǒng)調(diào)用的時候, 系統(tǒng)陷入內(nèi)核態(tài), 這個過程是會產(chǎn)生軟中斷的, 通過查看php-fpm的系統(tǒng)調(diào)用, 驗證我們的猜想

image

<figcaption style="box-sizing: border-box; display: block; text-align: center; font-size: 0.8em; line-height: 2em; color: rgb(144, 144, 144);"></figcaption>

果然, 發(fā)現(xiàn)大量的stat系統(tǒng)調(diào)用, 我們猜想, 是opcache在檢查文件是否過期導(dǎo)致的. 我們通過修改opcache的配置, 讓opcache更少的檢查文件timestamp, 減少這種系統(tǒng)調(diào)用

    opcache.validate_timestamps="60"
    opcache.revalidate_freq="0"

再次執(zhí)行ab命令進(jìn)行壓測

image

<figcaption style="box-sizing: border-box; display: block; text-align: center; font-size: 0.8em; line-height: 2em; color: rgb(144, 144, 144);"></figcaption>

果然qps直接漲到了205, 提升非常明顯, 有接近 46% 的提升

perf

現(xiàn)在任然不滿足這個性能, 希望在更多地方找到突破口. 通過

perf record -g
perf report -g

看到系統(tǒng)的分析報告

image

<figcaption style="box-sizing: border-box; display: block; text-align: center; font-size: 0.8em; line-height: 2em; color: rgb(144, 144, 144);"></figcaption>

我們看到, 好像這里面有太多tcp建立相關(guān)的系統(tǒng)調(diào)用(具體是不是我還不清楚, 請大神指正, 但是看到send, ip, tcp啥的我就懷疑可能是tcp/ip相關(guān)的問題). 我們懷疑兩種情況

  1. 與mysql, redis重復(fù)大量的建立TCP連接, 消耗資源
  2. 大量請求帶來的tcp連接

先說第一個, 經(jīng)過檢查, 發(fā)現(xiàn)數(shù)據(jù)庫連接使用了php-fpm的連接池, 但是redis連接沒有, redis用的predis, 這個是一個純PHP實現(xiàn), 性能不高, 換成了phpredis:

打開laravel的config/database.php文件, 修改redis的driver為phpredis, 確保本機(jī)已安裝php的redis擴(kuò)展. 另外由于Laravel自己封裝了一個Redis門面, 而恰好redis擴(kuò)展帶來的對象名也叫Redis. 所以需要修改Laravel的Redis門面為其他名字, 如RedisL5.

再次進(jìn)行壓測

image

<figcaption style="box-sizing: border-box; display: block; text-align: center; font-size: 0.8em; line-height: 2em; color: rgb(144, 144, 144);"></figcaption>

達(dá)到了喜人的286qps, 雖然和其他主打高性能的框架或者原生php比, 還有很高的提升空間(比如Swoole), 但是最終達(dá)到了104%的提升, 還是很有意義的

總結(jié)

我們通過top, 發(fā)現(xiàn)系統(tǒng)CPU占用高, 且發(fā)現(xiàn)確實是php-fpm進(jìn)程占用了CPU資源, 判斷系統(tǒng)瓶頸來自于PHP.

接著我們通過pidstat, vmstat發(fā)現(xiàn)壓測過程中, 出現(xiàn)了大量的系統(tǒng)中斷, 并通過 watch -d cat /proc/interrupts 發(fā)現(xiàn)主要的中斷來自于重調(diào)度中斷(RES)

通過strace查看具體的系統(tǒng)調(diào)用, 發(fā)現(xiàn)大量的系統(tǒng)調(diào)用來自于stat, 猜測可能是opcache頻繁的檢查時間戳, 判斷文件修改. 通過修改配置項, 達(dá)到了46%的性能提升

最后再通過perf, 查看函數(shù)調(diào)用棧, 分析得到, 可能是大量的與redis的TCP連接帶來不必要的資源消耗. 通過安裝redis擴(kuò)展, 以及使用phpredis來驅(qū)動Laravel的redis緩存, 提升性能, 達(dá)到了又一次近50%的性能提升.

最終我們完成了我們的性能提升104%的目標(biāo)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末锐想,一起剝皮案震驚了整個濱河市融蹂,隨后出現(xiàn)的幾起案子旺订,更是在濱河造成了極大的恐慌,老刑警劉巖超燃,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件区拳,死亡現(xiàn)場離奇詭異,居然都是意外死亡意乓,警方通過查閱死者的電腦和手機(jī)樱调,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來届良,“玉大人本涕,你說我怎么就攤上這事』锴裕” “怎么了菩颖?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長为障。 經(jīng)常有香客問我晦闰,道長,這世上最難降的妖魔是什么鳍怨? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任呻右,我火速辦了婚禮,結(jié)果婚禮上鞋喇,老公的妹妹穿的比我還像新娘声滥。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布落塑。 她就那樣靜靜地躺著纽疟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪憾赁。 梳的紋絲不亂的頭發(fā)上污朽,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天,我揣著相機(jī)與錄音龙考,去河邊找鬼蟆肆。 笑死,一個胖子當(dāng)著我的面吹牛晦款,可吹牛的內(nèi)容都是我干的炎功。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼缓溅,長吁一口氣:“原來是場噩夢啊……” “哼亡问!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起肛宋,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤州藕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后酝陈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體床玻,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年沉帮,在試婚紗的時候發(fā)現(xiàn)自己被綠了锈死。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡穆壕,死狀恐怖待牵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情喇勋,我是刑警寧澤缨该,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站川背,受9級特大地震影響贰拿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜熄云,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一膨更、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧缴允,春花似錦荚守、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锈候。三九已至,卻和暖如春缩功,著一層夾襖步出監(jiān)牢的瞬間晴及,已是汗流浹背都办。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工嫡锌, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人琳钉。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親晾匠。 傳聞我的和親對象是個殘疾皇子匾旭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容