為了構建高併發、高可用的系統架構,壓測、容量預估必不可少,在發現系統瓶頸後,需要有針對性地擴容、優化。結合樓主的經驗和知識,本文做一個簡單的總結,歡迎探討。
1、QPS保障目標
一開始就要明確定義QPS保障目標,以此來推算所需的服務、儲存資源。可根據歷史同期QPS,或者平時峰值的2到3倍估算。
壓測目標示例:
- qps達到多少時,服務的負載正常,如平均響應時間、95分位響應時間、cpu使用率、記憶體使用率、消費延遲低於多少
- 不要讓任何一個環節成為瓶頸,需考慮服務例項、資料庫、Redis、ES、Hbase等資源
2、服務注意點
2.1、服務qps上限
服務qps上限 = 工作執行緒數 * 1/平均單次請求處理耗時
主要關注以下幾點:
(1)工作執行緒數,對qps起到了直接影響。
dubbo工作執行緒數配置舉例:
<dubbo:protocol name="dubbo" threadpool="fixed" threads="1000" />
(2)cpu使用率:跟服務是I/O密集型,還是計算密集型有關。
- I/O密集型:呼叫多個下游服務,本身邏輯較簡單,cpu使用率不會很高,因此服務例項的個數不用很多
- 計算密集型:本身邏輯很複雜,有較重的計算,cpu使用率可能飆升,因此可適當多部署一些服務例項
(3)網路頻寬:
- 對於大量的小請求,基本無需考慮
- 如果請求內容較大,多個併發可能打滿網路頻寬,如上傳圖片、視訊等。
以實際壓測為準。或者線上上調整權重,引導較多流量訪問1臺例項,記錄達到閾值時的qps,可估算出單例項的最大qps。
2.2、超時時間設定
漏斗型:從上到下,timeout時間建議由大到小設定,也即底層/下游服務的timeout時間不宜設定太大;否則可能出現底層/下游服務執行緒池耗盡、然後拒絕請求的問題(丟擲java.util.concurrent.RejectedExecutionException異常)
原因是上游服務已經timeout了,而底層/下游服務仍在執行,上游請求源源不斷打到底層/下游服務,直至執行緒池耗盡、新請求被拒絕,最壞的情況是產生級聯的雪崩,上游服務也耗盡執行緒池,無法響應新請求。
具體timeout時間,取決於介面的響應時間,可參考95分位、或99分位的響應時間,略微大一些。
dubbo超時時間示例:在服務端、客戶端均可設定,推薦在服務端設定預設超時時間,客戶端也可覆蓋超時時間;
<dubbo:service id="xxxService" interface="com.xxx.xxxService" timeout=1000 />
<dubbo:reference id="xxxService" interface="com.xxx.xxxService" timeout=500 />
2.3、非同步並行呼叫
如果多個呼叫之間,沒有順序依賴關係,為了提高效能,可考慮非同步並行呼叫。
dubbo非同步呼叫示例:
-
首先,需要配置consumer.xml,指定介面是非同步呼叫:
<dubbo:reference id="xxxService" interface="com.xxx.xxxService" async=true />
-
然後,在程式碼中通過RpcContext.getContext().getFuture()獲取非同步呼叫結果Future物件:
// 呼叫1先執行
interface1.xxx();
// 呼叫2、3、4無順序依賴,可非同步並行執行
interface2.xxx();
future2 = RpcContext.getContext().getFuture();
interface3.xxx();
future3 = RpcContext.getContext().getFuture();
interface4.xxx();
future4 = RpcContext.getContext().getFuture();
// 獲取呼叫2、3、4的執行結果
result2 = future2.get();
result3 = future3.get();
result4 = future4.get();
// 此處會阻塞至呼叫2、3、4都執行完成,取決於執行時間最長的那個
handleResult2(result2);
handleResult3(result3);
handleResult4(result4);
// 呼叫5最後執行,會阻塞至前序操作都完成
interface5.xxx();
2.4、強依賴、弱依賴
- 強依賴呼叫:決不能跳過,失敗則拋異常、快速失敗
- 弱依賴呼叫:決不能阻塞流程,失敗可忽略
2.5 降級
- 粗粒度:開關控制,如對整個非關鍵功能降級,隱藏入口
- 細粒度:呼叫下游介面失敗時,返回預設值
2.6 限流
超過的部分直接拋限流異常,萬不得已為之。
3、儲存資源注意點
3.1、放大倍數:1次核心操作,對應的資源讀寫次數、介面呼叫次數
例如:1次核心操作,查了3次快取、寫了1次快取、查了2次資料庫、寫了1次資料庫、發了1次MQ訊息、調了下游服務A的介面;
則對於讀快取放大倍數為3,寫快取放大倍數為1,讀資料庫放大倍數為2,寫資料庫放大倍數為1,MQ放大倍數為1,呼叫下游服務A的放大倍數為1。針對寫放大倍數,需要單獨考慮主庫是否扛得住放大倍數的qps。
需關注:
- 讀、寫的放大倍數,要分開考慮,因為分散式架構通常是一主多從,一主需要支撐所有的寫QPS,多從可以支撐所有的讀QPS
- DB讀放大倍數、DB寫放大倍數
- Redis讀放大倍數、Redis寫放大倍數
- MQ放大倍數
- 介面呼叫放大倍數等
3.2、儲存資源QPS估算
儲存資源的QPS上限,跟機器的具體配置有關,8C32G機型的QPS上限當然要高於4C16G機型。下表為典型值舉例。
資源型別 | 單例項QPS數量級(典型值) | 水平擴充套件方式 | 叢集總QPS估算 |
---|---|---|---|
DB | 幾千 | 分庫分表 | 例項個數*單例項QPS,其中例項個數的範圍是1~分庫個數(可達數百) |
Redis | 幾萬 | Redis叢集 | 例項個數*單例項QPS,其中例項個數的範圍是1~分片個數(可達數百),總QPS可達百萬級 |
MQ | 幾萬 | partition拆分,每個分片最多被1個服務併發消費 | 例項個數*單例項QPS,其中例項個數的範圍是1~partition個數,總QPS可達百萬級 |
HBase | 幾千? | region拆分 | 例項個數*單例項QPS,其中例項個數的範圍是1~region個數 |
ES | 幾千? | shard拆分 | 例項個數*單例項QPS,其中例項個數的範圍是1~shard個數 |