rabbitmq簡介

基礎簡介

基本概念

AMQP

AMQP,即Advanced Message Queuing Protocol,高級消息隊列協議,是應用層協議的一個開放標準,爲面向消息的中間件設計。消息中間件主要用於組件之間的解耦,消息的發送者無需知道消息使用者的存在,反之亦然。
AMQP的主要特徵是面向消息、隊列、路由(包括點對點和發佈/訂閱)、可靠性、安全。
RabbitMQ是一個開源的AMQP實現,服務器端用Erlang語言編寫,支持多種客戶端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用於在分佈式系統中存儲轉發消息,在易用性、擴展性、高可用性等方面表現不俗。

ConnectionFactory、Connection、Channel

Connection是RabbitMQ的socket鏈接(TCP連接),它封裝了socket協議相關部分邏輯。ConnectionFactory爲Connection的製造工廠。

Channel是我們與RabbitMQ打交道的最重要的一個接口,我們大部分的業務操作是在Channel這個接口中完成的,包括定義Queue、定義Exchange、綁定Queue與Exchange、發佈消息等。

一個Connection支持建立多個Channel,而我們的指令,就是通過channel來告知到RabbitMQServer的。一般情況下,程序起始,第一步就是建立Connection,第二步就是建立Channel(當然關閉的時候,需要先關閉channel,再關閉Connection)。

Vhost

虛擬主機。一個RabbitMQ物理服務器上可以支持有多個vhost。不同的vhost可以認爲都是一個RabbitMQ Server。邏輯上隔離不同的實例,包含自己的exchange、queue、binding,可以避免exchange和queue的命名衝突,權限控制也是以vhost爲單位,可以給用戶指定一個或多個vhost。

Queue

Queue(隊列)是RabbitMQ的內部對象,用於存儲消息,用下圖表示。
RabbitMQ基礎概念詳細介紹

RabbitMQ中的消息都只能存儲在Queue中,生產者(下圖中的P)生產消息並最終投遞到Queue中,消費者(下圖中的C)可以從Queue中獲取消息並消費。

RabbitMQ基礎概念詳細介紹

多個消費者可以訂閱同一個Queue,這時Queue中的消息會被平均分攤給多個消費者進行處理,而不是每個消費者都收到所有的消息並處理。

RabbitMQ基礎概念詳細介紹

Message acknowledgment(ACK)

 在實際應用中,可能會發生消費者收到Queue中的消息,但沒有處理完成就宕機(或出現其他意外)的情況,這種情況下就可能會導致消息丟失。爲了避免這種情況發生,我們可以要求消費者在消費完消息後發送一個回執給RabbitMQ,RabbitMQ收到消息回執(Message acknowledgment)後纔將該消息從Queue中移除;如果RabbitMQ沒有收到回執並檢測到消費者的RabbitMQ連接斷開,則RabbitMQ會將該消息發送給其他消費者(如果存在多個消費者)進行處理。

Message durability

如果我們希望即使在RabbitMQ服務重啓的情況下,也不會丟失消息,我們可以將Queue與Message都設置爲可持久化的(durable),這樣可以保證絕大部分情況下我們的RabbitMQ消息不會丟失。

消息持久化必須滿足三個條件:

  1. exchange聲明爲持久化(durable=true)
  2. 隊列聲明爲持久化(durable=true)
  3. 消息投遞模式聲明爲持久化(delivery_mode=2)

如果只是設置了exchange持久化和queue持久化,中間傳遞的消息會丟失。

啓用持久化會損失性能,吞吐量甚至可能降低10倍

RabbitMQ集羣中的隊列並沒有備份,而是均勻分佈在各個節點,如果隊列所在節點掛掉,在節點恢復重建隊列前消息會丟失

如果在持久化到磁盤前崩潰,消息仍然會丟失。

Prefetch count

前面我們講到如果有多個消費者同時訂閱同一個Queue中的消息,Queue中的消息會被平攤給多個消費者。這時如果每個消息的處理時間不同,就有可能會導致某些消費者一直在忙,而另外一些消費者很快就處理完手頭工作並一直空閒的情況。我們可以通過設置prefetchCount來限制Queue每次發送給每個消費者的消息數,比如我們設置prefetchCount=1,則Queue每次給每個消費者發送一條消息;消費者處理完這條消息後Queue會再給該消費者發送一條消息。

RabbitMQ基礎概念詳細介紹

Exchange

由Exchange將消息路由到一個或多個Queue中(或者丟棄)。

生產者將消息發送到Exchange(交換器,下圖中的X),由Exchange將消息路由到一個或多個Queue中(或者丟棄)。

 RabbitMQ基礎概念詳細介紹

routing key

生產者在將消息發送給Exchange的時候,一般會指定一個routing key,來指定這個消息的路由規則,而這個routing key需要與Exchange Type及binding key聯合使用才能最終生效。
在Exchange Type與binding key固定的情況下(在正常使用時一般這些內容都是固定配置好的),我們的生產者就可以在發送消息給Exchange時,通過指定routing key來決定消息流向哪裏。
RabbitMQ爲routing key設定的長度限制爲255 bytes。 

Binding

RabbitMQ中通過Binding將Exchange與Queue關聯起來,這樣RabbitMQ就知道如何正確地將消息路由到指定的Queue了。

在綁定(Binding)Exchange與Queue的同時,一般會指定一個binding key;消費者將消息發送給Exchange時,一般會指定一個routing key;當binding key與routing key相匹配時,消息將會被路由到對應的Queue中。

  • 在綁定多個Queue到同一個Exchange的時候,這些Binding允許使用相同的binding key。
  • binding key 並不是在所有情況下都生效,它依賴於Exchange Type,比如fanout類型的Exchange就會無視binding key,而是將消息路由到所有綁定到該Exchange的Queue。

Exchange type

RabbitMQ常用的Exchange Type有fanout、direct、topic、headers這四種。

direct

direct類型的Exchange路由規則也很簡單,它會把消息路由到那些binding key與routing key完全匹配的Queue中。

RabbitMQ基礎概念詳細介紹

以上圖的配置爲例,我們以routingKey=”error”發送消息到Exchange,則消息會路由到Queue1(amqp.gen-S9b…,這是由RabbitMQ自動生成的Queue名稱)和Queue2(amqp.gen-Agl…);如果我們以routingKey=”info”或routingKey=”warning”來發送消息,則消息只會路由到Queue2。如果我們以其他routingKey發送消息,則消息不會路由到這兩個Queue中。

fanout

fanout類型的Exchange路由規則非常簡單,它會把所有發送到該Exchange的消息路由到所有與它綁定的Queue中。
RabbitMQ基礎概念詳細介紹

上圖中,生產者(P)發送到Exchange(X)的所有消息都會路由到圖中的兩個Queue,並最終被兩個消費者(C1與C2)消費。

topic

前面講到direct類型的Exchange路由規則是完全匹配binding key與routing key,但這種嚴格的匹配方式在很多情況下不能滿足實際業務需求。topic類型的Exchange在匹配規則上進行了擴展,它與direct類型的Exchage相似,也是將消息路由到binding key與routing key相匹配的Queue中,但這裏的匹配規則有些不同,它約定:

  • routing key爲一個句點號“. ”分隔的字符串(我們將被句點號“. ”分隔開的每一段獨立的字符串稱爲一個單詞),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”
  • binding key與routing key一樣也是句點號“. ”分隔的字符串
  • binding key中可以存在兩種特殊字符“*”與“#”,用於做模糊匹配,其中“*”用於匹配一個單詞,“#”用於匹配多個單詞(可以是零個)

RabbitMQ基礎概念詳細介紹

以上圖中的配置爲例,routingKey=”quick.orange.rabbit”的消息會同時路由到Q1與Q2,routingKey=”lazy.orange.fox”的消息會路由到Q1,routingKey=”lazy.brown.fox”的消息會路由到Q2,routingKey=”lazy.pink.rabbit”的消息會路由到Q2(只會投遞給Q2一次,雖然這個routingKey與Q2的兩個bindingKey都匹配);routingKey=”quick.brown.fox”、routingKey=”orange”、routingKey=”quick.orange.male.rabbit”的消息將會被丟棄,因爲它們沒有匹配任何bindingKey。

headers

headers類型的Exchange不依賴於routing key與binding key的匹配規則來路由消息,而是根據發送的消息內容中的headers屬性進行匹配。

headers類型的Exchange不依賴於routing key與binding key的匹配規則來路由消息,而是根據發送的消息內容中的headers屬性進行匹配。
在綁定Queue與Exchange時指定一組鍵值對;當消息發送到Exchange時,RabbitMQ會取到該消息的headers(也是一個鍵值對的形式),對比其中的鍵值對是否完全匹配Queue與Exchange綁定時指定的鍵值對;如果完全匹配則消息會路由到該Queue,否則不會路由到該Queue。

 

RPC

MQ本身是基於異步的消息處理,前面的示例中所有的生產者(P)將消息發送到RabbitMQ後不會知道消費者(C)處理成功或者失敗。但實際的應用場景中,我們很可能需要一些同步處理,需要同步等待服務端將我的消息處理完成後再進行下一步處理。這相當於RPC(Remote Procedure Call,遠程過程調用)。在RabbitMQ中也支持RPC。

RabbitMQ基礎概念詳細介紹
RabbitMQ中實現RPC的機制是:

  • 客戶端發送請求(消息)時,在消息的屬性(MessageProperties,在AMQP協議中定義了14中properties,這些屬性會隨着消息一起發送)中設置兩個值replyTo(一個Queue名稱,用於告訴服務器處理完成後將通知我的消息發送到這個Queue中)和correlationId(此次請求的標識號,服務器處理完成後需要將此屬性返還,客戶端將根據這個id瞭解哪條請求被成功執行了或執行失敗)
  • 服務器端收到消息並處理
  • 服務器端處理完消息後,將生成一條應答消息到replyTo指定的Queue,同時帶上correlationId屬性
  • 客戶端之前已訂閱replyTo指定的Queue,從中收到服務器的應答消息後,根據其中的correlationId屬性分析哪條請求被執行了,根據執行結果進行後續業務處理

推拉模式

對於MQ來說,有兩種經典的消費模式:推、拉。部分MQ只支持推模式或者拉模式。而RabbitMQ同時支持兩種模式。

推和拉兩種模式都是針對消費者而言的。

拉消息模式

拉模式的話,就是消費者根據自己的意願,主動從MQ中獲取消息。MQ將不會主動將消息告知到消費者。

核心方法:GetResponse basicGet(String queue, Boolean autoACK);

示例代碼如下:

public class ReceiverByGet {
    public final static String QUEUE_NAME = "hello";
    public static void main(String[] args) {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
 
 
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
 
 
        while(true) {
            // 這裏纔是重點
            GetResponse response = channel.basicGet(QUEUE_NAME, true);
            if (response = null) {
                System.out.println("Get Nothing");
                TimeUnit.MILLISECONDS.sleep(1000);
            } else {
                String message = new String(response.getBody(), "UTF-8");
                System.out.println(message);
                TimeUnit.MILLISECONDS.sleep(500);
            }
        }
    }
}

推模式

顧名思義,推模式情況下, MQ主導什麼情況下向消費者發送消息,以及每次發送多少消息。 而由於是MQ進行主導,在沒有任何控制的情況下,很有可能會對消費者產生壓力。可以通過設置prefatchCount(默認爲1)

顯而易見,使用推模式的話,必須謹慎的設置prefatchCount字段,不然要麼有可能會導致消費者消費能力不足導致被壓垮,要麼因爲prefatchCount字段過小導致系統處理消息的能力受限。

但是,使用拉模式的話,消費者可以根據自己的能力來決定消費速率,但是在消費遲緩的時候,很容易造成消息的堆積。並且,有可能會導致消息從生產到消費的時間過長,在處理存在有效期的消息時,會造成一定的影響。

消息的確認機制

生產側的消息確認

消息從生產者產生,交給RabbitMQ,但是由於網絡的不確定性,消息是有可能交付失敗的,所以RabbitMQ提供了兩種模式來保證生產者側的消息可靠性投遞。

事務模式

這裏的事務有三個核心方法:

channel.txSelect(); // 開啓事務

channel.txCommint(); // 事務提交

channel.txRollback(); //事務回滾
//示例
try {
    channel.txSelect();
    channel.basicPublish(exchange, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes());
    channel.txCommint();
} catch(Exception ex) {
    ex.printStackTrace();
    channel.txRollback();
}

 生產者confirm模式

 

生產者將信道設置成confirm模式,一旦信道設置成confirm模式,所有在該信道上面發佈的消息都會被指派一個唯一的ID(從1開始),一旦消息被投遞到所有匹配的隊列後,broker就會發送一個確認給生產者(包含消息的唯一ID),這就使得生產者知道消息已經正確到達目的隊列了。如果消息和隊列是持久化的,那麼確認消息會在消息寫入磁盤後再發出。

broker回傳給生產者的確認消息中deliver-tag域包含了確認消息的序列號。此外,broker也可以設置basic.ack的multiple域,表示到這個序列號之前的所有消息都已經得到了處理。

confirm模式最大的好處在於它可以是異步的,一旦發佈一條消息,生產者的應用程序就可以在等信道確認的同時繼續發送下一條消息,當消息最終得到確認之後,生產者應用便可以通過回調方法來處理該確認消息,如果RabbitMQ因爲自身內部錯誤導致消息丟失,就會發送一條nack消息,生產者應用程序同樣可以在回調方法中處理該nack消息。

同時,confirm模式和事務模式不允許同時存在。

confirm方式又分爲三種:單條confirm、批量confirm、異步confirm

單條confirm

又稱普通confirm模式。每發送一條消息,調用waitForConfirms()方法,等待MQ服務器端confirm。這實際上算是一種串行confirm了。

如果服務器端返回false或者超時未返回,生產者會對這條消息進行重傳。

代碼示例

channel.basicPublish(ConfirmConfig.exchangeName, confirmConfig.routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, ConfirmConfig.msg_10B.getBytes());
if (!channel.waitForConfirms()) {
    System.out.println("send message failed");
}

批量confirm

每發送一批消息後,調用waitForConfirms()方法,等待服務器端confirm。

批量confirm比單條confirm稍微複雜一些。生產者程序需要定期(每隔多少秒)或者定量(達到多少條)或者兩者結合起來publish消息,然後等待服務器端進行confirm。相比於單條confirm模式,批量confirm極大地提升了confirm效率。但是問題在於,一旦出現confirm返回false或者超時的情況,生產者需要將這一批次的消息全部重發,這會明顯會帶來消息重複。並且,在confirm消息經常丟失的情況下,批量confirm的性能應該是不升反降的。

channel.confrimSelect();
for (int i=0; i<batchCount; i++) {
    channel.basicPublish(ConfirmConfig.exchangeName, ConfirmConfig.routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, ConfirmConfig.msg_10B.getBytes());
    if (!channel.waitForConfirms()) {
        System.out.println("send message failed");
    }
}

 

異步confirm

提供一個回調方法,服務器端confirm了一條或者多條消息後client端會回調這個方法。

異步confirm模式的編程實現最爲複雜。Channel對象提供的ConfirmListener()回調方法只包含deliveryTag(當前Channel發出的消息序號),我們需要自己爲每一個Channel維護一個unconfirm的消息序號集合,每publish一條消息,集合中元素加1;每回調一次handleAck方法,unconfirm集合刪掉相應的一條(multiple = false)或多條(multiple = true)記錄。從程序運行效率上看,這個unconfirm集合最好採用有序集合SortedSet存儲結構。實際上,SDK中的waitForConfirms()方法也是通過SortedSet維護消息序號的。

關鍵代碼:

SortedSet<Long> confirmSet = Collections.SynchronizedSortedSet(new TreeSet<Long>());
channel.confirmSelect();
channel.addConfirmListener(new ConfirmListener(){
    public void handleAck(long deliveryTag, boolean multiple) throws IOException {
        if (multiple) {
            confirm.headSet(deliveryTag + 1).clear();
        } else {
            confirmSet.remove(deliveryTag);
        }
    }
    public void handleNack(long deliveryTag, boolean multiple) throws IOException {
        System.out.println("Nack, SeqNo: " + deliveryTag + ", multiple: " + multiple);
        if (multiple) {
            confirmSet.headSet(deliveryTag + 1).clear();
        } else {
            confirmSet.remove(deliveryTag);
        }
    }
});
while(true) {
    long nextSeqNo = channel.getNextPublishSeqNo();
    channel.basicPublish(ConfirmConfig.exchangeName, ConfirmConfig.routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, ConfirmConfig.msg_10B.getBytes());
    confirmSet.add(nextSeqNo);
}

事務模式 < 單條confirm << 批量confirm < 異步confirm

使用事務模式,性能是最差的。單條confirm模式性能會比事務稍微好一些。但是和批量confirm模式還有異步confirm模式相比,就差的遠了。批量confirm模式的問題在於confirm頻繁返回false進行大量消息重發時會使性能降低,異步confirm模式(async)編程模型較爲複雜。

消費者confirm模式

爲了保證消息成功到達消費者進行消費,RabbitMQ同樣對消費側存在消息的確認機制。

消費者在聲明隊列時,可以指定noAck參數,當noAck = false時,RabbitMQ會等待消費者顯式發回ack信號纔會從內存(和磁盤,如果是持久化消息的話)中移除消息。否則,RabbitMQ會在隊列中消息被消費後立即進行刪除。

  • AcknowledgeMode.NONE:無需確認。在消息發送到消費者時,就進行ack,也就認爲消費成功了。這種情況下,只有負責隊列的進程出現了異常,纔會進行nack,其他情況都是ack。
  • AcknowledgeMode.AUTO:根據情況ack。如果消費過程中沒有拋出異常,就認爲消費成功了,也就進行ack。這裏有幾個異常會存在特殊處理:AmqpRejectAndDontRequeueException,會認爲消息失敗,拒絕消息,並且requeue = false(也就是不再重新投遞);ImmediateAcknowledgeAmqpException,會進行ack;其他的異常均進行nack,同時requeue = true(也就是進行重新投遞)。
  • AcknowledgeMode.MANUAL:手動ack。上面兩種都是不需要關心ack方法調用的,但是使用手動ack時,必須要手動調用ack方法。不過,手動ack是支持批量處理的(可以設置是否進行批量ack),這樣可以減小IO消耗。不過它有着與批量confirm相同的問題。

消息持久化

持久化的作用就是,把指定的內容保存到磁盤上,這樣即使在MQ服務器崩潰的時候,重啓之後也能重新從磁盤中讀取出來,恢復之前的內容。

  1. exchange、queue、消息這三部分的持久化沒什麼關聯,誰聲明爲持久化,服務器重啓之後,就會對誰進行重新構建。不過,如果想恢復整個的流程,最好對這三者都進行持久化(當然,持久化是會效率有損耗的)。
  2. Queue和exchange都可以在創建的時候直接聲明爲持久化的(declare方法中的一個參數)。而消息是在發送的時候設置爲持久化的(basicPublish的一個參數)。
  3. 不需要爲Binding持久化,只要對exchange和queue設置爲持久化的,在恢復的時候,binding會自動恢復(其實可以理解爲binding本身只是一個虛擬概念,實際是依仗於exchange的模式以及queue的routing key設置才建立的)。
  4. RabbitMQ不會允許一個非持久化的exchange和一個持久化的queue進行綁定,反之亦然。想要成功綁定,exchange和queue必須都是持久化的,或者都是非持久化的。
  5. 一旦創建了exchange或者queue之後,是不允許進行修改的,要想修改只能刪除重建。所以,要將一個非持久化的隊列變成持久化的,唯一的辦法只能刪除這個隊列之後重新建立。
  6. 在不設置持久化的情況下,消息是會放在內存中的,不會落到磁盤上(所以重啓時會丟消息)。
  7. 如果生產者這一側開啓了事務模式,消息是一定會被持久化的。只有在消息刷到磁盤上,纔會告知成功。

持久化的問題

  1. 啓用持久化會損失性能,吞吐量甚至可能降低10倍

  2. RabbitMQ集羣中的隊列並沒有備份,而是均勻分佈在各個節點,如果隊列所在節點掛掉,在節點恢復重建隊列前消息會丟失。如果在持久化到磁盤前崩潰,消息仍然會丟失。

  3. 在生產者這一側,如果沒有采用事務模式,消息在進入RabbitMQ之後,並不是立刻落到磁盤上的,這裏有一個刷盤時機的限制。如果這時服務器宕機了,那麼這部分沒有刷盤的消息自然會被丟了。如果設置了事務,只有在消息刷到磁盤上,纔算是正常結束。RabbitMQ會有一個buffer,大小爲1M,生產者產生的數據進入MQ的時候,會先進入這個buffer。只有當buffer寫滿之後,纔會將buffer裏面的內容寫入文件中(這裏仍不一定立即寫入磁盤中)。
    此外,還有一個固定的刷盤週期:25ms,也就是不管buffer有沒有滿,每隔25ms,都會進行一次刷盤操作,將buffer裏面的內容與未刷入磁盤的文件內容落到磁盤上。

負載均衡

在MQ集羣中,往往不會只有一臺機器,這樣的話,每一個客戶端要具體連接到那一臺服務器就需要進行控制。舉個例子,假設集羣中三臺機器,但是所有的連接都打到了其中的一臺機器上,這樣的話,整個系統的性能和可靠性顯然是有問題的。

輪詢法

將請求按順序輪流地分配到後端服務器上,它均衡地對待後端的每一臺服務器,而不關心服務器實際的連接數和當前的系統負載。

代碼示例:

 

public class RoundRobin {
    private static List<String> list = new ArrayList<String>() {{
        add("192.168.0.2");
        add("192.168.0.3");
        add("192.168.0.4");
    }};
    private static int pos = 0;
    private static final Object lock = new Object();
    public static String getConnectionAddress() {
        String ip = null;
        synchronized(lock) {
            ip = list.get(pos);
            if (++pos >= list.size()) {
                pos = 0;
            }
        }
        return ip;
    }
}

隨機法

通過隨機算法,根據後端服務器的列表大小值來隨機選取其中的一臺服務器進行訪問。由概率統計理論可以得知,隨着客戶端調用服務端的次數增多,其實際效果越來越接近於平均分配(也就是輪詢分配)。

public class RandomAccess {
    private static List<String> list = new ArrayList<String>(){{
        add("192.168.0.2");
        add("192.168.0.3");
        add("192.168.0.4");
    }};
    public static String getConnectionAccess() {
        Random random = new Random();
        int pos = random.nextInt(list.size());
        return list.get(pos);
    }
}

源地址哈希法

源地址哈希的思想是根據獲取的客戶端ip地址,通過哈希函數計算到一個數值,用該數值對服務器列表的大小進行取模運算,得到的結果便是客戶端要訪問服務器的序號。採用源地址哈希法進行負載均衡,同一ip地址的客戶端,當後端服務器列表不變時,它每次都會映射到同一臺後端服務器進行訪問。 

 

public class IpHash {
    private static List<String> list = new ArrayList<String>(){{
        add("192.168.0.2");
        add("192.168.0.3");
        add("192.168.0.4");
    }};
    public static String getConnectionAccess() throws UnknownHostException {
        int ipHashCode = InetAddress.getLocalHost().getHostAddress().hashCode();
        int pos = ipHashCode % list.size();
        return list.get(pos);
    }
}

加權輪詢法

不同的服務器可能機器的配置和當前系統的負載並不相同,因此它們的抗壓能力也不相同。給配置高、負載低的機器配置更高的權重,讓其處理更多的請求;而配置低、負載高的機器,給其分配較低的權重,降低其系統負載。加權輪詢能夠很好地處理這一問題,並將請求順序且按照權重分配到後端。 

加權隨機法

與加權輪詢法一樣,加權隨機法也根據後端機器的配置、系統的負載分配不同的權重。不同的是,它是按照權重隨機請求後端服務器,而非順序。

最小連接數法

最小連接數算法比較靈活和智能,由於服務器的配置不盡相同,對於請求的處理有快有慢,它是根據服務器當前的連接情況,動態地選擇其中當前積壓連接數最少的一臺服務器來處理當前的請求,儘可能地提高服務器的利用效率,將負載合理地分流到每一臺服務器。

集羣

RabbitMQ是基於Erlang實現的,而Erlang天生是具有分佈式特性的(基於Erlang的magic cookie),所以對於RabbitMQ而言,實現集羣非常簡單,並不像其他的MQ那樣實現集羣需要zk等。

在集羣中,不同的節點會彼此同步四種類型的元數據:

  • 隊列元數據:隊列名稱和它的屬性
  • 交換器元數據:交換器名稱、類型和屬性
  • 綁定元數據:一張簡單的表格展示瞭如何將消息路由到queue
  • vhost元數據:爲vhost內的隊列、交換器和綁定提供命名空間和安全屬性。

在RabbitMQ集羣中,會有多個節點,這些節點有內存節點和磁盤節點之分。顧名思義,內存節點,就是上面所說的同步的內容都放到了內存中,而磁盤節點就是這些信息都會放到磁盤中。當然,如果將exchange和queue設置爲持久化的,即使是在內存節點上,這些信息也是會持久化到磁盤上的。

  1. 如果只有一個節點,則只能設置爲磁盤節點。
  2. RabbitMQ集羣中至少要有一個磁盤節點,所有其他的節點都可以是內存節點。在這種情況下,所有元數據有改動時,必須通知到磁盤節點,由其進行落盤處理。一旦磁盤節點掛掉,MQ可以正常運轉,但是不會允許進行元數據的增刪改查。
    1. 解決這種問題的一個思路是,使用雙磁盤節點,那麼只會在兩個節點都掛掉的時候,纔會出現這個問題,而出現這種情況的機率就比較低了。
    2. RabbitMQ節點啓動時不指定的話,默認是磁盤節點。
    3. 其實RabbitMQ文檔表示磁盤節點比內存節點性能差不了多少,建議都是用磁盤節點。

默認模式

下圖展示了默認模式下的各節點示意圖。各個節點之間只會複製隊列元數據,不會同步隊列的消息內容。

默認的集羣模式下,對於exchenge的拷貝,是鏡像拷貝,每個節點的內容完全一致,所有節點共享相同的exchange信息。此外,queue的元數據也會在各個節點之間進行同步,但是隊列內的數據不會參與同步。

MQ在從生產者那裏拿到消息之後,消息是會先進入exchange的,而exchange會立即將消息路由到queue裏面去,如果消費者消費能力不足,消息就會堆積在queue裏面。換句話說,MQ的消息緩存能力主要由queue來實現。而這裏的默認模式,每個節點之間會同步所有的exchange信息,並且會複製隊列元數據,所以每一個節點都知道exchange和queue的所有屬性,因此,消息無論從哪個節點進入exchange,這個節點都會知道這個消息應該路由到哪一個queue裏面去。但是,一個consumer往往不會與所有的節點建立連接,這樣的話,未建立連接的節點需要將queue裏面的消息轉發到那些建立了連接的節點以供消費。 

特點:

  • 每個節點不用關心所有的消息內容,因此可以有更大的空間來緩存消息(因爲消息只會存一份)。
  • 缺點是,如果一個節點掛掉,這個節點上所有未消費的消息都無法從其他節點獲取,一旦消息沒有做持久化,這些消息都會丟失。如果做了持久化,也只能等這個節點重啓之後,才能再進行消費。 

鏡像模式

鏡像模式與默認模式的一個區別就是隊列內消息的處理方式。在鏡像模式下,每一個消息會在每一個節點主動進行拷貝,每個節點存儲一份(默認模式下,只有當consumer需要消費數據時,纔會對消息進行拷貝)。如下圖所示:

樣,我們可以看出,對於鏡像模式而言,當某一個節點掛掉的時候,只要將鏈接切到另外一個節點下,就可以繼續進行消息的正常消費,可靠性大爲提升。但是,因爲MQ內部會存在大量的消息路由,所以MQ的整體性能會受到影響(內部拷貝會佔用大量的內部網絡帶寬),同時因爲每一個節點都會保存所有的消息,所以對消息的緩存能力會有一定的影響(主要是跟默認模式進行比較)。

特殊queue/exchange

排他隊列

如果一個隊列聲明爲了排他隊列,那麼該隊列僅對首次聲明它的連接可見,並且在斷開連接時自動進行刪除。

  • 排他隊列是基於連接可見的,同一個連接的不同channel是可以訪問其他channel創建的排他隊列的
  • 如果一個連接建立了排他隊列,那麼是不允許其他連接建立同名的排他隊列的
  • 即使這個隊列設置了持久化,一旦關閉連接(比如客戶端退出),這個隊列也會自動刪除。所以,一旦機器宕機,這個隊列是無法恢復的。同樣,排他隊列適用於一個客戶端進行消息的發送和讀取的應用場景。

 

臨時隊列

隊列可以設置一個自動刪除屬性,如果隊列沒有任何訂閱消費者時,會自動刪除的。

優先級隊列

RabbitMQ本身並沒有實現優先級隊列,不過有插件可以進行支持。

Dead Letter Exchange

可以對一個queue設置一個Dead Letter Exchange屬性,當消息超時或者消息被nack/reject並且設置requeue=false時,會進入這個隊列。

因爲RabbitMQ沒有延時隊列和死信隊列的設置,所以需要藉助於Dead Letter Exchange來實現。RabbitMQ可以爲queue或者消息添加一個超時屬性,當超時未消費的話,會進行判斷,如果有設置Dead Letter Exchange,會進入這個exchange裏面,如果沒有設置,會直接被丟棄掉。

Alternate Exchanges

生產者生產出來的消息,進入exchange之後,找不到合適的queue時,會落到這個隊列中。

參考文檔:

http://www.diggerplus.org/archives/3110 

https://blog.csdn.net/anzhsoft/article/details/19563091

https://blog.csdn.net/u013256816/article/details/77131753

http://www.rabbitmq.com/documentation.html

http://chyufly.github.io/blog/2016/04/10/rabbitmq-cluster/

https://www.cnblogs.com/sellsa/p/8056173.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章