這里只考慮允許并發(fā)消費與順序消費的業(yè)務場景。
首先我們明確一點因為順序消費會對messageQueue加鎖,相同情況下,性能必然遠小于并發(fā)消費;
先說結論:假設消息消費速度恒定0.5秒。
消費者線程總數(shù) :threadNumtopic
總隊列數(shù):msgQueue
并發(fā)消費的TPS等同于:
????????????tps = threadNum/0.5s
順序消費的速度等同于:?
????????????tps = min(msgQueue,threadNum)/0.5s ;
ps:因為加鎖引起的性能損耗更能達到理論性能。
ps: 如果消費線程較少,并且pullThresholdForQueue較大,將容易導致部分隊列饑餓;
接下來分析topic 為什么在消息多時擠壓尤為嚴重。 首先佐證一下consumer 的pullThresholdForQueue這個參數(shù)是否存在:每次重啟或者新增client,topic的tps 總是瞬間飆升,但是很快就下降了,就是因為這個參數(shù)將大量的message緩存至客戶端,從而引起B(yǎng)roker錯誤計算tps。(實際上broker計算tps是非常簡陋的,不是嚴格按照offset計算)接下來看一下線上真實的consumer tps變化:
2022-06-19 08:59:00 INFO - Stats In One Minute, SUM:148 TPS:2.47 AVGPT:1.01
2022-06-19 09:00:00 INFO - Stats In One Minute, SUM:826 TPS:13.77 AVGPT:1.01
2022-06-19 09:00:00 INFO - Stats In One Hour, SUM:6930 TPS:1.93 AVGPT:1.01
2022-06-19 09:01:00 INFO - Stats In One Minute, SUM:703 TPS:11.72 AVGPT:1.00
2022-06-19 09:02:00 INFO - Stats In One Minute, SUM:501 TPS:8.35 AVGPT:1.00
2022-06-19 09:03:00 INFO - Stats In One Minute, SUM:956 TPS:15.93 AVGPT:1.00
2022-06-19 09:04:00 INFO - Stats In One Minute, SUM:498 TPS:8.30 AVGPT:1.00
2022-06-19 09:05:00 INFO - Stats In One Minute, SUM:1663 TPS:27.72 AVGPT:1.01
2022-06-19 09:06:00 INFO - Stats In One Minute, SUM:2126 TPS:35.43 AVGPT:1.01
2022-06-19 09:07:00 INFO - Stats In One Minute, SUM:1912 TPS:31.87 AVGPT:1.01
2022-06-19 09:08:00 INFO - Stats In One Minute, SUM:1180 TPS:19.67 AVGPT:1.01
2022-06-19 09:09:00 INFO - Stats In One Minute, SUM:1633 TPS:27.22 AVGPT:1.01
2022-06-19 09:10:00 INFO - Stats In One Minute, SUM:1230 TPS:20.50 AVGPT:1.01
2022-06-19 09:11:00 INFO - Stats In One Minute, SUM:1848 TPS:30.80 AVGPT:1.00
2022-06-19 09:12:00 INFO - Stats In One Minute, SUM:1147 TPS:19.12 AVGPT:1.02
2022-06-19 09:13:00 INFO - Stats In One Minute, SUM:1665 TPS:27.75 AVGPT:1.04
2022-06-19 09:14:00 INFO - Stats In One Minute, SUM:1017 TPS:16.95 AVGPT:1.01
2022-06-19 09:15:00 INFO - Stats In One Minute, SUM:1478 TPS:24.63 AVGPT:1.01
2022-06-19 09:16:00 INFO - Stats In One Minute, SUM:1274 TPS:21.23 AVGPT:1.02
2022-06-19 09:17:00 INFO - Stats In One Minute, SUM:1371 TPS:22.85 AVGPT:1.04
2022-06-19 09:18:00 INFO - Stats In One Minute, SUM:1256 TPS:20.93 AVGPT:1.01
2022-06-19 09:19:00 INFO - Stats In One Minute, SUM:1189 TPS:19.82 AVGPT:1.01
2022-06-19 09:20:00 INFO - Stats In One Minute, SUM:1216 TPS:20.27 AVGPT:1.02
2022-06-19 09:21:00 INFO - Stats In One Minute, SUM:2558 TPS:42.63 AVGPT:1.01
2022-06-19 09:22:00 INFO - Stats In One Minute, SUM:2222 TPS:37.03 AVGPT:1.02
2022-06-19 09:23:00 INFO - Stats In One Minute, SUM:1616 TPS:26.93 AVGPT:1.01
2022-06-19 09:24:00 INFO - Stats In One Minute, SUM:1200 TPS:20.00 AVGPT:1.01
2022-06-19 09:25:00 INFO - Stats In One Minute, SUM:1087 TPS:18.12 AVGPT:1.06
2022-06-19 09:26:00 INFO - Stats In One Minute, SUM:564 TPS:9.40 AVGPT:1.01
2022-06-19 09:27:00 INFO - Stats In One Minute, SUM:236 TPS:3.93 AVGPT:1.02
2022-06-19 09:28:00 INFO - Stats In One Minute, SUM:1160 TPS:19.33 AVGPT:1.04
2022-06-19 09:29:00 INFO - Stats In One Minute, SUM:793 TPS:13.22 AVGPT:1.05
2022-06-19 09:30:00 INFO - Stats In One Minute, SUM:230 TPS:3.83 AVGPT:1.02
2022-06-19 09:31:00 INFO - Stats In One Minute, SUM:727 TPS:12.12 AVGPT:1.02
而在我測試順序消費能力時如果
? threadNum<messageQueue,
發(fā)現(xiàn)1000條消息消費順序變化為
? 0-100左右無序排列,之后無限接近于101-996,? 102-997,103-998,104-999
明顯看出有部分隊列發(fā)生饑餓,沒有資源消費。
但是當我將pullThresholdForQueue設置為1,將無法復現(xiàn)這個情況。
關于饑餓的研究:
整個ConsumeRequest與pullRequest相當于一個生產(chǎn)者、消費者模型;他們共同持有的ProcessQueue對象(對象中維護一個讀寫鎖、一個treeMap(消息對象數(shù)組),一個消費reentrantLock)。但是在DefaultMQpullConsumer的任務隊列中

每個pullRequest只會拉pullbatchSize條消息,即32條,但是每個pullRequest在到達最大值前不間斷啟動。
那么假設最壞的情況
即第一個任務在工作過程中,之后的每一個隊列都已經(jīng)將message緩存完畢,那么最終線程池task隊列最大數(shù)量等同于隊列數(shù)量;
簡化為一個thread多個queue的的模型,如果第一個ConsumerRequest只有一個messageExt對象;那么假設需要0.5秒消費完畢,而在這0.5s內,下一個pullRequest拉取了32個messageExt對象。即消費完第二個ConsumerRequest16秒后以Mq的發(fā)送速度完全可以讓之后的每個ConsumerRequest緩存到達滿值
此時存在四個ConsumerRequest,每個ConsumerRequest有pullThresholdForQueue條messageExt對象。
這樣每pullThresholdForQueue條消息會按照順序被消費者線程分別消費;如下圖所示:
pullBatch一直是32,每個consumerRequest的數(shù)組長度最開始也的確是32,但是阻塞消費者線程時,每個consumeRequest的treemap將持有250條消息,并且只有四個task;

下一個節(jié)點來看consumerThread:
如果consumerThread小于線程池的任務數(shù)量,之后的task必然需要等待前面的任務執(zhí)行完畢,hz13的一個jvm 消費線程只有5個,任務隊列至少有16個任務。那么后續(xù)11個任務只能饑餓等待。
顯而易見,從這64000條消息并不是 64000/64*0.5=500秒執(zhí)行完畢;而是:
64000/20*0.5=1600秒=26.6分鐘
才能執(zhí)行完畢,而對于最后的ConsumerRequest乃至64000之后的消息都是二十多分鐘顯然有較大的影響。
PS:這里要分清楚兩種對象,最后一個任務隊列TreeMap里的頭節(jié)點CR任務和尾結點CR任務;其實在這種場景下消息量超過64000后消費時間是恒定的,但是頭節(jié)點和尾節(jié)點顯然不是同一個時間進入隊列的;即頭節(jié)點的這個任務應該很早就被消費、但是因為線程資源問題、他只能等到最后、整體來看消費時間是不平滑的、是不公平的消費機制。
而我們顯示在業(yè)務繁忙時剛好延遲半小時左右,理論符合計算值。
從這兒就可以解釋之前的各種奇怪現(xiàn)象
解決方案:
必然要保證threadNum>=messageQueue,但是不能一刀切;
①:對延遲要求高的topic 增加threadNum
②:減小對延時要求不高的topic的messageQueue;(有個考慮是環(huán)境內所有的topic如果是都是64個隊列,這對broker的cpu、內存資源消耗較大)
③:利用并發(fā)消費,并發(fā)消費task完全不依賴于隊列數(shù)量,基本整體有序消費,不存在上述問題