什麼是消息隊列?

前言

這次給大家寫寫我學習消息隊列的筆記,希望對大家有幫助。

一、什麼是消息隊列?

消息隊列不知道大家看到這個詞的時候,會不會覺得它是一個比較高端的技術,反正我是覺得它好像是挺牛逼的。

消息隊列,一般我們會簡稱它爲 MQ(Message Queue),嗯,就是很直白的簡寫。

我們先不管消息 (Message) 這個詞,來看看隊列(Queue)。這一看,隊列大家應該都熟悉吧。

隊列是一種先進先出的數據結構。

先進先出

在 Java 裏邊,已經實現了不少的隊列了:

Java 的隊列實現類

那爲什麼還需要消息隊列 (MQ) 這種中間件呢???其實這個問題,跟之前我學 Redis 的時候很像。Redis 是一個以key-value形式存儲的內存數據庫,明明我們可以使用類似 HashMap 這種實現類就可以達到類似的效果了,那還爲什麼要 Redis?《Redis 合集

消息隊列可以簡單理解爲:把要傳輸的數據放在隊列中

圖片來源:https://www.cloudamqp.com/blog/2014-12-03-what-is-message-queuing.html

科普:

二、爲什麼要用消息隊列?

爲什麼要用消息隊列,也就是在問:用了消息隊列有什麼好處。我們看看以下的場景

2.1 解耦

現在我有一個系統 A,系統 A 可以產生一個userId

系統 A 可以產生一個 UserId

然後,現在有系統 B 和系統 C 都需要這個userId去做相關的操作

系統 A 給系統 B 和系統 C 傳入 userId 這個值

寫成僞代碼可能是這樣的:

public class SystemA {

    // 系統B和系統C的依賴
    SystemB systemB = new SystemB();
    SystemC systemC = new SystemC();

    // 系統A獨有的數據userId
    private String userId = "Java3y";

    public void doSomething() {

        // 系統B和系統C都需要拿着系統A的userId去操作其他的事
        systemB.SystemBNeed2do(userId);
        systemC.SystemCNeed2do(userId);

    }
}

結構圖如下:

結構圖

ok,一切平安無事度過了幾個天。

某一天,系統 B 的負責人告訴系統 A 的負責人,現在系統 B 的SystemBNeed2do(String userId)這個接口不再使用了,讓系統 A 別去調它了

於是,系統 A 的負責人說 "好的,那我就不調用你了。",於是就把調用系統 B 接口的代碼給刪掉了

public void doSomething() {

  // 系統A不再調用系統B的接口了
  //systemB.SystemBNeed2do(userId);
  systemC.SystemCNeed2do(userId);

}

又過了幾天,系統 D 的負責人接了個需求,也需要用到系統 A 的 userId,於是就跑去跟系統 A 的負責人說:"老哥,我要用到你的 userId,你調一下我的接口吧"

於是系統 A 說:"沒問題的,這就搞"

系統 A 需要調用系統 D 的接口

然後,系統 A 的代碼如下:

public class SystemA {

    // 已經不再需要系統B的依賴了
    // SystemB systemB = new SystemB();

    // 系統C和系統D的依賴
    SystemC systemC = new SystemC();
    SystemD systemD = new SystemD();

    // 系統A獨有的數據
    private String userId = "Java3y";

    public void doSomething() {


        // 已經不再需要系統B的依賴了
        //systemB.SystemBNeed2do(userId);

        // 系統C和系統D都需要拿着系統A的userId去操作其他的事
        systemC.SystemCNeed2do(userId);
        systemD.SystemDNeed2do(userId);

    }
}

時間飛逝:

於是系統 A 的負責人,每天都被這給騷擾着,改來改去,改來改去…….

還有另外一個問題,調用系統 C 的時候,如果系統 C 掛了,系統 A 還得想辦法處理。如果調用系統 D 時,由於網絡延遲,請求超時了,那系統 A 是反饋fail還是重試??

最後,系統 A 的負責人,覺得隔一段時間就改來改去,沒意思,於是就跑路了。

然後,公司招來一個大佬,大佬經過幾天熟悉,上來就說:將系統 A 的 userId 寫到消息隊列中,這樣系統 A 就不用經常改動了。爲什麼呢?下面我們來一起看看:

系統 A 將 userId 寫到消息隊列中,系統 C 和系統 D 從消息隊列中拿數據

系統 A 將 userId 寫到消息隊列中,系統 C 和系統 D 從消息隊列中拿數據。這樣有什麼好處

這樣一來,系統 A 與系統 B、C、D 都解耦了。

2.2 異步

我們再來看看下面這種情況:系統 A 還是直接調用系統 B、C、D

直接調接口

代碼如下:

public class SystemA {

    SystemB systemB = new SystemB();
    SystemC systemC = new SystemC();
    SystemD systemD = new SystemD();

    // 系統A獨有的數據
    private String userId ;

    public void doOrder() {

        // 下訂單
          userId = this.order();
        // 如果下單成功,則安排其他系統做一些事  
        systemB.SystemBNeed2do(userId);
        systemC.SystemCNeed2do(userId);
        systemD.SystemDNeed2do(userId);

    }
}

假設系統 A 運算出 userId 具體的值需要 50ms,調用系統 B 的接口需要 300ms,調用系統 C 的接口需要 300ms,調用系統 D 的接口需要 300ms。那麼這次請求就需要50+300+300+300=950ms

並且我們得知,系統 A 做的是主要的業務,而系統 B、C、D 是非主要的業務。比如系統 A 處理的是訂單下單,而系統 B 是訂單下單成功了,那發送一條短信告訴具體的用戶此訂單已成功,而系統 C 和系統 D 也是處理一些小事而已。

那麼此時,爲了提高用戶體驗和吞吐量,其實可以異步地調用系統 B、C、D 的接口。所以,我們可以弄成是這樣的:

此時才用了 100ms

系統 A 執行完了以後,將 userId 寫到消息隊列中,然後就直接返回了 (至於其他的操作,則異步處理)。

(例子可能舉得不太好,但我覺得說明到點子上就行了,見諒。)

2.3 削峯 / 限流

我們再來一個場景,現在我們每個月要搞一次大促,大促期間的併發可能會很高的,比如每秒 3000 個請求。假設我們現在有兩臺機器處理請求,並且每臺機器只能每次處理 1000 個請求。

削峯的場景

那多出來的 1000 個請求,可能就把我們整個系統給搞崩了… 所以,有一種辦法,我們可以寫到消息隊列中:

寫到消息隊列中,系統從消息隊列中拿到請求

系統 B 和系統 C 根據自己的能夠處理的請求數去消息隊列中拿數據,這樣即便有每秒有 8000 個請求,那只是把請求放在消息隊列中,去拿消息隊列的消息由系統自己去控制,這樣就不會把整個系統給搞崩。

三、使用消息隊列有什麼問題?

經過我們上面的場景,我們已經可以發現,消息隊列能做的事其實還是蠻多的。

說到這裏,我們先回到文章的開頭,"明明 JDK 已經有不少的隊列實現了,我們還需要消息隊列中間件呢?" 其實很簡單,JDK 實現的隊列種類雖然有很多種,但是都是簡單的內存隊列。爲什麼我說 JDK 是簡單的內存隊列呢?下面我們來看看要實現消息隊列 (中間件) 可能要考慮什麼問題

3.1 高可用

無論是我們使用消息隊列來做解耦、異步還是削峯,消息隊列肯定不能是單機的。試着想一下,如果是單機的消息隊列,萬一這臺機器掛了,那我們整個系統幾乎就是不可用了。

萬一單機的隊列掛掉了

所以,當我們項目中使用消息隊列,都是得集羣/分佈式的。要做集羣/分佈式就必然希望該消息隊列能夠提供現成的支持,而不是自己寫代碼手動去實現。

3.2 數據丟失問題

我們將數據寫到消息隊列上,系統 B 和 C 還沒來得及取消息隊列的數據,就掛掉了。如果沒有做任何的措施,我們的數據就丟了

數據丟失問題

學過 Redis 的都知道,Redis 可以將數據持久化磁盤上,萬一 Redis 掛了,還能從磁盤從將數據恢復過來。同樣地,消息隊列中的數據也需要存在別的地方,這樣才儘可能減少數據的丟失。

那存在哪呢?

同步存儲還是異步存儲?

3.3 消費者怎麼得到消息隊列的數據?

消費者怎麼從消息隊列裏邊得到數據?有兩種辦法:

3.4 其他

除了這些,我們在使用的時候還得考慮各種的問題:

雖然消息隊列給我們帶來了那麼多的好處,但同時我們發現引入消息隊列也會提高系統的複雜性。市面上現在已經有不少消息隊列輪子了,每種消息隊列都有自己的特點,選取哪種 MQ 還得好好斟酌

最後

本文主要講解了什麼是消息隊列,消息隊列可以爲我們帶來什麼好處,以及一個消息隊列可能會涉及到哪些問題。希望給大家帶來一定的幫助。

程序員田螺 專注分享後端面試題,包括計算機網絡、MySql 數據庫、Redis 緩存、操作系統、Java 後端、大廠面試真題等領域。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/GCylgiv1-OPnv0_tI51bmg