為了構建高并發、高可用的系統架構,壓測、容量預估必不可少,在發現系統瓶頸后,需要有針對性地擴容、優化。結合樓主的經驗和知識,本文做一個簡單的總結,歡迎探討。
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個數 |






