之前閱讀過一篇美團的技術文章《消息隊列設計精要》,覺得寫的不錯,可以讓人更加了解消息隊列

消息隊列的設計要點,大致總結整理如下:

  • 最簡單的消息隊列可以做成一個消息轉發器,把一次RPC做成兩次RPC。
    • 構建一個整體的數據流,例如producer發送給broker,broker發送給consumer,consumer回復消費確認,broker刪除/備份消息等。
    • 利用RPC將數據流串起來(可以使用開源RPC框架,比如Dubbo等)
      • 負載均衡、
      • 服務發現、
      • 通信協議:Thrift,Dubbo
      • 序列化協議
  • 存儲子系統:通過存儲承載消息堆積,然後在合適的時機投遞消息
    • 持久化、非持久化;
    • 從速度來看,文件系統 > 分散式KV(持久化)> 分散式文件系統 > 資料庫,而可靠性卻截然相反。需要根據具體業務選擇
  • 消費關係的保存
    • 單播

      :點對點
    • 多播:一點對多點
    • 通用設計:支持組間廣播,不同的組註冊不同的訂閱。組內的不同機器,如果註冊一個相同的ID,則單播;如果註冊不同的ID(如IP地址+埠),則廣播。
    • 廣播關係的維護,一般由於消息隊列本身都是集群,所以都維護在公共存儲上,如config server、zookeeper等。
  • 消費確認(任務執行完整性):
    • 把消息的送達和消息的處理分開,這樣才真正的實現了消息隊列的本質-解耦。
    • 對於沒有特殊邏輯的消息,默認Auto Ack也是可以的
    • 但一定要允許消費方主動進行消費確認ack,並與broker約定下次投遞時間
  • 高級特性
    • 可靠投遞(最終一致性):
      • 高可用:超時重發與消息重複、消息隊列冪等設計(broker多機器共享一個DB或者一個分散式文件/kv系統)、定時任務補償
      • 不是所有的系統都要求最終一致性或者可靠投遞。任何基礎組件要服務於業務場景。
      • 消息可能會重複,並且在異常情況下,要接受消息的延遲。
      • 每當要發生不可靠的事情(RPC等)之前,先將消息落地,然後發送。
      • 當消息發送失敗或者不知道成功失敗(比如超時)時,消息狀態是待發送。
        • 對於各種不確定(超時、down機、消息沒有送達、送達後數據沒落地、數據落地了回復沒收到),其實對於發送方來說,都是一件事情,就是消息沒有送達。
      • 定時任務不停輪詢所有待發送消息,最終一定可以送達。
    • 重複消息
      • 如何鑒別消息重複,並冪等的處理重複消息:
        • 版本號
        • 狀態機
      • 一個消息隊列server如何盡量減少重複消息的投遞:
        • 鑒別消息重複:broker記錄MessageId,直到投遞成功後清除,重複的ID到來不做處理。
        • 減少重複投遞:對於server投遞到consumer的消息,由於不確定對端是在處理過程中還是消息發送丟失的情況下,有必要記錄下投遞的IP地址。決定重發之前詢問這個IP,消息處理成功了嗎?如果詢問無果,再重發。
    • 順序消息
      • 順序消息要求:允許消息丟失;從發送方到服務方到接受者都是單點單線程。
      • pull模式實現比較容易(見下)
      • 一個主流消息隊列的設計範式里,應該是不丟消息的前提下,盡量減少重複消息,不保證消息的投遞順序。
    • 事務特性
      • 在與本地業務的同一個事務中,本地消息落地(落庫、需要業務方提供資料庫)
      • 消息只要投遞到服務端確認後本地才做刪除
      • 定時任務掃描本地消息庫表進行補償發送
    • 性能優化
      • 非同步
        • 對於客戶端來說,同步與非同步主要是拿到一個Result,還是Future(Listenable)的區別。實現方式可以是線程池,NIO或者其他事件機制。
        • 服務端非同步需要RPC協議支持。參考servlet 3.0規範,服務端可以吐一個future給客戶端,並且在future done的時候通知客戶端。
        • 實踐:
          • 客戶端必須等待服務端消息成功落地,才算是消息發送成功。
          • 我們不希望消息的發送阻塞客戶端的主流程,所以可以先使用線程池提交一個發送請求,主流程繼續往下走。
          • 服務端是純非同步。客戶端的線程池wait在服務端吐回的future上,直到服務端處理完畢,才解除阻塞繼續進行。
      • 批量:消費者合適消費消息:通過網路請求小包合併成大包提高性能
    • 消息消費是push還是pull
      • push:push模型最大的致命傷是慢消費。
        • 如果消費者的速度比發送者的速度慢很多,勢必造成消息在broker的堆積。
        • 最致命的是broker給consumer推送一堆consumer無法處理的消息,consumer不是reject就是error,然後來回踢皮球
        • 所以對於建立索引等慢消費,消息量有限且到來的速度不均勻的情況,pull模式比較合適。
      • pull:pull模式存在消息延遲與忙等問題
        • pull模式如果想做到全局順序消息,就相對容易很多:
          • producer對應partition,並且單線程。
          • consumer對應partition,消費確認(或批量確認),繼續消費即可。
        • 所以對於日誌push送這種最好全局有序,但允許出現小誤差的場景,pull模式非常合適。如果你不想看到通篇亂套的日誌~~Anyway,需要順序消息的場景還是比較有限的而且成本太高,請慎重考慮。

本文首發於公眾號:EnjoyMoving,歡迎關注交流~

wx4.sinaimg.cn/mw690/73(公眾號二維碼)

推薦閱讀:

查看原文 >>
相关文章