記一次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文章。
總結
自己日常遇到這種問題的機率特別少,所以一般都不注重應用整體性能,這次遇到些問題,收穫頗多,也讓自己明白性能,監控的重要性,同時自己有機會去做這種事情,挺有興趣和熱情,而且有信心做好。
這篇文章更重要的是紀錄,不涉及到什麼技術相關,後面抽空找些技術例子與大家分享。