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