<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Topic太多!RocketMQ炸了!

          共 6436字,需瀏覽 13分鐘

           ·

          2023-06-11 18:34

          bda1175b8451ec278d68cf8d2fec7ee1.webp



          網(wǎng)上博客常說(shuō),kafka的topic數(shù)量過(guò)多會(huì)影響kafka,而RocketMQ不會(huì)受到topic數(shù)量影響。

          但是,果真如此嗎?

          最近排查一個(gè)問(wèn)題,發(fā)現(xiàn)RocketMQ穩(wěn)定性同樣受到topic數(shù)量影響!!

          好了,一起來(lái)回顧下這次問(wèn)題排查吧,最佳實(shí)踐引申思考放在最后,千萬(wàn)不要錯(cuò)過(guò)。

          1、問(wèn)題描述

          我們的RocketMQ集群為4.6.0版本,按照3個(gè)nameserver,2個(gè)broker,每個(gè)broker為主從雙節(jié)點(diǎn)部署。

          e78cd103394b66cbc46981fd7429cb2d.webp部署架構(gòu)

          某天收到警報(bào),broker-b突然從nameserver掉線,且主從雙節(jié)點(diǎn)都無(wú)法重新注冊(cè)。

          2、初步排查

          2.1 檢查進(jìn)程存活&網(wǎng)絡(luò)

          因?yàn)榭刂婆_(tái)上顯示broker-a正常,因此可以認(rèn)為 nameserver、broker-a都是正常的,問(wèn)題出在broker-b上。

          當(dāng)時(shí)第一反應(yīng)是broker-b進(jìn)程掛了,或者網(wǎng)絡(luò)不通了。

          登陸broker節(jié)點(diǎn),看到進(jìn)程依然存活。

          然后通過(guò)telnet檢查和nameserver的聯(lián)通性,顯示正常,網(wǎng)絡(luò)沒(méi)有問(wèn)題。

          2.2 檢查日志

          檢查broker日志,馬上發(fā)現(xiàn)了異常。

                2023-01-09 14:07:37 WARN brokerOutApi_thread_3 - registerBroker Exception, mqnameserver3:xxxx
          org.apache.rocketmq.remoting.exception.RemotingSendRequestException: send request to <qnameserver3/xx.xx.xx.xxx:xxxx> failed
              at org.apache.rocketmq.remoting.netty.NettyRemotingAbstract.invokeSyncImpl(NettyRemotingAbstract.java:429) ~[rocketmq-remoting-4.6.0.jar:4.6.0]
              at org.apache.rocketmq.remoting.netty.NettyRemotingClient.invokeSync(NettyRemotingClient.java:373
          ......

          異常比較明確,broker請(qǐng)求nameserver失敗,所以導(dǎo)致無(wú)法注冊(cè)到集群中。

          那為什么會(huì)注冊(cè)失敗呢?沒(méi)有非常明確的提示,因此去看下nameserver上的日志信息。

                2023-01-09 14:09:26 ERROR NettyServerCodecThread_1 - decode exceptionxx.xxx.xx.xxx:40093
          io.netty.handler.codec.TooLongFrameExceptionAdjusted frame length exceeds 16777216: 16777295 - discarded
              at io.netty.handler.codec.LengthFieldBasedFrameDecoder.fail(LengthFieldBasedFrameDecoder.java:499) [netty-all-4.0.42.Final.jar:4.0.42.Final]
          ......

          這個(gè)異??雌饋?lái)是nameserver上的netty拋出的,請(qǐng)求過(guò)大拋出了異常。

          根據(jù)日志關(guān)鍵字,直接定位到了源碼,確實(shí)有默認(rèn)的大小限制,并且可以通過(guò)com.rocketmq.remoting.frameMaxLength進(jìn)行控制。

          2.3 源碼分析

          雖然找到了異常的直接原因,但是為什么broker突然會(huì)有這么大的請(qǐng)求?是什么帶來(lái)的?

          從broker的warning日志中,并沒(méi)有辦法看到更多有效信息。

          因此,還是得深入分析下broker上的源碼。根據(jù)日志關(guān)鍵字,很快找到broker中的異常位置

          dd4451587e39a176b122b838af94545f.webpbroker異常位置

          注意!這里通過(guò)遍歷nameserverlist,在線程池中異步注冊(cè),跟后面的一個(gè)小知識(shí)點(diǎn)有關(guān)。

          從源碼中可以分析出,如果有過(guò)大的請(qǐng)求的話,應(yīng)該就是這個(gè)requestBody引起,它攜帶了大量topic信息topicConfigWrapper。

          但是我們?cè)诳刂婆_(tái)上看到當(dāng)前集群中,只有300+topic(這里其實(shí)是一個(gè)誤區(qū),最后會(huì)解釋),理論上來(lái)說(shuō)是非常小的,為什么會(huì)超出容量限制呢?

          看了下源碼上下文,并沒(méi)有對(duì)reqeustBody或者topicConfigWrapper有相關(guān)日志的記錄,因此,還是需要arthas來(lái)看看了。

          2.4 arthas定位

          直接通過(guò)arthas定位實(shí)際內(nèi)存值

                watch org.apache.rocketmq.broker.out.BrokerOuterAPI registerBrokerAll {params,returnObj} -x 3

          查看結(jié)果

          592833b69e4950475881e39dcbe36eb3.webp內(nèi)存中實(shí)際topic數(shù)量

          啥玩意?!

          topicConfigTablemap大小為size=71111???!

          進(jìn)一步看看這些topic里面都是些啥?我們調(diào)整下arthas的參數(shù)-x為4,改變watch變量的深度。

          6782bd3e9f1cf8d47bc1904983ed1f97.webp內(nèi)存中相關(guān)topic名稱

          發(fā)現(xiàn)問(wèn)題了!

          我們看到了大量%RETRY%開(kāi)頭的topic。

          3、根本原因

          至此,根本原因就能明確了。

          RETRY topic過(guò)多,導(dǎo)致 broker 向 nameserver 發(fā)送心跳(定時(shí)發(fā)送注冊(cè)請(qǐng)求)時(shí),心跳請(qǐng)求中攜帶的 body 上的 topic 信息過(guò)大,超過(guò)了 nameserver 上使用的 NettyDecoder.java 限制的 16M (默認(rèn)值),心跳請(qǐng)求失敗,所以broker掉線。

          4、恢復(fù)

          既然問(wèn)題基本確定了,那么先嘗試恢復(fù)吧。

          前面已經(jīng)看到了對(duì)最大請(qǐng)求體的配置,因此,我們?cè)?code style="font-size:inherit;color:rgb(248,35,117);">bin/runserver.sh中添加一個(gè)JAVA_OPTION對(duì)com.rocketmq.remoting.frameMaxLength進(jìn)行配置。然后重啟nameserver。

          重新觀察broker,果然重啟成功了。

                2023-01-09 16:03:55 INFO brokerOutApi_thread_3 - register broker[0]to name server mqnameserver4:9876 OK
          2023-01-09 16:03:55 INFO brokerOutApi_thread_4 - register broker[0]to name server mqnameserver2:9876 OK

          當(dāng)然,這只是臨時(shí)恢復(fù)措施,后面重點(diǎn)要思考以下問(wèn)題并進(jìn)行優(yōu)化:

          • RETRY topic數(shù)量這么多是否正常?是否可以清理無(wú)效topic?

          • 如何做好后續(xù)的topic數(shù)量監(jiān)控告警?

          5、最佳實(shí)踐

          5.1 定時(shí)刪除無(wú)效RETRY topic

          考慮使用定時(shí)任務(wù)掃描所有業(yè)務(wù)topic下的消費(fèi)組,再根據(jù)消費(fèi)組狀態(tài)(狀態(tài)為not_online的消費(fèi)組),拼出對(duì)應(yīng)RETRY topic進(jìn)行刪除。以上步驟均有開(kāi)源MQ sdk 的 api 可以調(diào)用。

          即使后續(xù)消費(fèi)組重新使用,RETRY topic 也會(huì)重新創(chuàng)建,不影響消費(fèi)。

          5.2 topic總數(shù)監(jiān)控

          前面說(shuō)到在控制臺(tái)上看到當(dāng)前集群中只有300+topic,這里其實(shí)是一個(gè)誤區(qū),只勾選了NORMAL類型的topic,并沒(méi)有注意RETRY、DLQ、SYSTEM類型的topic。

          1e1540359ffc669b3f147c852222a6a0.webp控制臺(tái)誤區(qū)

          而這次幾萬(wàn)個(gè)topic基本都是RETRY類型的。

          后續(xù)需要添加topic數(shù)量監(jiān)控(包括RETRY類型),防止由于topic數(shù)量過(guò)多,導(dǎo)致broker注冊(cè)失敗。

          6、引申思考

          6.1 RETRY topic是什么?為什么有這么多?

          這需要從RocketMQ的重試機(jī)制與死信機(jī)制說(shuō)起。

          RocketMQ 提供了自帶的重試機(jī)制,消息消費(fèi)失敗或超時(shí),會(huì)被投遞到 RETRY topic。RETRY topic 里的消息會(huì)按照延時(shí)隊(duì)列的延時(shí)時(shí)間進(jìn)行消費(fèi),這樣也避免了有問(wèn)題的消息阻塞正常消費(fèi)。

          RETRY topic 里保存的是消費(fèi)狀態(tài)為 consumer_later 的消息,在重試達(dá)到 16 次(默認(rèn)值)以后,消息會(huì)進(jìn)入死信隊(duì)列(本質(zhì)上也是一個(gè)新的topic類型,DLS topic)。

          DLQ topic在使用時(shí)才會(huì)創(chuàng)建,因此不會(huì)像RETRY topic 這樣大量膨脹。

          但是,RETRY topic不一樣。它是由RocketMQ服務(wù)端自動(dòng)創(chuàng)建,創(chuàng)建的時(shí)機(jī)有兩個(gè):

          • 消費(fèi)失敗的時(shí)候,將消息發(fā)送回 broker,這時(shí)候會(huì)在服務(wù)端創(chuàng)建RETRY topic 44f0fceaa07fe7759df5bf6200e37c68.webp

          消費(fèi)失敗創(chuàng)建RETRY topic
          • consumer client 和服務(wù)端保持心跳時(shí)創(chuàng)建RETRY topic c56af0affc7e223ad1fdc5cde98d4624.webp

          心跳時(shí)創(chuàng)建 retry topic

          線下環(huán)境的消費(fèi)組存在大量的臨時(shí)測(cè)試group,而 RocketMQ會(huì)給每個(gè)實(shí)際存在的消費(fèi)組創(chuàng)建RETRY topic,導(dǎo)致 RETRY topic 大量膨脹。

          6.2 如果所有消息自動(dòng)重試,順序消息會(huì)亂序嗎?

          我們知道,RocketMQ中包含三種消息類型:普通消息、普通有序消息、嚴(yán)格有序消息。

          三種消息的類型介紹如下:

          • 普通消息:消息是無(wú)序的,任意發(fā)送發(fā)送哪一個(gè)隊(duì)列都可以。

          • 普通有序消息:同一類消息(例如某個(gè)用戶的消息)總是發(fā)送到同一個(gè)隊(duì)列,在異常情況下,也可以發(fā)送到其他隊(duì)列。

          • 嚴(yán)格有序消息:消息必須被發(fā)送到同一個(gè)隊(duì)列,即使在異常情況下,也不允許發(fā)送到其他隊(duì)列。

          對(duì)于這三種類型的消息,RocketMQ對(duì)應(yīng)的提供了對(duì)應(yīng)的方法來(lái)分別消息:

                //發(fā)送普通消息,異常時(shí)默認(rèn)重試
          public SendResult send(Message msg)

          //發(fā)送普通有序消息,通過(guò)selector動(dòng)態(tài)決定發(fā)送哪個(gè)隊(duì)列,異常默認(rèn)不重試,可以用戶自己重試,并發(fā)送到其他隊(duì)列
          public SendResult send(Message msg, MessageQueueSelector selector, Object arg)

          //發(fā)送嚴(yán)格有序消息,通過(guò)指定隊(duì)列,保證嚴(yán)格有序,異常默認(rèn)不重試
          public SendResult send(Message msg, MessageQueue mq)

          所以RocketMQ客戶端的生產(chǎn)者默認(rèn)重試機(jī)制,只對(duì)普通消息有作用。對(duì)于普通有序消息、嚴(yán)格有序消息是沒(méi)有作用。

          6.3 nameserver數(shù)據(jù)一致性問(wèn)題

          在通過(guò)修改啟動(dòng)參數(shù)com.rocketmq.remoting.frameMaxLength進(jìn)行臨時(shí)恢復(fù)的時(shí)候,發(fā)現(xiàn)一個(gè)問(wèn)題:日志恢復(fù)了,但是控制臺(tái)上卻仍然沒(méi)有顯示broker-b。

          排查了下發(fā)現(xiàn),由于nameserver有4臺(tái),只重啟了一臺(tái),而控制臺(tái)連接訪問(wèn)的nameserver是另一臺(tái),所以顯示不正確。

          通過(guò)切換控制臺(tái)nameserver地址,就能看到broker-b了。

          為什么不同nameserver允許數(shù)據(jù)不一致呢?

          前面在排查的過(guò)程中也發(fā)現(xiàn)了,broker源碼中通過(guò)遍歷nameserverlist,在線程池中異步注冊(cè)topic信息到nameserver。

          0a0d4c7c7a4f87c1858c9a364fffecba.webp注冊(cè)邏輯

          而這也體現(xiàn)了RocketM中對(duì)nameserver的設(shè)計(jì)思想。

          nameserver是一個(gè)AP組件,而不是CP組件!

          在 RocketMQ 中 Nameserver 集群中的節(jié)點(diǎn)相互之間不通信,各節(jié)點(diǎn)相互獨(dú)立,實(shí)現(xiàn)非常簡(jiǎn)單。但同樣會(huì)帶來(lái)一個(gè)問(wèn)題:

          Topic 的路由信息在各個(gè)節(jié)點(diǎn)上會(huì)出現(xiàn)不一致。

          那 Nameserver 如何解決這個(gè)問(wèn)題呢?RocketMQ 的設(shè)計(jì)者采取的方案是不解決,即為了保證 Nameserver 的高性能,允許存在這些缺陷。

          NameServer之間不通信,消息發(fā)送端通過(guò)PULL方式更新topic信息,無(wú)法及時(shí)感知路由信息的變化,因此引入了消息發(fā)送重試(只針對(duì)普通消息)與故障規(guī)避機(jī)制來(lái)保證消息的發(fā)送高可用。

          事實(shí)上,在RocketMQ的早期版本,即MetaQ 1.x和MetaQ 2.x階段,也是依賴Zookeeper的(CP型組件)。但MetaQ 3.x(即RocketMQ)卻去掉了ZooKeeper依賴,轉(zhuǎn)而采用自己的NameServer。

          NameServer數(shù)據(jù)不一致,比較大的影響就是topic的隊(duì)列會(huì)存在負(fù)載不均衡的問(wèn)題,以及消費(fèi)端的重復(fù)消費(fèi)問(wèn)題,這些問(wèn)題對(duì)消息隊(duì)列來(lái)說(shuō)都是可以忍受的,只要最終能保持一致,恢復(fù)平衡即可。



          往期熱門(mén)筆記合集推薦:


          原創(chuàng):阿丸筆記(微信公眾號(hào):aone_note),歡迎  分享 ,轉(zhuǎn)載請(qǐng)保留出處。

          沒(méi)有留言功能的悲傷,掃描下方二維碼「加我」聊些有的沒(méi)的吧~

                                                                                        覺(jué)得不錯(cuò),就點(diǎn)個(gè) 再看 吧??



          瀏覽 95
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  8050午夜网 | 国内精品久久久久久久星 | 怡红院久久 | 99激情| AAA免费看 |