「消息隊列」看過來

一、什麼是消息隊列?


「消息隊列」看過來

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

二、為什麼需要消息隊列?


好處一:解耦

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

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

隨著我們業務模塊接入越來越多,我們的代碼與其他模塊越來越耦合,修改代碼的難度也指數級的增加,所以我們引入「消息隊列」,把「設備壞了」這樣的消息發送到隊列中,其他關心這條消息的業務就會得到這樣的「通知」,然後就會去做對應的事,這樣各個模塊之間就

解耦了。偽代碼看上去如下:

好處二:異步處理

接著上面的例子,假設我們已經把「發送郵件」、「修改庫存」以及「通知經理」的代碼都寫入了我們的 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,它少量代碼就可以高效地實現高級應用場景。


分享到:


相關文章: