前言
文本已收錄至我的GitHub倉庫,歡迎Star:https://github.com/bin392328206/six-finger
種一棵樹最好的時間是十年前,其次是現在
Tips
面試指南系列,很多情況下不會去深挖細節,是小六六以被面試者的角色去回顧知識的一種方式,所以我默認大部分的東西,作為面試官的你,肯定是懂的。
https://www.processon.com/view/link/600ed9e9637689349038b0e4
上面的是腦圖地址
叨絮
消息隊列,在互聯網企業級開發是一個必不可少的中間件,今天來看看我們的MQ吧
然后下面是前面的文章匯總
- 2021-Java后端工程師面試指南-(引言)
- 2021-Java后端工程師面試指南-(Java基礎篇)
- 2021-Java后端工程師面試指南-(并發-多線程)
- 2021-Java后端工程師面試指南-(JVM)
- 2021-Java后端工程師面試指南-(MySQL)
- 2021-Java后端工程師面試指南-(Redis)
- 2021-Java后端工程師面試指南-(Elasticsearch)
小六六接觸的MQ呢?也不算太多,我就具體說說我們經常用的rabbitmq和rocketmq
說說什么是消息隊列
我們可以把消息隊列看作是一個存放消息的容器,當我們需要使用消息的時候,直接從容器中取出消息供自己使用即可。
消息隊列是分布式系統中重要的組件之一。使用消息隊列主要是為了通過異步處理提高系統性能和削峰、降低系統耦合性。
我們知道隊列 Queue 是一種先進先出的數據結構,所以消費消息時也是按照順序來消費的。
那你的系統為啥要使用消息隊列
- 通過異步處理提高系統性能(減少響應所需時間)
- 削峰/限流:先將短時間高并發產生的事務消息存儲在消息隊列中,然后后端服務再慢慢根據自己的能力去消費這些消息,這樣就避免直接把后端服務打垮掉。
- 降低系統耦合性:使用消息隊列還可以降低系統耦合性。我們知道如果模塊之間不存在直接調用,那么新增模塊或者修改模塊就對其他模塊影響較小,這樣系統的可擴展性無疑更好一些
那你說說引入消息隊列的優缺點是什么
優點:
- 解耦
- 削峰
- 異步數據分發
缺點
- 系統可用性降低
- 系統復雜度提高
- 一致性問題
說說你接觸過的mq,說說他們的特點和使用場景唄
那你聊聊JMS和AMQP
JMS
JMS(JAVA Message Service,Java消息服務)API是一個消息服務的標準或者說是規范,允許應用程序組件基于JavaEE平臺創建、發送、接收和讀取消息。它使分布式通信耦合度更低,消息服務更加可靠以及異步性。
ActiveMQ 就是基于 JMS 規范實現的。
AMQP
AMQP,即Advanced Message Queuing Protocol,一個提供統一消息服務的應用層標準 高級消息隊列協議(二進制應用層協議),是應用層協議的一個開放標準,為面向消息的中間件設計,兼容 JMS。基于此協議的客戶端與消息中間件可傳遞消息,并不受客戶端/中間件同產品,不同的開發語言等條件的限制。
- AMQP 為消息定義了線路層(wire-level protocol)的協議,而JMS所定義的是API規范。在 Java 體系中,多個client均可以通過JMS進行交互,不需要應用修改代碼,但是其對跨平臺的支持較差。而AMQP天然具有跨平臺、跨語言特性。
- JMS 支持TextMessage、MapMessage 等復雜的消息類型;而 AMQP 僅支持 byte[] 消息類型(復雜的類型可序列化后發送)。
- 由于Exchange 提供的路由算法,AMQP可以提供多樣化的路由方式來傳遞消息到消息隊列,而 JMS 僅支持 隊列 和 主題/訂閱 方式兩種。
如何保證消息隊列的高可用?
這個的話其實就是看你自己公司使用哪個隊列你就回答哪個隊列,小六六這邊說rabbit 和rocket
RabbitMQ
RabbitMQ 有三種模式:單機模式、普通集群模式、鏡像集群模式。
- 單機模式,就是 Demo 級別的,一般就是你本地啟動了玩玩兒的??,沒人生產用單機模式。
- 普通集群模式(無高可用性)這種方式確實很麻煩,也不怎么好,沒做到所謂的分布式,就是個普通集群。因為這導致你要么消費者每次隨機連接一個實例然后拉取數據,要么固定連接那個 queue 所在實例消費數據,前者有數據拉取的開銷,后者導致單實例性能瓶頸。
- 鏡像集群模式(高可用性)這種模式,才是所謂的 RabbitMQ 的高可用模式。跟普通集群模式不一樣的是,在鏡像集群模式下,你創建的 queue,無論元數據還是 queue 里的消息都會存在于多個實例上,就是說,每個 RabbitMQ 節點都有這個 queue 的一個完整鏡像,包含 queue 的全部數據的意思。然后每次你寫消息到 queue 的時候,都會自動把消息同步到多個實例的 queue 上。
RocketMQ
RocketMQ集群模式: 單Master模式 多Master模式 多Master多Slave模式(異步) 多Master多Slave模式(同步)
- 單Master模式:這種方式風險較大,一旦Broker重啟或者宕機時,會導致整個服務不可用。不建議線上環境使用,可以用于本地測試
- 多Master模式:一個集群無Slave,全是Master,例如2個Master或者3個Master,這種模式的優缺點如下:
- 優點:配置簡單,單個Master宕機或重啟維護對應用無影響,在磁盤配置為RAID10時,即使機器宕機不可恢復情況下,由于RAID10磁盤非 常可靠,消息也不會丟(異步刷盤丟失少量消息,同步刷盤一條不丟),性能最高;
- 缺點:單臺機器宕機期間,這臺機器上未被消費的消息在機器恢復之前不可訂閱,消息實時性會受到影響。
- 多Master多Slave模式(異步):每個Master配置一個Slave,有多對Master-Slave,HA采用異步復制方式,主備有短暫消息延遲(毫秒級),這種模式的優缺點如下:
- 優點:即使磁盤損壞,消息丟失的非常少,且消息實時性不會受影響,同時Master宕機后,消費者仍然可以從Slave消費,而且此過程對應用透明,不需要人工干預,性能同多Master模式幾乎一樣;
- 缺點:Master宕機,磁盤損壞情況下會丟失少量消息。
- 多Master多Slave模式(同步):每個Master配置一個Slave,有多對Master-Slave,HA采用同步雙寫方式,即只有主備都寫成功,才向應用返回成功,這種模式的優缺點如下:
- 優點:數據與服務都無單點故障,Master宕機情況下,消息無延遲,服務可用性與數據可用性都非常高;
- 缺點:性能比異步復制模式略低(大約低10%左右),發送單個消息的RT會略高,且目前版本在主節點宕機后,備機不能自動切換為主機。
說說rocketmq的各個組件唄
- Producer:消息的發送者;舉例:發信者
- Consumer:消息接收者;舉例:收信者
- Broker:暫存和傳輸消息;舉例:郵局
- NameServer:管理Broker;舉例:各個郵局的管理機構
- Topic:區分消息的種類;一個發送者可以發送消息給一個或者多個Topic;一個消息的接收者可以訂閱一個或者多個Topic消息
- Message Queue:相當于是Topic的分區;用于并行發送和接收消息
說說rocketmq組件的特別唄
- NameServer是一個幾乎無狀態節點,可集群部署,節點之間無任何信息同步。意味著每個節點都包含全部的數據。
- Broker部署相對復雜,Broker分為Master與Slave,一個Master可以對應多個Slave,但是一個Slave只能對應一個Master,Master與Slave的對應關系通過指定相同的BrokerName,不同的BrokerId來定義,BrokerId為0表示Master,非0表示Slave。Master也可以部署多個。每個Broker與NameServer集群中的所有節點建立長連接,定時注冊Topic信息到所有NameServer。
- Producer與NameServer集群中的其中一個節點(隨機選擇)建立長連接,定期從NameServer取Topic路由信息,并向提供Topic服務的Master建立長連接,且定時向Master發送心跳。Producer完全無狀態,可集群部署。
- Consumer與NameServer集群中的其中一個節點(隨機選擇)建立長連接,定期從NameServer取Topic路由信息,并向提供Topic服務的Master、Slave建立長連接,且定時向Master、Slave發送心跳。Consumer既可以從Master訂閱消息,也可以從Slave訂閱消息,訂閱規則由Broker配置決定。
既然你說你用rocketmq,那么我問你當集群啟動的時候它的工作流程是怎么樣的
- 啟動NameServer,NameServer起來后監聽端口,等待Broker、Producer、Consumer連上來,相當于一個路由控制中心。
- Broker啟動,跟所有的NameServer保持長連接,定時發送心跳包。心跳包中包含當前Broker信息(IP+端口等)以及存儲所有Topic信息。注冊成功后,NameServer集群中就有Topic跟Broker的映射關系。
- 收發消息前,先創建Topic,創建Topic時需要指定該Topic要存儲在哪些Broker上,也可以在發送消息時自動創建Topic。
- Producer發送消息,啟動時先跟NameServer集群中的其中一臺建立長連接,并從NameServer中獲取當前發送的Topic存在哪些Broker上,輪詢從隊列列表中選擇一個隊列,然后與隊列所在的Broker建立長連接從而向Broker發消息。
- Consumer跟Producer類似,跟其中一臺NameServer建立長連接,獲取當前訂閱Topic存在哪些Broker上,然后直接跟Broker建立連接通道,開始消費消息。
如何保證消息不被重復消費?或者說,如何保證消息消費的冪等性?
首先我們來看看在消息隊列的各個組件中,有哪些組件會出現不冪等
- 生產者已把消息發送到mq,在mq給生產者返回ack的時候網絡中斷,故生產者未收到確定信息,生產者認為消息未發送成功,但實際情況是,mq已成功接收到了消息,在網絡重連后,生產者會重新發送剛才的消息,造成mq接收了重復的消息
- 消費者在消費mq中的消息時,mq已把消息發送給消費者,消費者在給mq返回ack時網絡中斷,故mq未收到確認信息,該條消息會重新發給其他的消費者,或者在網絡重連后再次發送給該消費者,但實際上該消費者已成功消費了該條消息,造成消費者消費了重復的消息;
解決方案
- 第一個就是生產者,我們必須保證我們只有一個消息發送到了隊列中,可以通過一個唯一的id來保證,當然這種情況是非常小的
- 第二個就是消費者,也可利用mq的該id來判斷,或者可按自己的規則生成一個全局唯一id,每次消費消息時用該id先判斷該消息是否已消費過,至于實現方式有很多,redis 數據庫等等都行
如何保證消息的順序消費
- 生產者必須要將所有的消息順序的寫入到一個隊列中。
- 然后消費者的話,就只能保證一個消費者,這樣的話就能實現順序消費了,但是順序消費的壞處就是我們的吞吐量要下降
如何保證消息的可靠性傳輸?或者說,如何處理消息丟失的問題?
數據的丟失問題,可能出現在生產者、MQ、消費者中,咱們從 RabbitMQ 和 RocketMQ 分別來分析一下吧。
RabbitMQ
image
- 生產者弄丟了數據
生產者將數據發送到 RabbitMQ 的時候,可能數據就在半路給搞丟了,因為網絡問題啥的,都有可能。
此時可以選擇用 RabbitMQ 提供的事務功能或者是confirm 機制 事務機制和 confirm 機制最大的不同在于,事務機制是同步的,你提交一個事務之后會阻塞在那兒,但是 confirm 機制是異步的,你發送個消息之后就可以發送下一個消息,然后那個消息 RabbitMQ 接收了之后會異步回調你的一個接口通知你這個消息接收到了。 - RabbitMQ 弄丟了數據
就是 RabbitMQ 自己弄丟了數據,這個你必須開啟 RabbitMQ 的持久化,就是消息寫入之后會持久化到磁盤,哪怕是 RabbitMQ 自己掛了,恢復之后會自動讀取之前存儲的數據,一般數據不會丟。除非極其罕見的是,RabbitMQ 還沒持久化,自己就掛了,可能導致少量數據丟失,但是這個概率較小。 - 消費端弄丟了數據
RabbitMQ 如果丟失了數據,主要是因為你消費的時候,剛消費到,還沒處理,結果進程掛了,比如重啟了,那么就尷尬了,RabbitMQ 認為你都消費了,這數據就丟了。
這個時候得用 RabbitMQ 提供的 ack 機制,簡單來說,就是你必須關閉 RabbitMQ 的自動 ack,可以通過一個 api 來調用就行,然后每次你自己代碼里確保處理完的時候,再在程序里 ack 一把。這樣的話,如果你還沒處理完,不就沒有 ack 了?那 RabbitMQ 就認為你還沒處理完,這個時候 RabbitMQ 會把這個消費分配給別的 consumer 去處理,消息是不會丟的。
RocketMQ
可以從三個方面來分析rocket的消息可靠性
-
Producer端消息丟失
- producer端防止消息發送失敗,可以采用同步阻塞式的發送(也就是發送同步消息),同步的檢查Brocker返回的狀態是否持久化成功,發送超時或者失敗,則會默認重試2次,rocker選擇了確保消息一定發送成功,但有可能發生重復投遞
- 如果是異步發送消息,會有一個回調接口,當brocker存儲成功或者失敗的時候,也可以在這里根據返回狀態來決定是否需要重試(當然這個是需要我們自己來實現的)
-
Brocker端消息丟失
- rocketmq一般都是先把消息寫到PageCache中,然后再持久化到磁盤上,數據從pagecache刷新到磁盤有兩種方式,同步和異步
- 同步刷盤方式:消息寫入內存的 PageCache后,立刻通知刷盤線程刷盤,然后等待刷盤完成,刷盤線程執行完成后喚醒等待的線程,返回消息寫成功的狀態。這種方式可以保證數據絕對安全,但是吞吐量不大。
- 異步刷盤方式(默認):消息寫入到內存的 PageCache中,就立刻給客戶端返回寫操作成功,當 PageCache中的消息積累到一定的量時,觸發一次寫操作,將 PageCache中的消息寫入到磁盤中。這種方式吞吐量大,性能高,但是 PageCache中的數據可能丟失,不能保證數據絕對的安全。
-
Cousmer端消息丟失
- cousmer端默認是消息之后自動返回消費成功確認ack,但是這時如果我們的程序執行失敗了,數據不就丟失了嗎?
- 所以我們可以將自動提交(AutoCommit)消費響應,設置為在代碼中手動提交,只有真正消費成功之后再通知brocker消費成功,然后更新消費唯一offset或者刪除brocker中的消息
大量消息在 mq 里積壓了幾個小時了還沒解決此時應該怎么辦
- 第一條,為啥會出現消息大量積壓,是本身我們的生產者的消息產多了,還是我們的消費者出現問題了,先弄清楚原因先
- 第二 如果是我們生產的消息多了,那么我們可以多加幾個消費者去消費消息
- 第三,如果說是我們的消費者出現了問題,那么我首先肯定是要修復消費者的bug,但是有一點就是就算我們修復了bug,但是要到生產的流程來說還要花幾個小時才能消費完,這時候,我們要零時寫一個邏輯,把消費者的耗時邏輯直接確認,然后把消息轉到另外一個隊列,另外一個隊列用10背速度去消費,等轉發完成之后,換成正常的消費邏輯,這樣就可以盡快的使業務得到正常的使用了。
說說延時隊列唄
延時隊列,首先,它是一種隊列,隊列意味著內部的元素是有序的,元素出隊和入隊是有方向性的,元素從一端進入,從另一端取出。
其次,延時隊列,最重要的特性就體現在它的延時屬性上,跟普通的隊列不一樣的是,普通隊列中的元素總是等著希望被早點取出處理,而延時隊列中的元素則是希望被在指定時間得到取出和處理,所以延時隊列中的元素是都是帶時間屬性的,通常來說是需要被處理的消息或者任務。
簡單來說,延時隊列就是用來存放需要在指定時間被處理的元素的隊列。
RabbitMQ中的一個高級特性——TTL(Time To Live),當我們有一些特殊的場景,比如注冊幾天后,沒有購買就給他們發優惠卷,這些運營手段,就可以用到這個延時隊列了,在rabbitmq里面就是ttl+死信隊列來實現的。
然后RocketMQ的延時隊列的話用處就不大了,rocketmq實現的延時隊列只支持特定的延時時間段,1s,5s,10s,...2h,不能支持任意時間段的延時
深入聊聊RocketMQ唄,因為rabbitmq的源碼不是Java,所以不好問,但是RocketMQ的源碼還是要大致了解了解
聊聊消息的存儲和發送
- 消息存儲
磁盤如果使用得當,磁盤的速度完全可以匹配上網絡 的數據傳輸速度。目前的高性能磁盤,順序寫速度可以達到600MB/s, 超過了一般網卡的傳輸速度。但是磁盤隨機寫的速度只有大概100KB/s,和順序寫的性能相差6000倍!因為有如此巨大的速度差別,好的消息隊列系統會比普通的消息隊列系統速度快多個數量級。RocketMQ的消息用順序寫,保證了消息存儲的速度。
- 消息發送
Linux操作系統分為【用戶態】和【內核態】,文件操作、網絡操作需要涉及這兩種形態的切換,免不了進行數據復制。
一臺服務器 把本機磁盤文件的內容發送到客戶端,一般分為兩個步驟:
1)read;讀取本地文件內容;
2)write;將讀取的內容通過網絡發送出去。
這兩個看似簡單的操作,實際進行了4 次數據復制,分別是:
- 從磁盤復制數據到內核態內存;
- 從內核態內存復 制到用戶態內存;
- 然后從用戶態 內存復制到網絡驅動的內核態內存;
- 最后是從網絡驅動的內核態內存復 制到網卡中進行傳輸。
通過使用mmap的方式,可以省去向用戶態的內存復制,提高速度。這種機制在Java中是通過MappedByteBuffer實現的
RocketMQ充分利用了上述特性,也就是所謂的“零拷貝”技術,提高消息存盤和網絡發送的速度。
這里需要注意的是,采用MappedByteBuffer這種內存映射的方式有幾個限制,其中之一是一次只能映射1.5~2G 的文件至用戶態的虛擬內存,這也是為何RocketMQ默認設置單個CommitLog日志數據文件為1G的原因了
聊聊分布式事務唄
如何解釋分布式事務呢?事務大家都知道吧?要么都執行要么都不執行 。在同一個系統中我們可以輕松地實現事務,但是在分布式架構中,我們有很多服務是部署在不同系統之間的,而不同服務之間又需要進行調用。比如此時我下訂單然后增加積分,如果保證不了分布式事務的話,就會出現A系統下了訂單,但是B系統增加積分失敗或者A系統沒有下訂單,B系統卻增加了積分。
如今比較常見的分布式事務實現有 2PC、TCC 和 事務最終一致性,一般我們除了強一致性的場景,一般用的可靠消息最終一致性,那么對于RocketMQ 它是怎么實現的呢?
在 RocketMQ 中使用的是 事務消息加上事務反查機制 來解決分布式事務問題的
在第一步發送的 half 消息 ,它的意思是 在事務提交之前,對于消費者來說,這個消息是不可見的 。
你可以試想一下,如果沒有從第5步開始的 事務反查機制 ,如果出現網路波動第4步沒有發送成功,這樣就會產生 MQ 不知道是不是需要給消費者消費的問題,他就像一個無頭蒼蠅一樣。在 RocketMQ 中就是使用的上述的事務反查來解決的
-
事務消息發送及提交
- 發送消息(half消息)。
- 服務端響應消息寫入結果。
- 根據發送結果執行本地事務(如果寫入失敗,此時half消息對業務不可見,本地邏輯不執行)。
- 根據本地事務狀態執行Commit或者Rollback(Commit操作生成消息索引,消息對消費者可見)
-
事務補償
- 對沒有Commit/Rollback的事務消息(pending狀態的消息),從服務端發起一次“回查”
- Producer收到回查消息,檢查回查消息對應的本地事務的狀態
- 根據本地事務狀態,重新Commit或者Rollback
- 其中,補償階段用于解決消息Commit或者Rollback發生超時或者失敗的情況。
聊聊RocketMQ的底層存儲機制
RocketMQ 是如何設計它的存儲結構了。我首先想大家介紹 RocketMQ 消息存儲架構中的三大角色——CommitLog 、ConsumeQueue 和 IndexFile 。
- CommitLog: 消息主體以及元數據的存儲主體,存儲 Producer 端寫入的消息主體內容,消息內容不是定長的。單個文件大小默認1G ,文件名長度為20位,左邊補零,剩余為起始偏移量,比如00000000000000000000代表了第一個文件,起始偏移量為0,文件大小為1G=1073741824;當第一個文件寫滿了,第二個文件為00000000001073741824,起始偏移量為1073741824,以此類推。消息主要是順序寫入日志文件,當文件滿了,寫入下一個文件。
- ConsumeQueue: 消息消費隊列,引入的目的主要是提高消息消費的性能,由于RocketMQ 是基于主題 Topic 的訂閱模式,消息消費是針對主題進行的,如果要遍歷 commitlog 文件中根據 Topic 檢索消息是非常低效的。Consumer 即可根據 ConsumeQueue 來查找待消費的消息。其中,ConsumeQueue(邏輯消費隊列)作為消費消息的索引,保存了指定 Topic 下的隊列消息在 CommitLog 中的起始物理偏移量 offset ,消息大小 size 和消息 Tag 的 HashCode 值。consumequeue 文件可以看成是基于 topic 的 commitlog 索引文件,故 consumequeue 文件夾的組織方式如下:topic/queue/file三層組織結構,具體存儲路徑為:$HOME/store/consumequeue/{topic}/{queueId}/{fileName}。同樣 consumequeue 文件采取定長設計,每一個條目共20個字節,分別為8字節的 commitlog 物理偏移量、4字節的消息長度、8字節tag hashcode,單個文件由30W個條目組成,可以像數組一樣隨機訪問每一個條目,每個 ConsumeQueue文件大小約5.72M;
- IndexFile: IndexFile(索引文件)提供了一種可以通過key或時間區間來查詢消息的方法。
結束
接下來復習下ssm框架
日常求贊
好了各位,以上就是這篇文章的全部內容了,能看到這里的人呀,都是真粉。
創作不易,各位的支持和認可,就是我創作的最大動力,我們下篇文章見
微信 搜 "六脈神劍的程序人生" 回復888 有我找的許多的資料送給大家