當服務QPS增高時,我們做什么
- 1 性能的關鍵指標
- 2 服務化系統構成模式
- 請求對系統資源的占用
- 請求對系統資源的占用
- 2.1 基礎服務
- 2.2 集成服務
- 2.3 混合服務
- 混合服務的資源消耗
- 2.4 系統資源消耗
- 3 常見系統優化tips
- 3.1 代碼調優
- 3.2 數據庫調優
- References
這篇文章是在給團隊中級初級開發人員做的分享,相對比較淺。
很多同學在實際的開發中害怕系統的QPS增高,因為覺得QPS太高會導致系統掛掉;基于這種心理會想著盡量的降低系統的請求量,甚至有人會將很多處理放置到服務中來處理,這樣外部發一起請求,服務就把所有的業務處理完了(比如將for循環的計算放置到服務端)。
這種方式降低了系統的請求量,但是降低了系統的QPS嗎?這種做法系統更安全了還是更危險了?
首先來介紹一下基本概念。
1 .性能的關鍵指標
- 系統吞吐量(Throughput)
吞吐量指單位時間內系統處理的請求數量,體現系統的整體處理能力。
- 響應時間(系統延遲Latency)
請求的平均響應時間
一般來說,一個系統的性能收到系統吞吐量和響應時間兩個條件的約束,缺一不可。比如,我的系統可以頂得住一百萬的并發,但是系統的延遲是2分鐘以上,那么,這個一百萬的負載毫無意義。系統延遲很短,但是吞吐量很低,同樣沒有意義。
一般情況下,針對一個系統
• 吞吐量(Throughput)越大,系統延遲(Latency)越差。因為請求量過大,系統太繁忙,所以響應速度自然會低。
• 系統延遲(Latency)越好,能支持的吞吐量(Throughput)就會越高。因為Latency短說明處理速度快,于是就可以處理更多的請求。
• 并發數
系統同時能夠處理的請求數/事務數。
• QPS(也稱TPS,Query per second/transaction per second)
并發數/響應時間
整體來看QPS能夠概括系統吞吐量和延遲兩方面指標,因此也是系統最重要的指標之一。但當系統的QPS升高,到底會對系統產生哪些影響,或者在我們如何避免QPS升高而對系統造成的危害呢?
我們緊接這來看看服務化系統的主要模式及系統資源的消耗。
2. 服務化系統構成模式
2.1 基礎服務

一個最基礎的服務,一般就包含兩種操作:業務邏輯處理和DB的讀寫。
當一個請求發過來的時候,會消耗哪些系統資源呢?
請求對系統資源的占用

當一個請求發過來之后,常規的這個請求會消耗一下資源:CPU(負責計算)、系統內存、網絡鏈接等系統自身資源;如果我們的系統是基于Java的,那還涉及到JVM資源的占用,JVM的heap和stack資源,其中Heap是更重要的指標。如果在這個請求需要與DB有交互,在連接DB進行操作的過程中,會消耗系統的數據庫鏈接池資源。對應的在DB側,會消耗DB的計算資源,而DB的計算最重要的指標就是DB的響應時間和DB的連接數。
2.2 集成服務

這種服務相對基礎服務是另一個極端,這種服務只依賴與其他的服務,并沒有自己的數據。
請求對系統資源的占用

在這個系統里面,我們可以將依賴服務當作DB來看待,只不過在請求的過程中不再消耗系統的數據庫連接池資源。
2.3 混合服務

這種系統結構是我們最常用的結構,既有自身的業務數據,也會有部分計算依賴與其他服務。
混合服務的資源消耗

這種結構里面會集成上面兩種結構的系統消耗。
2.4 系統資源消耗
系統負載
- 系統CPU利用率
如果系統的CPU使用率已經很高,說明我們的系統是個計算度很復雜的系統,這時候如果QPS已經上不去了,就需要趕緊擴容,通過增加機器分擔計算的方式來提高系統的吞吐量。
- 系統內存
如果CPU使用率一般,但是系統的QPS已經負載不了了,說明我們的機器并沒有忙于計算,而是收到其他資源的限制,如內存或者io。這時候首先看下內存是不是已經不夠了,如果內存不夠了,那就趕緊擴容了。
針對Java項目來說,JVM中Heap信息也是內存的一個直接反應,如Java的老年代的內存占比,是否發生Full GC的情況等。
- 系統IO
系統的IO一般是和CPU使用率相反的,CPU利用率高的時候,IO使用率就不大,而IO使用率高的時候CPU一般利用率不高。
- 網絡帶寬(可支持的網絡鏈接數)
當我們自身系統的網絡帶寬被占用完畢的時候,相當于把系統的入口和出口給堵死了,這時候外界的需求排不進來了,QPS自然上不去。
在我們的系統中時常會使用連接池的方式來連接DB,也會使用HTTP連接池的方式向依賴系統發起服務,或者使用線程池的方式提供給其他服務使用。很多時候因為系統的本身連接池自身有***連接數的限制,會導致系統連接數耗盡,單系統其他資源依然屬于正常情況。這時候可以適當增加連接數的方式,來增加系統的吞吐能力,但這種方法需要慎重,因為過多的連接池,會更快的消耗系統資源,并且會將壓力傳遞給依賴系統。
依賴系統的性能
- DB 性能
DB性能很多時候是系統的根本,因為一旦DB出現了大問題,不單單會導致一個系統出問題,很可能會導致所有依賴此DB的系統出現業務邏輯問題。
一般開發在實踐中,遇到最多的問題就是不當的SQL導致DB讀寫性能很低,如未使用索引的讀寫SQL;如數據庫表不適當的鎖范圍;另外,如果DB本身的讀寫已經達到了自身的限度,這時候可以考慮更換機器,更換系統的硬盤,或者增加讀庫等方法,但這方面的優化內容非常復雜,在后面會有專門的篇幅來討論。
- 依賴服務的性能 依賴別人的服務,很多不確定其系統性能如何,在可能情況下,可以讓下游系統緊急擴容的方式來解決其自身性能問題;但對于自身系統而言,可以采用快速失敗和接口降級的方式來實現。
如果上面所說的系統自身指標和依賴系統的指標都相對正常,但系統的QPS依然無法負載,說明系統內部出現問題,如系統被阻塞了。
在進行系統優化之前需要進行Profile測試分析,根據2:8原則來說,20%的代碼耗了你80%的性能,找到那20%的代碼,你就可以優化那80%的性能。
3 常見系統優化tips
3.1 代碼調優
- 調用接口異步化
調用依賴服務時,采用異步并行的方式調用,將多個耗時的請求合并發出,可以降低很多無謂的等待時間。
- IO異步化緩存化 系統中最常用的文件io是記錄日志,在記錄日志的時候設置合適的日志緩存,并使用異步化的方式寫入日志文件;在必要的地方記錄日志,避免日志濫用,不僅對io造成壓力,且會浪費系統硬盤空間,在一些極端情況下,會因為硬盤空間耗盡而導致系統吞吐量顯著下降。
針對其他需要進行文件讀寫的操作,建議使用異步化的方式,降低阻塞的可能。
- API的request及response不使用過大的對象
過大的request和response會增加網絡帶寬的壓力,且過大的字節傳入容易造成數據丟失。
- 適當使用緩存
這個是在互聯網服務中最常用的優化方式了,在此不再詳述。
- 慎重使用線程
有人說,thread is evil,因為多線程瓶頸就在于互斥和同步的鎖上,以及線程上下文切換的成本,怎么樣的少用鎖或不用鎖是根本。另外在系統中使用線程池時,避免因為使用線程池模式和數量限制設置不當,而成為系統瓶頸。
3.2 數據庫調優
- 數據庫的鎖的方式。
并發情況下,鎖是非常非常影響性能的。各種隔離級別,行鎖,表鎖,頁鎖,讀寫鎖,事務鎖,以及各種寫優先還是讀優先機制。性能***的是不要鎖,所以,分庫分表,冗余數據,減少一致性事務處理,可以有效地提高性能。
- 使用索引
在讀寫數據的時候都需要在where條件中檢查索引的使用。
- 避免在SQL級的join操作
SQL中的join操作對索引的優化是個很復雜的問題,因為互聯網的項目經常會發生變化,針對數據表的索引也會不斷優化,如果使用join很可能會無法正確索引;且SQL級的索引的功能維護性也非常差。
- 部分結果集
在查詢上增加適當的limit
- 不要select * ,而是明確指出各個字段,如果有多個表,一定要在字段名前加上表名,不要讓引擎去算。
- 不要用Having,因為其要遍歷所有的記錄。性能差得不能再差。
- 盡可能地使用UNION ALL 取代 UNION。
- 索引過多,insert和delete就會越慢。而update如果update多數索引,也會慢,但是如果只update一個,則只會影響一個索引表。
- 關于MySQL的優化,現在相關的資料也非常多,推薦高性能MySQL(第二版),這本書對MySQL的高性能有著更深入的討論。
原文:http://blog.brucefeng.info/post/high-qps-service