2021-Java后端工程師面試指南-(消息隊(duì)列)

前言

文本已收錄至我的GitHub倉(cāng)庫(kù),歡迎Star:https://github.com/bin392328206/six-finger
種一棵樹(shù)最好的時(shí)間是十年前,其次是現(xiàn)在

Tips

面試指南系列,很多情況下不會(huì)去深挖細(xì)節(jié),是小六六以被面試者的角色去回顧知識(shí)的一種方式,所以我默認(rèn)大部分的東西,作為面試官的你,肯定是懂的。

https://www.processon.com/view/link/600ed9e9637689349038b0e4

上面的是腦圖地址

叨絮

消息隊(duì)列,在互聯(lián)網(wǎng)企業(yè)級(jí)開(kāi)發(fā)是一個(gè)必不可少的中間件,今天來(lái)看看我們的MQ吧

然后下面是前面的文章匯總

小六六接觸的MQ呢?也不算太多,我就具體說(shuō)說(shuō)我們經(jīng)常用的rabbitmq和rocketmq

說(shuō)說(shuō)什么是消息隊(duì)列

我們可以把消息隊(duì)列看作是一個(gè)存放消息的容器,當(dāng)我們需要使用消息的時(shí)候,直接從容器中取出消息供自己使用即可。
消息隊(duì)列是分布式系統(tǒng)中重要的組件之一。使用消息隊(duì)列主要是為了通過(guò)異步處理提高系統(tǒng)性能和削峰、降低系統(tǒng)耦合性。
我們知道隊(duì)列 Queue 是一種先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),所以消費(fèi)消息時(shí)也是按照順序來(lái)消費(fèi)的。

那你的系統(tǒng)為啥要使用消息隊(duì)列

  • 通過(guò)異步處理提高系統(tǒng)性能(減少響應(yīng)所需時(shí)間)
  • 削峰/限流:先將短時(shí)間高并發(fā)產(chǎn)生的事務(wù)消息存儲(chǔ)在消息隊(duì)列中,然后后端服務(wù)再慢慢根據(jù)自己的能力去消費(fèi)這些消息,這樣就避免直接把后端服務(wù)打垮掉。
  • 降低系統(tǒng)耦合性:使用消息隊(duì)列還可以降低系統(tǒng)耦合性。我們知道如果模塊之間不存在直接調(diào)用,那么新增模塊或者修改模塊就對(duì)其他模塊影響較小,這樣系統(tǒng)的可擴(kuò)展性無(wú)疑更好一些

那你說(shuō)說(shuō)引入消息隊(duì)列的優(yōu)缺點(diǎn)是什么

優(yōu)點(diǎn):

  • 解耦
  • 削峰
  • 異步數(shù)據(jù)分發(fā)

缺點(diǎn)

  • 系統(tǒng)可用性降低
  • 系統(tǒng)復(fù)雜度提高
  • 一致性問(wèn)題

說(shuō)說(shuō)你接觸過(guò)的mq,說(shuō)說(shuō)他們的特點(diǎn)和使用場(chǎng)景唄

image

那你聊聊JMS和AMQP

JMS
JMS(JAVA Message Service,Java消息服務(wù))API是一個(gè)消息服務(wù)的標(biāo)準(zhǔn)或者說(shuō)是規(guī)范,允許應(yīng)用程序組件基于JavaEE平臺(tái)創(chuàng)建、發(fā)送、接收和讀取消息。它使分布式通信耦合度更低,消息服務(wù)更加可靠以及異步性。
ActiveMQ 就是基于 JMS 規(guī)范實(shí)現(xiàn)的。

AMQP
AMQP,即Advanced Message Queuing Protocol,一個(gè)提供統(tǒng)一消息服務(wù)的應(yīng)用層標(biāo)準(zhǔn) 高級(jí)消息隊(duì)列協(xié)議(二進(jìn)制應(yīng)用層協(xié)議),是應(yīng)用層協(xié)議的一個(gè)開(kāi)放標(biāo)準(zhǔn),為面向消息的中間件設(shè)計(jì),兼容 JMS?;诖藚f(xié)議的客戶(hù)端與消息中間件可傳遞消息,并不受客戶(hù)端/中間件同產(chǎn)品,不同的開(kāi)發(fā)語(yǔ)言等條件的限制。

  • AMQP 為消息定義了線(xiàn)路層(wire-level protocol)的協(xié)議,而JMS所定義的是API規(guī)范。在 Java 體系中,多個(gè)client均可以通過(guò)JMS進(jìn)行交互,不需要應(yīng)用修改代碼,但是其對(duì)跨平臺(tái)的支持較差。而AMQP天然具有跨平臺(tái)、跨語(yǔ)言特性。
  • JMS 支持TextMessage、MapMessage 等復(fù)雜的消息類(lèi)型;而 AMQP 僅支持 byte[] 消息類(lèi)型(復(fù)雜的類(lèi)型可序列化后發(fā)送)。
  • 由于Exchange 提供的路由算法,AMQP可以提供多樣化的路由方式來(lái)傳遞消息到消息隊(duì)列,而 JMS 僅支持 隊(duì)列 和 主題/訂閱 方式兩種。

如何保證消息隊(duì)列的高可用?

這個(gè)的話(huà)其實(shí)就是看你自己公司使用哪個(gè)隊(duì)列你就回答哪個(gè)隊(duì)列,小六六這邊說(shuō)rabbit 和rocket

RabbitMQ
RabbitMQ 有三種模式:?jiǎn)螜C(jī)模式、普通集群模式、鏡像集群模式。

  • 單機(jī)模式,就是 Demo 級(jí)別的,一般就是你本地啟動(dòng)了玩玩兒的??,沒(méi)人生產(chǎn)用單機(jī)模式。
  • 普通集群模式(無(wú)高可用性)這種方式確實(shí)很麻煩,也不怎么好,沒(méi)做到所謂的分布式,就是個(gè)普通集群。因?yàn)檫@導(dǎo)致你要么消費(fèi)者每次隨機(jī)連接一個(gè)實(shí)例然后拉取數(shù)據(jù),要么固定連接那個(gè) queue 所在實(shí)例消費(fèi)數(shù)據(jù),前者有數(shù)據(jù)拉取的開(kāi)銷(xiāo),后者導(dǎo)致單實(shí)例性能瓶頸。
  • 鏡像集群模式(高可用性)這種模式,才是所謂的 RabbitMQ 的高可用模式。跟普通集群模式不一樣的是,在鏡像集群模式下,你創(chuàng)建的 queue,無(wú)論元數(shù)據(jù)還是 queue 里的消息都會(huì)存在于多個(gè)實(shí)例上,就是說(shuō),每個(gè) RabbitMQ 節(jié)點(diǎn)都有這個(gè) queue 的一個(gè)完整鏡像,包含 queue 的全部數(shù)據(jù)的意思。然后每次你寫(xiě)消息到 queue 的時(shí)候,都會(huì)自動(dòng)把消息同步到多個(gè)實(shí)例的 queue 上。

RocketMQ
RocketMQ集群模式: 單Master模式 多Master模式 多Master多Slave模式(異步) 多Master多Slave模式(同步)

  • 單Master模式:這種方式風(fēng)險(xiǎn)較大,一旦Broker重啟或者宕機(jī)時(shí),會(huì)導(dǎo)致整個(gè)服務(wù)不可用。不建議線(xiàn)上環(huán)境使用,可以用于本地測(cè)試
  • 多Master模式:一個(gè)集群無(wú)Slave,全是Master,例如2個(gè)Master或者3個(gè)Master,這種模式的優(yōu)缺點(diǎn)如下:
    • 優(yōu)點(diǎn):配置簡(jiǎn)單,單個(gè)Master宕機(jī)或重啟維護(hù)對(duì)應(yīng)用無(wú)影響,在磁盤(pán)配置為RAID10時(shí),即使機(jī)器宕機(jī)不可恢復(fù)情況下,由于RAID10磁盤(pán)非 ??煽?,消息也不會(huì)丟(異步刷盤(pán)丟失少量消息,同步刷盤(pán)一條不丟),性能最高;
    • 缺點(diǎn):?jiǎn)闻_(tái)機(jī)器宕機(jī)期間,這臺(tái)機(jī)器上未被消費(fèi)的消息在機(jī)器恢復(fù)之前不可訂閱,消息實(shí)時(shí)性會(huì)受到影響。
  • 多Master多Slave模式(異步):每個(gè)Master配置一個(gè)Slave,有多對(duì)Master-Slave,HA采用異步復(fù)制方式,主備有短暫消息延遲(毫秒級(jí)),這種模式的優(yōu)缺點(diǎn)如下:
    • 優(yōu)點(diǎn):即使磁盤(pán)損壞,消息丟失的非常少,且消息實(shí)時(shí)性不會(huì)受影響,同時(shí)Master宕機(jī)后,消費(fèi)者仍然可以從Slave消費(fèi),而且此過(guò)程對(duì)應(yīng)用透明,不需要人工干預(yù),性能同多Master模式幾乎一樣;
    • 缺點(diǎn):Master宕機(jī),磁盤(pán)損壞情況下會(huì)丟失少量消息。
  • 多Master多Slave模式(同步):每個(gè)Master配置一個(gè)Slave,有多對(duì)Master-Slave,HA采用同步雙寫(xiě)方式,即只有主備都寫(xiě)成功,才向應(yīng)用返回成功,這種模式的優(yōu)缺點(diǎn)如下:
    • 優(yōu)點(diǎn):數(shù)據(jù)與服務(wù)都無(wú)單點(diǎn)故障,Master宕機(jī)情況下,消息無(wú)延遲,服務(wù)可用性與數(shù)據(jù)可用性都非常高;
    • 缺點(diǎn):性能比異步復(fù)制模式略低(大約低10%左右),發(fā)送單個(gè)消息的RT會(huì)略高,且目前版本在主節(jié)點(diǎn)宕機(jī)后,備機(jī)不能自動(dòng)切換為主機(jī)。

說(shuō)說(shuō)rocketmq的各個(gè)組件唄

  • Producer:消息的發(fā)送者;舉例:發(fā)信者
  • Consumer:消息接收者;舉例:收信者
  • Broker:暫存和傳輸消息;舉例:郵局
  • NameServer:管理Broker;舉例:各個(gè)郵局的管理機(jī)構(gòu)
  • Topic:區(qū)分消息的種類(lèi);一個(gè)發(fā)送者可以發(fā)送消息給一個(gè)或者多個(gè)Topic;一個(gè)消息的接收者可以訂閱一個(gè)或者多個(gè)Topic消息
  • Message Queue:相當(dāng)于是Topic的分區(qū);用于并行發(fā)送和接收消息

說(shuō)說(shuō)rocketmq組件的特別唄

  • NameServer是一個(gè)幾乎無(wú)狀態(tài)節(jié)點(diǎn),可集群部署,節(jié)點(diǎn)之間無(wú)任何信息同步。意味著每個(gè)節(jié)點(diǎn)都包含全部的數(shù)據(jù)。
  • Broker部署相對(duì)復(fù)雜,Broker分為Master與Slave,一個(gè)Master可以對(duì)應(yīng)多個(gè)Slave,但是一個(gè)Slave只能對(duì)應(yīng)一個(gè)Master,Master與Slave的對(duì)應(yīng)關(guān)系通過(guò)指定相同的BrokerName,不同的BrokerId來(lái)定義,BrokerId為0表示Master,非0表示Slave。Master也可以部署多個(gè)。每個(gè)Broker與NameServer集群中的所有節(jié)點(diǎn)建立長(zhǎng)連接,定時(shí)注冊(cè)Topic信息到所有NameServer。
  • Producer與NameServer集群中的其中一個(gè)節(jié)點(diǎn)(隨機(jī)選擇)建立長(zhǎng)連接,定期從NameServer取Topic路由信息,并向提供Topic服務(wù)的Master建立長(zhǎng)連接,且定時(shí)向Master發(fā)送心跳。Producer完全無(wú)狀態(tài),可集群部署。
  • Consumer與NameServer集群中的其中一個(gè)節(jié)點(diǎn)(隨機(jī)選擇)建立長(zhǎng)連接,定期從NameServer取Topic路由信息,并向提供Topic服務(wù)的Master、Slave建立長(zhǎng)連接,且定時(shí)向Master、Slave發(fā)送心跳。Consumer既可以從Master訂閱消息,也可以從Slave訂閱消息,訂閱規(guī)則由Broker配置決定。

既然你說(shuō)你用rocketmq,那么我問(wèn)你當(dāng)集群?jiǎn)?dòng)的時(shí)候它的工作流程是怎么樣的

  • 啟動(dòng)NameServer,NameServer起來(lái)后監(jiān)聽(tīng)端口,等待Broker、Producer、Consumer連上來(lái),相當(dāng)于一個(gè)路由控制中心。
  • Broker啟動(dòng),跟所有的NameServer保持長(zhǎng)連接,定時(shí)發(fā)送心跳包。心跳包中包含當(dāng)前Broker信息(IP+端口等)以及存儲(chǔ)所有Topic信息。注冊(cè)成功后,NameServer集群中就有Topic跟Broker的映射關(guān)系。
  • 收發(fā)消息前,先創(chuàng)建Topic,創(chuàng)建Topic時(shí)需要指定該Topic要存儲(chǔ)在哪些Broker上,也可以在發(fā)送消息時(shí)自動(dòng)創(chuàng)建Topic。
  • Producer發(fā)送消息,啟動(dòng)時(shí)先跟NameServer集群中的其中一臺(tái)建立長(zhǎng)連接,并從NameServer中獲取當(dāng)前發(fā)送的Topic存在哪些Broker上,輪詢(xún)從隊(duì)列列表中選擇一個(gè)隊(duì)列,然后與隊(duì)列所在的Broker建立長(zhǎng)連接從而向Broker發(fā)消息。
  • Consumer跟Producer類(lèi)似,跟其中一臺(tái)NameServer建立長(zhǎng)連接,獲取當(dāng)前訂閱Topic存在哪些Broker上,然后直接跟Broker建立連接通道,開(kāi)始消費(fèi)消息。

如何保證消息不被重復(fù)消費(fèi)?或者說(shuō),如何保證消息消費(fèi)的冪等性?

首先我們來(lái)看看在消息隊(duì)列的各個(gè)組件中,有哪些組件會(huì)出現(xiàn)不冪等

  • 生產(chǎn)者已把消息發(fā)送到mq,在mq給生產(chǎn)者返回ack的時(shí)候網(wǎng)絡(luò)中斷,故生產(chǎn)者未收到確定信息,生產(chǎn)者認(rèn)為消息未發(fā)送成功,但實(shí)際情況是,mq已成功接收到了消息,在網(wǎng)絡(luò)重連后,生產(chǎn)者會(huì)重新發(fā)送剛才的消息,造成mq接收了重復(fù)的消息
  • 消費(fèi)者在消費(fèi)mq中的消息時(shí),mq已把消息發(fā)送給消費(fèi)者,消費(fèi)者在給mq返回ack時(shí)網(wǎng)絡(luò)中斷,故mq未收到確認(rèn)信息,該條消息會(huì)重新發(fā)給其他的消費(fèi)者,或者在網(wǎng)絡(luò)重連后再次發(fā)送給該消費(fèi)者,但實(shí)際上該消費(fèi)者已成功消費(fèi)了該條消息,造成消費(fèi)者消費(fèi)了重復(fù)的消息;

解決方案

  • 第一個(gè)就是生產(chǎn)者,我們必須保證我們只有一個(gè)消息發(fā)送到了隊(duì)列中,可以通過(guò)一個(gè)唯一的id來(lái)保證,當(dāng)然這種情況是非常小的
  • 第二個(gè)就是消費(fèi)者,也可利用mq的該id來(lái)判斷,或者可按自己的規(guī)則生成一個(gè)全局唯一id,每次消費(fèi)消息時(shí)用該id先判斷該消息是否已消費(fèi)過(guò),至于實(shí)現(xiàn)方式有很多,redis 數(shù)據(jù)庫(kù)等等都行

如何保證消息的順序消費(fèi)

  • 生產(chǎn)者必須要將所有的消息順序的寫(xiě)入到一個(gè)隊(duì)列中。
  • 然后消費(fèi)者的話(huà),就只能保證一個(gè)消費(fèi)者,這樣的話(huà)就能實(shí)現(xiàn)順序消費(fèi)了,但是順序消費(fèi)的壞處就是我們的吞吐量要下降

如何保證消息的可靠性傳輸?或者說(shuō),如何處理消息丟失的問(wèn)題?

數(shù)據(jù)的丟失問(wèn)題,可能出現(xiàn)在生產(chǎn)者、MQ、消費(fèi)者中,咱們從 RabbitMQ 和 RocketMQ 分別來(lái)分析一下吧。

RabbitMQ


image
  • 生產(chǎn)者弄丟了數(shù)據(jù)
    生產(chǎn)者將數(shù)據(jù)發(fā)送到 RabbitMQ 的時(shí)候,可能數(shù)據(jù)就在半路給搞丟了,因?yàn)榫W(wǎng)絡(luò)問(wèn)題啥的,都有可能。
    此時(shí)可以選擇用 RabbitMQ 提供的事務(wù)功能或者是confirm 機(jī)制 事務(wù)機(jī)制和 confirm 機(jī)制最大的不同在于,事務(wù)機(jī)制是同步的,你提交一個(gè)事務(wù)之后會(huì)阻塞在那兒,但是 confirm 機(jī)制是異步的,你發(fā)送個(gè)消息之后就可以發(fā)送下一個(gè)消息,然后那個(gè)消息 RabbitMQ 接收了之后會(huì)異步回調(diào)你的一個(gè)接口通知你這個(gè)消息接收到了。
  • RabbitMQ 弄丟了數(shù)據(jù)
    就是 RabbitMQ 自己弄丟了數(shù)據(jù),這個(gè)你必須開(kāi)啟 RabbitMQ 的持久化,就是消息寫(xiě)入之后會(huì)持久化到磁盤(pán),哪怕是 RabbitMQ 自己掛了,恢復(fù)之后會(huì)自動(dòng)讀取之前存儲(chǔ)的數(shù)據(jù),一般數(shù)據(jù)不會(huì)丟。除非極其罕見(jiàn)的是,RabbitMQ 還沒(méi)持久化,自己就掛了,可能導(dǎo)致少量數(shù)據(jù)丟失,但是這個(gè)概率較小。
  • 消費(fèi)端弄丟了數(shù)據(jù)
    RabbitMQ 如果丟失了數(shù)據(jù),主要是因?yàn)槟阆M(fèi)的時(shí)候,剛消費(fèi)到,還沒(méi)處理,結(jié)果進(jìn)程掛了,比如重啟了,那么就尷尬了,RabbitMQ 認(rèn)為你都消費(fèi)了,這數(shù)據(jù)就丟了。
    這個(gè)時(shí)候得用 RabbitMQ 提供的 ack 機(jī)制,簡(jiǎn)單來(lái)說(shuō),就是你必須關(guān)閉 RabbitMQ 的自動(dòng) ack,可以通過(guò)一個(gè) api 來(lái)調(diào)用就行,然后每次你自己代碼里確保處理完的時(shí)候,再在程序里 ack 一把。這樣的話(huà),如果你還沒(méi)處理完,不就沒(méi)有 ack 了?那 RabbitMQ 就認(rèn)為你還沒(méi)處理完,這個(gè)時(shí)候 RabbitMQ 會(huì)把這個(gè)消費(fèi)分配給別的 consumer 去處理,消息是不會(huì)丟的。

RocketMQ

可以從三個(gè)方面來(lái)分析rocket的消息可靠性

  • Producer端消息丟失

    • producer端防止消息發(fā)送失敗,可以采用同步阻塞式的發(fā)送(也就是發(fā)送同步消息),同步的檢查Brocker返回的狀態(tài)是否持久化成功,發(fā)送超時(shí)或者失敗,則會(huì)默認(rèn)重試2次,rocker選擇了確保消息一定發(fā)送成功,但有可能發(fā)生重復(fù)投遞
    • 如果是異步發(fā)送消息,會(huì)有一個(gè)回調(diào)接口,當(dāng)brocker存儲(chǔ)成功或者失敗的時(shí)候,也可以在這里根據(jù)返回狀態(tài)來(lái)決定是否需要重試(當(dāng)然這個(gè)是需要我們自己來(lái)實(shí)現(xiàn)的)
  • Brocker端消息丟失

    • rocketmq一般都是先把消息寫(xiě)到PageCache中,然后再持久化到磁盤(pán)上,數(shù)據(jù)從pagecache刷新到磁盤(pán)有兩種方式,同步和異步
    • 同步刷盤(pán)方式:消息寫(xiě)入內(nèi)存的 PageCache后,立刻通知刷盤(pán)線(xiàn)程刷盤(pán),然后等待刷盤(pán)完成,刷盤(pán)線(xiàn)程執(zhí)行完成后喚醒等待的線(xiàn)程,返回消息寫(xiě)成功的狀態(tài)。這種方式可以保證數(shù)據(jù)絕對(duì)安全,但是吞吐量不大。
    • 異步刷盤(pán)方式(默認(rèn)):消息寫(xiě)入到內(nèi)存的 PageCache中,就立刻給客戶(hù)端返回寫(xiě)操作成功,當(dāng) PageCache中的消息積累到一定的量時(shí),觸發(fā)一次寫(xiě)操作,將 PageCache中的消息寫(xiě)入到磁盤(pán)中。這種方式吞吐量大,性能高,但是 PageCache中的數(shù)據(jù)可能丟失,不能保證數(shù)據(jù)絕對(duì)的安全。
  • Cousmer端消息丟失

    • cousmer端默認(rèn)是消息之后自動(dòng)返回消費(fèi)成功確認(rèn)ack,但是這時(shí)如果我們的程序執(zhí)行失敗了,數(shù)據(jù)不就丟失了嗎?
    • 所以我們可以將自動(dòng)提交(AutoCommit)消費(fèi)響應(yīng),設(shè)置為在代碼中手動(dòng)提交,只有真正消費(fèi)成功之后再通知brocker消費(fèi)成功,然后更新消費(fèi)唯一offset或者刪除brocker中的消息

大量消息在 mq 里積壓了幾個(gè)小時(shí)了還沒(méi)解決此時(shí)應(yīng)該怎么辦

  • 第一條,為啥會(huì)出現(xiàn)消息大量積壓,是本身我們的生產(chǎn)者的消息產(chǎn)多了,還是我們的消費(fèi)者出現(xiàn)問(wèn)題了,先弄清楚原因先
  • 第二 如果是我們生產(chǎn)的消息多了,那么我們可以多加幾個(gè)消費(fèi)者去消費(fèi)消息
  • 第三,如果說(shuō)是我們的消費(fèi)者出現(xiàn)了問(wèn)題,那么我首先肯定是要修復(fù)消費(fèi)者的bug,但是有一點(diǎn)就是就算我們修復(fù)了bug,但是要到生產(chǎn)的流程來(lái)說(shuō)還要花幾個(gè)小時(shí)才能消費(fèi)完,這時(shí)候,我們要零時(shí)寫(xiě)一個(gè)邏輯,把消費(fèi)者的耗時(shí)邏輯直接確認(rèn),然后把消息轉(zhuǎn)到另外一個(gè)隊(duì)列,另外一個(gè)隊(duì)列用10背速度去消費(fèi),等轉(zhuǎn)發(fā)完成之后,換成正常的消費(fèi)邏輯,這樣就可以盡快的使業(yè)務(wù)得到正常的使用了。

說(shuō)說(shuō)延時(shí)隊(duì)列唄

延時(shí)隊(duì)列,首先,它是一種隊(duì)列,隊(duì)列意味著內(nèi)部的元素是有序的,元素出隊(duì)和入隊(duì)是有方向性的,元素從一端進(jìn)入,從另一端取出。
其次,延時(shí)隊(duì)列,最重要的特性就體現(xiàn)在它的延時(shí)屬性上,跟普通的隊(duì)列不一樣的是,普通隊(duì)列中的元素總是等著希望被早點(diǎn)取出處理,而延時(shí)隊(duì)列中的元素則是希望被在指定時(shí)間得到取出和處理,所以延時(shí)隊(duì)列中的元素是都是帶時(shí)間屬性的,通常來(lái)說(shuō)是需要被處理的消息或者任務(wù)。
簡(jiǎn)單來(lái)說(shuō),延時(shí)隊(duì)列就是用來(lái)存放需要在指定時(shí)間被處理的元素的隊(duì)列。

RabbitMQ中的一個(gè)高級(jí)特性——TTL(Time To Live),當(dāng)我們有一些特殊的場(chǎng)景,比如注冊(cè)幾天后,沒(méi)有購(gòu)買(mǎi)就給他們發(fā)優(yōu)惠卷,這些運(yùn)營(yíng)手段,就可以用到這個(gè)延時(shí)隊(duì)列了,在rabbitmq里面就是ttl+死信隊(duì)列來(lái)實(shí)現(xiàn)的。

image

然后RocketMQ的延時(shí)隊(duì)列的話(huà)用處就不大了,rocketmq實(shí)現(xiàn)的延時(shí)隊(duì)列只支持特定的延時(shí)時(shí)間段,1s,5s,10s,...2h,不能支持任意時(shí)間段的延時(shí)

深入聊聊RocketMQ唄,因?yàn)閞abbitmq的源碼不是Java,所以不好問(wèn),但是RocketMQ的源碼還是要大致了解了解

聊聊消息的存儲(chǔ)和發(fā)送

  • 消息存儲(chǔ)

磁盤(pán)如果使用得當(dāng),磁盤(pán)的速度完全可以匹配上網(wǎng)絡(luò) 的數(shù)據(jù)傳輸速度。目前的高性能磁盤(pán),順序?qū)懰俣瓤梢赃_(dá)到600MB/s, 超過(guò)了一般網(wǎng)卡的傳輸速度。但是磁盤(pán)隨機(jī)寫(xiě)的速度只有大概100KB/s,和順序?qū)懙男阅芟嗖?000倍!因?yàn)橛腥绱司薮蟮乃俣炔顒e,好的消息隊(duì)列系統(tǒng)會(huì)比普通的消息隊(duì)列系統(tǒng)速度快多個(gè)數(shù)量級(jí)。RocketMQ的消息用順序?qū)?保證了消息存儲(chǔ)的速度。

  • 消息發(fā)送

Linux操作系統(tǒng)分為【用戶(hù)態(tài)】和【內(nèi)核態(tài)】,文件操作、網(wǎng)絡(luò)操作需要涉及這兩種形態(tài)的切換,免不了進(jìn)行數(shù)據(jù)復(fù)制。

一臺(tái)服務(wù)器 把本機(jī)磁盤(pán)文件的內(nèi)容發(fā)送到客戶(hù)端,一般分為兩個(gè)步驟:

1)read;讀取本地文件內(nèi)容;

2)write;將讀取的內(nèi)容通過(guò)網(wǎng)絡(luò)發(fā)送出去。

這兩個(gè)看似簡(jiǎn)單的操作,實(shí)際進(jìn)行了4 次數(shù)據(jù)復(fù)制,分別是:

  1. 從磁盤(pán)復(fù)制數(shù)據(jù)到內(nèi)核態(tài)內(nèi)存;
  2. 從內(nèi)核態(tài)內(nèi)存復(fù) 制到用戶(hù)態(tài)內(nèi)存;
  3. 然后從用戶(hù)態(tài) 內(nèi)存復(fù)制到網(wǎng)絡(luò)驅(qū)動(dòng)的內(nèi)核態(tài)內(nèi)存;
  4. 最后是從網(wǎng)絡(luò)驅(qū)動(dòng)的內(nèi)核態(tài)內(nèi)存復(fù) 制到網(wǎng)卡中進(jìn)行傳輸。
image

通過(guò)使用mmap的方式,可以省去向用戶(hù)態(tài)的內(nèi)存復(fù)制,提高速度。這種機(jī)制在Java中是通過(guò)MappedByteBuffer實(shí)現(xiàn)的

RocketMQ充分利用了上述特性,也就是所謂的“零拷貝”技術(shù),提高消息存盤(pán)和網(wǎng)絡(luò)發(fā)送的速度。

這里需要注意的是,采用MappedByteBuffer這種內(nèi)存映射的方式有幾個(gè)限制,其中之一是一次只能映射1.5~2G 的文件至用戶(hù)態(tài)的虛擬內(nèi)存,這也是為何RocketMQ默認(rèn)設(shè)置單個(gè)CommitLog日志數(shù)據(jù)文件為1G的原因了

聊聊分布式事務(wù)唄

如何解釋分布式事務(wù)呢?事務(wù)大家都知道吧?要么都執(zhí)行要么都不執(zhí)行 。在同一個(gè)系統(tǒng)中我們可以輕松地實(shí)現(xiàn)事務(wù),但是在分布式架構(gòu)中,我們有很多服務(wù)是部署在不同系統(tǒng)之間的,而不同服務(wù)之間又需要進(jìn)行調(diào)用。比如此時(shí)我下訂單然后增加積分,如果保證不了分布式事務(wù)的話(huà),就會(huì)出現(xiàn)A系統(tǒng)下了訂單,但是B系統(tǒng)增加積分失敗或者A系統(tǒng)沒(méi)有下訂單,B系統(tǒng)卻增加了積分。

如今比較常見(jiàn)的分布式事務(wù)實(shí)現(xiàn)有 2PC、TCC 和 事務(wù)最終一致性,一般我們除了強(qiáng)一致性的場(chǎng)景,一般用的可靠消息最終一致性,那么對(duì)于RocketMQ 它是怎么實(shí)現(xiàn)的呢?

在 RocketMQ 中使用的是 事務(wù)消息加上事務(wù)反查機(jī)制 來(lái)解決分布式事務(wù)問(wèn)題的


image

在第一步發(fā)送的 half 消息 ,它的意思是 在事務(wù)提交之前,對(duì)于消費(fèi)者來(lái)說(shuō),這個(gè)消息是不可見(jiàn)的 。

你可以試想一下,如果沒(méi)有從第5步開(kāi)始的 事務(wù)反查機(jī)制 ,如果出現(xiàn)網(wǎng)路波動(dòng)第4步?jīng)]有發(fā)送成功,這樣就會(huì)產(chǎn)生 MQ 不知道是不是需要給消費(fèi)者消費(fèi)的問(wèn)題,他就像一個(gè)無(wú)頭蒼蠅一樣。在 RocketMQ 中就是使用的上述的事務(wù)反查來(lái)解決的

  • 事務(wù)消息發(fā)送及提交

    • 發(fā)送消息(half消息)。
    • 服務(wù)端響應(yīng)消息寫(xiě)入結(jié)果。
    • 根據(jù)發(fā)送結(jié)果執(zhí)行本地事務(wù)(如果寫(xiě)入失敗,此時(shí)half消息對(duì)業(yè)務(wù)不可見(jiàn),本地邏輯不執(zhí)行)。
    • 根據(jù)本地事務(wù)狀態(tài)執(zhí)行Commit或者Rollback(Commit操作生成消息索引,消息對(duì)消費(fèi)者可見(jiàn))
  • 事務(wù)補(bǔ)償

    • 對(duì)沒(méi)有Commit/Rollback的事務(wù)消息(pending狀態(tài)的消息),從服務(wù)端發(fā)起一次“回查”
    • Producer收到回查消息,檢查回查消息對(duì)應(yīng)的本地事務(wù)的狀態(tài)
    • 根據(jù)本地事務(wù)狀態(tài),重新Commit或者Rollback
    • 其中,補(bǔ)償階段用于解決消息Commit或者Rollback發(fā)生超時(shí)或者失敗的情況。

聊聊RocketMQ的底層存儲(chǔ)機(jī)制

RocketMQ 是如何設(shè)計(jì)它的存儲(chǔ)結(jié)構(gòu)了。我首先想大家介紹 RocketMQ 消息存儲(chǔ)架構(gòu)中的三大角色——CommitLog 、ConsumeQueue 和 IndexFile 。

  • CommitLog: 消息主體以及元數(shù)據(jù)的存儲(chǔ)主體,存儲(chǔ) Producer 端寫(xiě)入的消息主體內(nèi)容,消息內(nèi)容不是定長(zhǎng)的。單個(gè)文件大小默認(rèn)1G ,文件名長(zhǎng)度為20位,左邊補(bǔ)零,剩余為起始偏移量,比如00000000000000000000代表了第一個(gè)文件,起始偏移量為0,文件大小為1G=1073741824;當(dāng)?shù)谝粋€(gè)文件寫(xiě)滿(mǎn)了,第二個(gè)文件為00000000001073741824,起始偏移量為1073741824,以此類(lèi)推。消息主要是順序?qū)懭肴罩疚募?,?dāng)文件滿(mǎn)了,寫(xiě)入下一個(gè)文件。
  • ConsumeQueue: 消息消費(fèi)隊(duì)列,引入的目的主要是提高消息消費(fèi)的性能,由于RocketMQ 是基于主題 Topic 的訂閱模式,消息消費(fèi)是針對(duì)主題進(jìn)行的,如果要遍歷 commitlog 文件中根據(jù) Topic 檢索消息是非常低效的。Consumer 即可根據(jù) ConsumeQueue 來(lái)查找待消費(fèi)的消息。其中,ConsumeQueue(邏輯消費(fèi)隊(duì)列)作為消費(fèi)消息的索引,保存了指定 Topic 下的隊(duì)列消息在 CommitLog 中的起始物理偏移量 offset ,消息大小 size 和消息 Tag 的 HashCode 值。consumequeue 文件可以看成是基于 topic 的 commitlog 索引文件,故 consumequeue 文件夾的組織方式如下:topic/queue/file三層組織結(jié)構(gòu),具體存儲(chǔ)路徑為:$HOME/store/consumequeue/{topic}/{queueId}/{fileName}。同樣 consumequeue 文件采取定長(zhǎng)設(shè)計(jì),每一個(gè)條目共20個(gè)字節(jié),分別為8字節(jié)的 commitlog 物理偏移量、4字節(jié)的消息長(zhǎng)度、8字節(jié)tag hashcode,單個(gè)文件由30W個(gè)條目組成,可以像數(shù)組一樣隨機(jī)訪(fǎng)問(wèn)每一個(gè)條目,每個(gè) ConsumeQueue文件大小約5.72M;
  • IndexFile: IndexFile(索引文件)提供了一種可以通過(guò)key或時(shí)間區(qū)間來(lái)查詢(xún)消息的方法。

結(jié)束

接下來(lái)復(fù)習(xí)下ssm框架

日常求贊

好了各位,以上就是這篇文章的全部?jī)?nèi)容了,能看到這里的人呀,都是真粉

創(chuàng)作不易,各位的支持和認(rèn)可,就是我創(chuàng)作的最大動(dòng)力,我們下篇文章見(jiàn)

微信 搜 "六脈神劍的程序人生" 回復(fù)888 有我找的許多的資料送給大家

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

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容