「訊息佇列」看過來!

我沒有三顆心臟發表於2019-07-16

為了獲得更良好的閱讀體驗,建議您前往個人獨立域名部落格觀看:傳送門
)

「訊息佇列」看過來!

一、什麼是訊息佇列?


「訊息佇列」看過來!

當我試圖用一則通俗的比喻來說明這個概念的時候,我想到一個有意思的比喻:如果把佇列抽象成一個集合體,那麼訊息佇列也就是一堆訊息的集合。按照這個思路我想到了「雜誌」。這不就是一堆訊息的集合嗎,關心這些訊息的人都能通過「購買」來獲得這些訊息,而我可以通過不同種類的「雜誌」或許到不同的訊息。並且如果我作為出版方,我可以提供所有出版過的「雜誌」,也可以選擇讓讀者只能購買近期的。

二、為什麼需要訊息佇列?


好處一:解耦

假設我們做了一個會議室預定系統,我們的一個裝置壞了。我們需要通知預定這個會議室的所有人,於是我們需要發郵件,虛擬碼如下:

@Service
public class EquipmentServiceImpl implements EquipmentService {
    @Autowired private EmailService emailService;
    @Autowired private EquipmentRepository equipmentRepository;

    public void setEquipmentBroken(Long id) {
        Equipment equipment = equipmentRepository.findById(id);
        equipment.setStatus(Equipment.StatusEnum.BROKEN);

        emailService.sendEmail();
    }
}

問題來了,如果我們後來發現裝置壞了並且需要更改可用庫存的數量,這時候我們是不是要在這裡加入 InventoryService 庫存服務的程式碼呢?後來如果經理說裝置壞了應該通知他才對啊,所以我們要不要加入 emailService.sendEmailTo(Manager) 這樣的程式碼呢?

隨著我們業務模組接入越來越多,我們的程式碼與其他模組越來越耦合,修改程式碼的難度也指數級的增加,所以我們引入「訊息佇列」,把「裝置壞了」這樣的訊息傳送到佇列中,其他關心這條訊息的業務就會得到這樣的「通知」,然後就會去做對應的事,這樣各個模組之間就解耦了。虛擬碼看上去如下:

public void setEquipmentBroken(Long id) {
    Equipment equipment = equipmentRepository.findById(id);
    equipment.broken();

    eventBus.publish(new EquipmentBrokenEvent(equipment.id));
}

好處二:非同步處理

接著上面的例子,假設我們已經把「傳送郵件」、「修改庫存」以及「通知經理」的程式碼都寫入了我們的 Service 程式碼中,它們分別耗時:30ms、50ms、80ms,並且我們得知,原本最主要的功能其實是「傳送郵件」,但我們完成主要的功能之後卻等待了更多的額外時間,這顯示是不合理的。

所以我們為了提高使用者體驗&提高吞吐量,我們其實可以引入「訊息佇列」來進行非同步的操作。

好處三:削峰/限流

「訊息佇列」看過來!

假設我們的伺服器最多能支援每秒 1000 個請求,而我們公司在節日要搞促銷,為了避免伺服器掛掉我們額外申請了兩臺伺服器做了負載均衡,於是我們現在的機器最理想的情況能夠支援每秒 3000 個請求,但奈何活動太火爆了,每秒來的請求有大概 4000 個,這些多出來的請求就可能導致伺服器給直接掛掉了。

「訊息佇列」看過來!

所以我們就引入了一個「訊息佇列」,讓訊息不直接到達伺服器,而是先讓「訊息佇列」儲存這些資料,然後讓下面的伺服器每一次都取各自能處理的請求數再去處理,這樣當請求數超過伺服器最大負載時,就不至於把伺服器搞掛了。

三、訊息佇列適用的場景


基於上面的描述,我們大概能想到「訊息佇列」的侷限性,例如當「生產者」需要「從消費者獲得反饋」時,就會出現一定的問題。例如我之前嘗試著使用「事件驅動」的方式編碼時,我想要把 Service 的一些主邏輯給轉移到關注該事件的監聽器上時,發現有點問題,我原本的意圖是想讓一部分程式碼解耦,但作為主邏輯的一部分我需要保證它們準確的執行,當我使用「訊息」的方式傳遞出去時,我無法得到消費者的反饋,所以最終我還是把主邏輯給遷回來了,算是一次失敗的嘗試吧。

場景一:非同步處理

通過上述的問題你也看到了,「訊息佇列」適用於非同步處理,並且是那些不期望從消費者得到反饋的處理。就好像一開始說到的裝置壞了的問題,我只需要通知裝置壞了,至於之後需要做什麼事,關心的人自然會去做相應的處理。

場景二:日誌收集

上面提到的非同步處理,跟日誌系統似乎搭配起來也很好。特別是當你需要把日誌發往單獨的資料平臺的時候,「訊息佇列」尤為有用,我們不再需要在業務程式碼裡面侵入我們的各種打點or日誌,只需要簡單的釋出一條訊息,再去關注做處理就好了。

場景四:應用解耦

基於上面的例子你應該也能感受一二了。

場景三:流量削峰

這也是「訊息佇列」常見的場景,通過引入「訊息佇列」,我們一來可以控制請求的人數,二來也可以緩解短時間內高流量的壓力。

場景四:訊息通訊

訊息通訊是指,訊息佇列一般都內建了高效的通訊機制,因此也可以用在純的訊息通訊。比如實現點對點訊息佇列,或者聊天室等。

四、常見訊息佇列中介軟體


如果自己設計一個?

我們在討論市面上常見的「訊息佇列」中介軟體之前,我們先來考慮自己造一個怎麼樣?如果是你自己來設計,你會怎麼做?乍一想,似乎每個語言都會有自己實現的「佇列」,往佇列裡塞資料,再從佇列裡面挨個取就行了?

「訊息佇列」看過來!

但是一細想好像事情並不簡單。作為一個「訊息佇列」,你首先要保證資料不能給人家弄丟了吧?存記憶體?萬一斷電了怎麼辦?寫磁碟?訊息量超過系統寫磁碟速率上限了怎麼辦?備份又該怎麼做呢?

好,假設我一整搗鼓,保證了我的資料不會丟失了,下一個問題,生產者怎麼往「訊息佇列」裡面塞資料?我的意思是,生產者可能不止一個,把全量的訊息放在一個佇列似乎不太合適,我需要給這些訊息分個類吧?新來了一個分類的訊息我怎麼動態的擴容呢?消費者又如何消費這些資料呢?多個消費者之間又如何進行協調呢?

好吧..總之問題挺多的..並不像表面那麼簡單。

RabbitMQ

「訊息佇列」看過來!

RabbitMQ 是使用 Erlang 編寫的一個開源的訊息佇列,本身支援很多的協議:AMQP,XMPP, SMTP, STOMP,也正因如此,它非常重量級,更適合於企業級的開發。同時實現了 Broker 構架,這意味著訊息在傳送給客戶端時先在中心佇列排隊。對路由,負 載均衡或者資料持久化都有很好的支援。

Redis

Redis 也能用來做「訊息佇列」。Redis 是一個基於 Key-Value 對的 NoSQL 資料庫,開發維護很活躍。雖然它是一個 Key-Value 資料庫儲存系統,但它本身支援 MQ 功能, 所以完全可以當做一個輕量級的佇列服務來使用。對於 RabbitMQ 和 Redis 的入隊和出隊操作,各執行 100 萬次,每 10 萬次記錄一次執行時間。測試 資料分為 128 Bytes、512 Bytes、1 K和 10 K四個不同大小的資料。實驗表明:入隊時,當資料比較小時 Redis 的效能要高於 RabbitMQ,而如果資料大小超過了 10 K,Redis 則慢的無法忍受;出隊時,無論資料大小,Redis 都表現出非常好的效能,而 RabbitMQ 的出隊效能則遠低於Redis。

Kafka/Jafka

Kafka 是 Apache 下的一個子專案,是一個高效能跨語言分散式 Publish/Subscribe 訊息佇列系統,而 Jafka 是在 Kafka 之上孵化而來的,即 Kafka 的一個升級版。

具有以下特性:

  • 快速持久化,可以在O(1)的系統開銷下進行訊息持久化;
  • 高吞吐,在一臺普通的伺服器上既可以達到 10 W/s的吞吐速率;
  • 完全的分散式系統,Broker、Producer、Consumer都原生自動支援分散式,自動實現複雜均衡;
  • 支援 Hadoop 資料並行載入,對於像Hadoop的一樣的日誌資料和離線分析系統,但又要求實時處理的限制,這是一個可行的解決方案。

Kafka 通過 Hadoop 的並行載入機制來統一了線上和離線的訊息處理。Apache Kafka 相對於 ActiveMQ 是一個非常輕量級的訊息系統,除了效能非常好之外,還是一個工作良好的分散式系統。

ZeroMQ

ZeroMQ 號稱最快的訊息佇列系統,尤其針對大吞吐量的需求場景。ZeroMQ 能夠實現 RabbitMQ 不擅長的高階 / 複雜的佇列,但是開發人員需要自己組合多種技術框架,技術上的複雜度是對這 MQ 能夠應用成功的挑戰。ZeroMQ 具有一個獨特的非中介軟體的模式,你不需要安裝和執行一個訊息伺服器或中介軟體,因為你的應用程式將扮演這個伺服器角色。你只需要簡單的引用 ZeroMQ 程式庫,可以使用 NuGet 安裝,然後你就可以愉快的在應用程式之間傳送訊息了。但是 ZeroMQ 僅提供非永續性的佇列,也就是說如果當機,資料將會丟失。其中,Twitter 的 Storm 0.9.0 以前的版本中預設使用 ZeroMQ 作為資料流的傳輸(Storm 從 0.9 版本開始同時支援 ZeroMQ 和 Netty 作為傳輸模組)。

ActiveMQ

ActiveMQ 是 Apache 下的一個子專案。 類似於 ZeroMQ,它能夠以代理人和點對點的技術實現佇列。同時類似於 RabbitMQ,它少量程式碼就可以高效地實現高階應用場景。

參考資料


  1. Kafka 設計解析(一):Kafka 背景及架構介紹
  2. 淺談訊息佇列及常見的訊息中介軟體
  3. 訊息佇列介紹及常用MQ對比
  4. 什麼是訊息佇列?
  5. 訊息佇列的使用場景是怎樣的? - 知乎

按照慣例黏一個尾巴:

歡迎轉載,轉載請註明出處!
獨立域名部落格:wmyskxz.com
簡書ID:@我沒有三顆心臟
github:wmyskxz
歡迎關注公眾微訊號:wmyskxz
分享自己的學習 & 學習資料 & 生活
想要交流的朋友也可以加qq群:3382693

相關文章