記一次WEB服務器性能優化

記一次WEB服務器性能優化

上個週末,自己做的一個業務後端,因爲流量“過大”導致大部分請求在正常時間內都無法完成響應,故障持續了1個小時,中途只能調低nginx的連接上限,讓部分人能夠訪問。

這個業務後端上線一個多月,那一天的流量大約佔之前所有流量的1/3,而且集中在那麼幾個小時內,QPS壓力應該是平時的幾十倍,所以沒抗住…

不管怎樣,這是自己的鍋,在實現這個業務後端的時候沒有謹慎的考慮性能問題,所以當高峯流量過後就開始分析問題以給性能優化提供幫助,也爲下次的流量高峯做準備。

業務後端概述

這個業務後端主要由Nginx、PM2、Node.js、MySQL、Redis、OSS組成,下面分別描述一下它們在系統中扮演的角色:

  • Nginx

除了後臺管理系統的靜態文件,業務本身的靜態文件都是放在OSS上,所以Nginx在這裏的作用只是作爲反向代理

  • PM2

Node.js的進程管理,使用默認配置,沒有其他插件

  • Node.js

沒有使用WEB框架,主要就是業務實現,合計約100個接口

  • MySQL

所有數據都在MySQL中,整個系統一共用到約30張表,表的數據量都還不大,最大的一張表約40W條記錄

  • Redis

Redis在未優化之前,主要用來存session,以及之前一篇文章說講述的的隊列數據

  • OSS

靜態資源都存放在阿里雲的OSS上,這部分流量完全不經過機器,跟系統負載沒有關係

以上系統組成中,MySQL用的阿里雲RDS服務(基礎版1核1G),其他除OSS外都部署在同一臺阿里雲ECS(1核2G)上。

整個後端系統大約花2-3周做出來的,沒有壓力測試,沒有性能指標等,也沒有特意去優化,並且自己心中知道有部分邏輯不合理,有量的時候應該會出問題。

分析現狀

問題過後,要做的第一件事是分析現狀,主要包括以下內容:

  • 故障原因
  • 量化本次高峯流量
  • 尋找系統保持正常負載的極限QPS
  • 尋找API熱點

故障原因

導致此次故障的原因即此次的性能瓶頸,主要是MySQL性能問題,CPU 95%以上,基本處於死機狀態,大量的SQL查詢超時導致客戶端請求超時。

導致MySQL性能跟不上,原因是SQL不合理、設計不合理這兩個點,本次優化也就着重這兩部分內容。

關於兩種不合理的簡單理解:

  • SQL不合理

SQL不合理比較容易理解,也就是SQL語句寫的不好,而SQL語句的好與不好又與表設計(索引、表結構、表關係)相關,一般表關係與表結構都不能輕易調整,所以更多的是優化索引與SQL語句

  • 設計不合理

設計不合理主要是指業務邏輯實現不合理,這裏主要是指跟MySQL有關的邏輯,比如最簡單的翻頁查詢order by & limit,哪怕SQL優化的很好,在數據多一點場景下還是會對DB造成過多壓力,而DB往往又是單點,所以整個系統的QPS就上不去

解決設計不合理的問題核心就是加緩存,減輕DB壓力,加緩存是個非常細膩的活,馬虎不得,需要仔細思考

這次優化,針對這兩個點都有所作爲,效果很不錯,限於篇幅下次再來講述。

量化本次高峯流量

知道故障原因還不能馬上進行優化,因爲有量化手段才能統計後續優化的效果,以及制定優化的目標。

前文提到過系統沒有任何監控,所以最初收集到的信息只有:

問題出現的半小時前,Nginx的連接數約500-600,此時系統正常,CPU負載25%左右,MySQL沒有收集到當時的負載信息

收到故障反饋時,Nginx的連接數大約1000,整個系統已經很難正常響應請求,MySQL CPU 95%+,由於是IO導致系統性能瓶頸,機器CPU保持在30%以下。

高峯期之後從訪問日誌中分析出當時系統的QPS峯值:124,我知道這個QPS很小,但系統就是沒扛住,有經驗的朋友不要笑~

極限QPS

統計和分析QPS的時候,按時段做了一張表:

時段 訪問量 峯值時間 峯值QPS 平均響應時長(ms) 響應>1s 出錯請求數
18-19 7761 18:02:50 12 23.4577 4(0.05%) 6
19-20 37320 19:40:41 41 26.4526 2(0.00%) 8
20-21 58060 20:01:40 42 30.581 60(0.10%) 253
21-22 61457 21:53:58 66 30.7309 24(0.03%) 41
22-23 116568 22:56:52 81 550.192 4910(4.21%) 1348
23-24 116506 23:51:33 124 3916.72 49725(42.68%) 8456

大約22:30的時候,我在那臺機器上觀察了一會Nginx連接數與CPU負載,比較正常,知道有一定的流量,然後在23點的時候,收到用戶反饋說打不開應用。

從表中可以看到21-22點的QPS峯值是66,在22:56時QPS達到另一個峯值81,隨後系統進入異常狀態,可以猜測系統的正常QPS極限應該在66-81之間。

熱點API

上面的流量高峯和極限QPS都是針對系統所有API的一個平均值,而實際上各API的訪問頻率、次數是有差異的,所以爲了後續的優化,在這裏統計了一下當天的API訪問次數,選出前十作爲後續重點優化對象。

url 訪問量 平均響應時長(ms) 響應>1s
/fsfl/api/client/user/detail 101872 651.378 14542(14.27%)
/fsfl/api/client/star/time-history 52766 2.49104 0(0%)
/fsfl/api/client/complex/home?size=50 40925 865.471 5270(12.87%)
/fsfl/api/client/star/week-items?page=0&size=20 40749 5536.49 8668(21.15%)
/fsfl/api/client/user/update 27944 370.269 2834(10.14%)
/fsfl/api/client/star/week-detail/16523?size=20 22448 3073.31 7091(31.58%)
/fsfl/api/client/star/week-detail/32807?size=20 19000 709.773 1433(7.54%)
/fsfl/api/user/login 10778 2026.86 2513(23.31%)
/fsfl/api/client/user/check-state 9536 325.902 603(6.32%)
/fsfl/api/client/user/share-award 7480 572.353 707(9.45%)

QPS那張和這張熱點API表中的數據,都是自己用人工方式從訪問日誌中一個個提取出來的,後續也單獨拿出來講講如何提取,但這裏自己在提取的時候,忘了統計單個API峯值QPS,過了一週也不想回頭再去統計。

表中/fsfl/api/client/star/time-history這個接口容易引起注意,這個接口是一個非常簡單的代碼執行接口,不涉及任何IO,後續也選它作爲測試的基準。

wrk單獨測了測這個接口,在那臺機器上的QPS極限大約600-700,後面把機器升級成4核4G後,極限QPS在2000左右,作爲基準,所有其他業務API在優化後,以這個數值爲參考,能達到1/5我覺得都算是及格,實際情況是優化的大部分接口能達到1/3,也就是600-700的QPS。

優化點

整個系統的優化點主要有四個:

  • Node.js加緩存
  • SQL優化
  • 硬件升級
  • 水平擴展

緩存與SQL優化

優化的核心是緩存與SQL,也就是解決前文的SQL不合理與設計不合理,尋找的過程是從API的訪問次數入手,優先解決熱點。

按熱點API逐個review其背後的邏輯、SQL並設計緩存,這部分工作我紮紮實實做了三四天,抽空單獨找些例子出來寫成文章講述具體如何做。

在這裏稍微提一下自己在做緩存優化時的基本原則,所有數據依舊以DB爲準,緩存可自動初始化,緩存可隨時被清除而業務依舊保持正常,同時觸發緩存再次初始化,這裏由於不是做非常高可靠服務,暫時沒有考慮緩存穿透等高級內容。

硬件升級

1核的機器,再怎麼優化也會達到機器的CPU瓶頸,尤其是減輕DB壓力後,應用這邊的壓力也會有所增加,所以機器以及MySQL的硬件都選擇進行升級,最後選擇機器升級成4核4G,MySQL升級成2核4G。

機器升級成4核,單API的QPS並不會提升4倍,就比如前文QPS最高的接口就只是由650提升到2000,這個是正常的,因爲Nginx,PM2這些在多進程模式下“調度”本身會帶來CPU開銷,還有就是操作系統層面的問題,進程不是跟CPU綁定在一起的,執行的時候會涉及更多的上下文切換等工作(這部分內容我沒深入考究,只是憑經驗所寫)。

水平擴展

這個由於使用Node.js實現業務的時候就就只依賴兩個單點:MySQL、Redis,所以實現水平擴展比較容易,但靠這一次的緩存優化+SQL優化+升級硬件就可以滿足預期,所以就暫時沒有考慮部署多臺機器。

優化成果

大部分API在優化之前的QPS都是2位數,經過SQL優化與設計緩存,最終保證了熱點API的QPS大致在400-700之間,優化後的API,QPS上不去的原因都是是帶寬與機器CPU

優化後系統整體QPS提升估計在4-5倍,而且減緩了DB的壓力,當再出現性能問題時就考慮用加機器與加帶寬的方式解決CPU瓶頸。

剩餘問題與優化空間

問題

也許QPS再次超出這次極限兩三倍,可以用加機器和帶寬的方式解決,但隨即可能冒出其他問題,因爲這次的優化着重是考慮所有熱點API其背後的邏輯優化,但剩餘的大部分非熱點API在整體訪問QPS的的提升過程中所面臨的問題依舊是嚴峻的。

比如系統在一秒內完成了200個請求,也許這200請求裏面有70%是熱點API,剩下的30%是幾個非熱點API,假設其中一個非熱點API佔30%中的40%,也就是這個API的QPS最起碼要達到24,系統才能正常運轉,如果這個API依賴DB,當平均QPS上升到400時,DB有可能就垮掉了,然後整個系統也就處於異常狀態了。

依賴DB並且QPS不高,這種接口目前肯定是還存在系統當中,比如有幾個接口背後用了事務與select for update,如果量繼續創新高的話,是有可能帶來問題的。

要解決這個問題,依賴一定的預知判斷力,以及反饋機制,比如多觀察MySQL的負載情況,機器的負載情況等,有精力、能力的話是時候考慮一下監控這一塊,簡單實現,滿足自己需求即可。

優化空間

上述提到的問題,其實就是優化空間存在的地方,我覺靠這個模式做到幾千QPS應該不是什麼大問題。

除了上面提到優化方案,我還打算用一下Nginx上的Proxy Cache,這個只能針對部分接口去做,可能還需要前端配合。

從熱門接口中區分出不需要非常實時的一類,也許就是延遲1s,然後利用Nginx上的Proxy Cache,這個能帶來的性能提升是非常顯著的,具體可以參考這篇Nginx文章

總結

自己日常遇到這種問題的機率特別少,所以一般都不注重應用整體性能,這次遇到些問題,收穫頗多,也讓自己明白性能,監控的重要性,同時自己有機會去做這種事情,挺有興趣和熱情,而且有信心做好。

這篇文章更重要的是紀錄,不涉及到什麼技術相關,後面抽空找些技術例子與大家分享。

博客原文

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章