高級JAVA開發 MQ部分

MQ

參考和摘自:
中華石杉 《Java工程師面試突擊第1季》
ActiveMQ—知識點整理
消息總線真的能保證冪等
ActiveMQ消息傳送機制以及ACK機制詳解

MQ的作用、爲什麼要用MQ

解耦、異步、消峯

應用場景:

  1. 解耦:利用 發佈/訂閱(Publish/Subscribe)模型,多服務訂閱同一queue,省去生產者主動調用並維護多個消費者(service),消費者可隨時unSubscribe,生產者並不感知。
  2. 異步:利用 點對點( Point-to-Point)模型,將多個任務分別放到不同隊列中,之後直接返回。消費者各自從不同隊列取得任務並消費。這麼做的好處是不用阻塞等待多個任務全部返回再響應用戶操作,加速響應。
  3. 消峯:利用 點對點( Point-to-Point)模型,在系統請求高峯期不採用阻塞式調用,將任務全部打入MQ中,讓系統調用鏈中消費者慢慢消化任務。防止系統被訪問高峯打死(很大原因是直接訪問數據庫,數據庫成爲瓶頸,後面也會在 緩存 章節繼續分析)。

常見的MQ的優缺點

摘自中華石杉老師的筆記

特性 ActiveMQ RabbitMQ RocketMQ Kafka
單機吞吐量 萬級
吞吐量比RocketMQ和Kafka要低了一個數量級
萬級
吞吐量比RocketMQ和Kafka要低了一個數量級
10萬級
RocketMQ也是可以支撐高吞吐的一種MQ
10萬級
這是kafka最大的優點,就是吞吐量高。
一般配合大數據類的系統來進行實時數據計算、日誌採集等場景
topic數量對吞吐量的影響 topic可以達到幾百,幾千個的級別,吞吐量會有較小幅度的下降。
這是RocketMQ的一大優勢,在同等機器下,可以支撐大量的topic
topic從幾十個到幾百個的時候,吞吐量會大幅度下降。所以在同等機器下,kafka儘量保證topic數量不要過多。如果要支撐大規模topic,需要增加更多的機器資源
時效性 ms級 微秒級
這是rabbitmq的一大特點,延遲是最低的
ms級 延遲在ms級以內
可用性
基於主從架構實現高可用性

基於主從架構實現高可用性
非常高
分佈式架構
非常高
kafka是分佈式的,一個數據多個副本,少數機器宕機,不會丟失數據,不會導致不可用
消息可靠性 有較低的概率丟失數據 經過參數優化配置,可以做到0丟失 經過參數優化配置,消息可以做到0丟失
功能支持 MQ領域的功能極其完備 基於erlang開發,所以併發能力很強,性能極其好,延時很低 MQ功能較爲完善,還是分佈式的,擴展性好 功能較爲簡單,主要支持簡單的MQ功能,在大數據領域的實時計算以及日誌採集被大規模使用,是事實上的標準
優劣勢總結 非常成熟,功能強大,在業內大量的公司以及項目中都有應用。
偶爾會有較低概率丟失消息。而且現在社區以及國內應用都越來越少,官方社區現在對ActiveMQ 5.x維護越來越少,幾個月才發佈一個版本。
而且確實主要是基於解耦和異步來用的,較少在大規模吞吐的場景中使用。
erlang語言開發,性能極其好,延時很低;
吞吐量到萬級,MQ功能比較完備
而且開源提供的管理界面非常棒,用起來很好用
社區相對比較活躍,幾乎每個月都發布幾個版本分
在國內一些互聯網公司近幾年用rabbitmq也比較多一些
但是問題也是顯而易見的,RabbitMQ確實吞吐量會低一些,這是因爲他做的實現機制比較重。
而且erlang開發,國內有幾個公司有實力做erlang源碼級別的研究和定製?如果說你沒這個實力的話,確實偶爾會有一些問題,你很難去看懂源碼,你公司對這個東西的掌控很弱,基本職能依賴於開源社區的快速維護和修復bug。
而且rabbitmq集羣動態擴展會很麻煩,不過這個我覺得還好。其實主要是erlang語言本身帶來的問題。很難讀源碼,很難定製和掌控。
接口簡單易用,而且畢竟在阿里大規模應用過,有阿里品牌保障
日處理消息上百億之多,可以做到大規模吞吐,性能也非常好,分佈式擴展也很方便,社區維護還可以,可靠性和可用性都是ok的,還可以支撐大規模的topic數量,支持複雜MQ業務場景
而且一個很大的優勢在於,阿里出品都是java系的,我們可以自己閱讀源碼,定製自己公司的MQ,可以掌控
社區活躍度相對較爲一般,不過也還可以,文檔相對來說簡單一些,然後接口這塊不是按照標準JMS規範走的有些系統要遷移需要修改大量代碼
還有就是阿里出臺的技術,你得做好這個技術萬一被拋棄,社區黃掉的風險,那如果你們公司有技術實力我覺得用RocketMQ挺好的
kafka的特點其實很明顯,就是僅僅提供較少的核心功能,但是提供超高的吞吐量,ms級的延遲,極高的可用性以及可靠性,而且分佈式可以任意擴展
同時kafka最好是支撐較少的topic數量即可,保證其超高吞吐量
而且kafka唯一的一點劣勢是有可能消息重複消費,那麼對數據準確性會造成極其輕微的影響,在大數據領域中以及日誌採集中,這點輕微影響可以忽略
這個特性天然適合大數據實時計算以及日誌收集

使用MQ帶來的問題以及處理辦法

MQ帶來的問題列舉

  1. 系統可用性降低,需要保證MQ健康的運行,否則MQ掛掉會導致整個應用不可用,後果難以設想。
  2. 系統的複雜性變高,比如消息重複消費、消息丟失 等等,需要用一些手段來處理,怎麼處理接下來細說,這裏將會成爲重要考點。
  3. 一致性問題(分佈式事務)。A、B、C、D四個任務必須同時成功,放入MQ後A、B、C都成功了,偏偏D任務失敗了該如何處理。這也是重要考點。

消息重複消費(冪等)問題

消費者拿到消息但是還沒來得及ACK(或者還沒來得及提交offset)就掛掉了,重啓後會重新拿到已經消費但是還沒通知(ACK)給MQ的消息,就產生了重複消費問題。(PS:這裏如果MQ採用AUTO_ACK模式,消費者拿到消息後會第一時間給MQ做出ACK反饋,之後再去消費消息。但還沒來得及消費完就掛掉了,那麼MQ會認爲此條消息處理過了,消費者重啓後會繼續從MQ拿消息,會產生消息丟失的問題,參看:<消息丟失問題>章節)
解決辦法:
數據攜帶唯一id,基於內存Map或Set、Redis、數據庫唯一鍵,保存消費成功的數據,重複數據來第二次時候可以感知到並直接丟棄處理。

面試遇到過這麼一個問題:
面試官:有100w用戶電話號碼數據在數據庫中,並不保證其中是否有重複,要求爲每一個用戶發一條短信並且不可重複發送,現提供一個基於http的短信發送微服務,該如何設計架構,最簡單最快的把短信發送出去?

我的思路:
1.數據量過大,不能在數據庫上做去重,並且用多線程讀取更快。
2.短信服務會成爲瓶頸,需要緩衝。
3.怎麼在消費的過程中直接過濾掉重複消息。

我:用多線程讀取數據(計算好多少頁,每個線程取多少次,一次取多少條)將用戶數據灌入MQ,多個消費者從MQ取得數據先在Redis中讀一下看看有沒有消費過,並在Redis中存入消費過的電話號碼,調用短信服務(多部署幾個)發送。重複電話號碼直接捨棄數據不消費。
接着面試官問:如果就有那麼幾個消費者同時取到電話號碼,巧了,他們取到的電話號碼相同,查了一下Redis,沒人發送過,就有重複發送的問題,怎麼解決。
我:呃…


回來想了一下這個問題,因爲先讀取Redis再寫入Redis並不能保證原子性,出現了併發問題。採用和分佈式鎖相同的思想來解決這個問題。參考下面文章:
Redis分佈式鎖的正確實現方式
用Redis的set加入NX(SET IF NOT EXIST,即當key不存在時,我們進行set操作;若key已經存在,則不做任何操作;)參數保證set原子性並且取得返回值。完美解決~~~


PS:

  1. Redis是主從集羣,上述分佈式鎖會有漏洞。Redisson分佈式鎖實現框架可以彌補漏洞。(slave節點異步同步master數據,有延時。master節點設置鎖還未同步到slave時掛掉了,鎖就失效了)
  2. 由於本人技術水平有限,上述的架構方案並不是最優的,讀文章小夥伴有更好的解決方案請發郵件給我,不勝感激~~~~

消息丟失問題

1.生產者發送消息丟失:
生產者發送消息到MQ過程中,網絡原因丟了。
MQ接收到消息後內部出錯,沒保存下來。
解決辦法:
1.用事務,發送時用try catch包裹發送消息代碼塊,如果發送失敗可以攔截Exception再做進一步處理(重發或者…)
事務機制是同步阻塞的,影響發送消息吞吐量。
2.(RabbitMq)利用confirm機制。把channel設置成confirm模式,發送結果通知給本地實現的接口,如果通知失敗再做進一步的處理(重發或者…) 異步不會阻塞,吞吐量高
2.MQ本身問題丟失:
MQ掛掉了導致內存中消息丟失
解決辦法:配置持久化。但是也有一種可能,消息接收到了但是還沒來得及持久化就掛掉了,還是會丟失一點點數據。
3.MQ到消費者消費過程中導致的丟失,消費端弄丟了數據
消費着拿到消息但是還沒來得及消費就掛掉了,MQ以爲消費者已經處理完了
解決辦法:關閉AUTO_ACK,採用CLIENT_ACK模式,客戶端收到消息處理成功後再手動發送ACK給MQ。如果消費者掛掉了,MQ針對這條消息會ACK超時,重新發給別人繼續消費。

消息順序性問題

這裏假設要處理一個訂單的三個任務,三個任務爲一組:

  1. RabbitMQ等非分佈式MQ解決方案:
    思路:將同訂單任務按順序放到一個Queue中,一個消費者只從一個Queue消費。
    在這裏插入圖片描述
    這裏不能建立無窮多個Queue,將訂單號的hash值對Queue個數取餘分發到對應Queue中即可。
  2. Kafka等分佈式MQ解決方案:
    參考:kafka topic消息分配partition規則(Java源碼)
    思路:由於Kafka是分佈式的,每個Queue中的數據會分佈到多個partition中。Kafka可以保證每個partition中的數據是有順序的,那麼指定key(比如訂單ID),將需要保證順序的任務放到同一partition中(kafka會把key值一樣的任務放到一個partition中,和上述訂單號對Queue個數取餘原理相同),一個消費者只能從一個partition消費,這樣就保證了按順序執行。

如果消費者是多線程併發的從partition取得數據,多線程不能保證執行順序,那麼上述模型就不好用了。
解決辦法: 在消費者端建立多個線程安全的隊列(比如LinkedBlockingQueue,這裏我認爲採用阻塞隊列比較好,省得消耗jvm內存過多導致oom異常),從MQ取得任務,將相同訂單號的任務發送到同一個隊列(也可以用hash取餘的辦法),再對每一個隊列啓動一個線程消費任務,保證順序執行。
在這裏插入圖片描述

消息過期丟失、大量積壓等問題

  1. 過期問題:
    一般不設置消息的過期時間。如果設置了過期時間,只能在事後從數據源頭找出數據,寫程序將數據重新發送到MQ中。
  2. 大量積壓後如何快速消費:
    ActiveMQ、RabbitMQ等非分佈式MQ單個Queue數據量過大增加臨時MQ節點也不能解決問題(每個節點存儲全量數據),需要增加消費者臨時節點來加速消費。
    Kafka是分佈式MQ,每個消費者只能指定一個partition消費,那麼新建立一個更多節點的Kafka集羣,增加臨時服務將原Kafka集羣中的數據直接分發到新Kafka集羣,這樣消息會平均分配到更多的機器中,減緩MQ壓力,再針對新Kafka集羣添加實際業務處理的消費者,增加消費速度即可。
  3. MQ積壓導致磁盤空間不足:
    (Kafka)增加臨時消費節點將消息寫到臨時MQ集羣中。
    或者增加臨時消費者拿到數據直接將數據扔掉,事後做補償。防止MQ直接壓垮掉導致整個系統不可用。

如何保證MQ高可用性

RabbitMQ高可用以及部署模式

單機模式,普通集羣模式,鏡像集羣模式

  1. 單機模式
  2. 普通集羣模式
    集羣中Master保存Queue元數據(Queue的屬性之類的數據)和Queue數據,slave只保存Queue的元數據,在訪問slave節點時,slave去和主節點通信取得Queue數據。
    總結:
    優點:提高消費吞吐量
    缺點:1. 性能開銷大,在集羣內部產生大量數據傳輸 2.可用性幾乎沒有保障,主節點掛掉了,整個MQ不可用。
  3. 鏡像集羣模式
    所有節點都保存Queue的所有數據。在寫入數據時,各個節點自動同步。消費數據時同樣。
    總結:
    優點:高可用,單個節點掛掉不影響整個集羣
    缺點:1.性能開銷大,需要所有機器同步所有數據。 2.不是分佈式的,單Queue數據量超級大,超出單機最大容量時無法處理。 3.擴展性很差,和第2點一樣,加機器也不能緩解數據量超級大的問題。

如何開啓鏡像集羣模式

在管理控制檯新增一個策略,這個策略是鏡像集羣模式的策略,指定的時候可以要求數據同步到所有節點,也可以要求同步到指定數量的節點,然後再次創建Queue的時候應用這個策略,就會自動將數據同步到其他的節點上去了。

kafka的高可用性

多個broker組成,每個broker是一個節點;創建一個topic,這個topic可以劃分爲多個partition,每個partition可以存在於不同的broker上,每個partition放一部分數據。(可以理解爲每個Queue的數據被劃分到不同機器上,換言說每個機器都持有同一個Queue的一部分數據)。在0.8版本以前沒有HA機制,其中一臺機器宕機則數據直接丟失。0.8版本後可以針對每個partition設置多個broker,其中一臺是leader節點,其餘是follower節點,只有leader節點提供對外讀寫服務,數據讀寫自動同步到follower節點,如果leader掛掉,follower們通過選舉算法選舉一個follower作爲新的leader繼續提供服務。

摘自中華石杉《Java工程師面試突擊第1季(可能是史上最好的Java面試突擊課程)\06_引入消息隊列之後該如何保證其高可用性?\視頻\04》圖片摘自中華石杉《Java工程師面試突擊第1季(可能是史上最好的Java面試突擊課程)\06_引入消息隊列之後該如何保證其高可用性?\視頻\04

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章