并發(fā)消費與順序消費TPS

這里只考慮允許并發(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ù)量,基本整體有序消費,不存在上述問題

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容