高級JAVA開發 MQ部分
MQ
參考和摘自:
中華石杉 《Java工程師面試突擊第1季》
ActiveMQ—知識點整理
消息總線真的能保證冪等
ActiveMQ消息傳送機制以及ACK機制詳解
MQ的作用、爲什麼要用MQ
解耦、異步、消峯
應用場景:
- 解耦:利用 發佈/訂閱(Publish/Subscribe)模型,多服務訂閱同一queue,省去生產者主動調用並維護多個消費者(service),消費者可隨時unSubscribe,生產者並不感知。
- 異步:利用 點對點( Point-to-Point)模型,將多個任務分別放到不同隊列中,之後直接返回。消費者各自從不同隊列取得任務並消費。這麼做的好處是不用阻塞等待多個任務全部返回再響應用戶操作,加速響應。
- 消峯:利用 點對點( 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帶來的問題列舉
- 系統可用性降低,需要保證MQ健康的運行,否則MQ掛掉會導致整個應用不可用,後果難以設想。
- 系統的複雜性變高,比如消息重複消費、消息丟失 等等,需要用一些手段來處理,怎麼處理接下來細說,這裏將會成爲重要考點。
- 一致性問題(分佈式事務)。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:
- Redis是主從集羣,上述分佈式鎖會有漏洞。Redisson分佈式鎖實現框架可以彌補漏洞。(slave節點異步同步master數據,有延時。master節點設置鎖還未同步到slave時掛掉了,鎖就失效了)
- 由於本人技術水平有限,上述的架構方案並不是最優的,讀文章小夥伴有更好的解決方案請發郵件給我,不勝感激~~~~
消息丟失問題
- 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超時,重新發給別人繼續消費。
消息順序性問題
這裏假設要處理一個訂單的三個任務,三個任務爲一組:
- RabbitMQ等非分佈式MQ解決方案:
思路:將同訂單任務按順序放到一個Queue中,一個消費者只從一個Queue消費。
這裏不能建立無窮多個Queue,將訂單號的hash值對Queue個數取餘分發到對應Queue中即可。 - 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取餘的辦法),再對每一個隊列啓動一個線程消費任務,保證順序執行。
消息過期丟失、大量積壓等問題
- 過期問題:
一般不設置消息的過期時間。如果設置了過期時間,只能在事後從數據源頭找出數據,寫程序將數據重新發送到MQ中。 - 大量積壓後如何快速消費:
ActiveMQ、RabbitMQ等非分佈式MQ單個Queue數據量過大增加臨時MQ節點也不能解決問題(每個節點存儲全量數據),需要增加消費者臨時節點來加速消費。
Kafka是分佈式MQ,每個消費者只能指定一個partition消費,那麼新建立一個更多節點的Kafka集羣,增加臨時服務將原Kafka集羣中的數據直接分發到新Kafka集羣,這樣消息會平均分配到更多的機器中,減緩MQ壓力,再針對新Kafka集羣添加實際業務處理的消費者,增加消費速度即可。 - MQ積壓導致磁盤空間不足:
(Kafka)增加臨時消費節點將消息寫到臨時MQ集羣中。
或者增加臨時消費者拿到數據直接將數據扔掉,事後做補償。防止MQ直接壓垮掉導致整個系統不可用。
如何保證MQ高可用性
RabbitMQ高可用以及部署模式
單機模式,普通集羣模式,鏡像集羣模式
- 單機模式
- 普通集羣模式:
集羣中Master保存Queue元數據(Queue的屬性之類的數據)和Queue數據,slave只保存Queue的元數據,在訪問slave節點時,slave去和主節點通信取得Queue數據。
總結:
優點:提高消費吞吐量
缺點:1. 性能開銷大,在集羣內部產生大量數據傳輸 2.可用性幾乎沒有保障,主節點掛掉了,整個MQ不可用。 - 鏡像集羣模式:
所有節點都保存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