RabbitMQ實戰 RabbitMQ概念 RabbitMQ進階 RabbitMQ運維 跨越集羣限制 RabbitMQ高階 RabbitMQ擴展

RabbitMQ是採用Erlang語言實現AMQP(Advanced Message Queuing Protocol,高級消息隊列協議)。

RabbitMQ的具體特定包括下面幾點:

  • 可靠性:有持久化、傳輸確認以及發佈確認等機制保證可靠性

  • 靈活的路由:在消息進入隊列之前,通過交換器來路由消息。典型的路由有一些內置交換器實現,複製的路由可能將多個交換器綁定在一起,也可以通過插件機制來實現自己的交換器。

  • 擴展性:多個RabbitMQ節點可以組成一個集羣,也可以根據實際業務情況動態擴展集羣節點

  • 高可用:隊列可以在集羣中的機器上設置鏡像,部分節點出現問題時隊列仍然可用

  • 多種協議:除了支持AMQP,還支持STOMP(Simple (or Streaming) Text Oriented Messaging Protocol,簡單(流)文本面向消息協議),MQTT(Message Queue Telemetry Transport ,IBM開發的即時通信協議)等多種消息中間件協議。

  • 多語言客戶端:支持Java、Python、Ruby、PHP等

  • 管理界面:提供了一個易用的用戶界面,可以監控和管理消息、集羣中的節點等。

  • 插件機制:提供了許多插件,實現多方面擴展

RabbitMQ概念

生產者(Producer)創建消息發佈到RabbitMQ,消息一般包含兩個部分:消息體(payload)和標籤(Label)。消息的標籤用來表述這條消息,比如一個交換器名稱和一個路由鍵。生產者把消息交由RabbitMQ之後會根據標籤把消息發送給感興趣的消費者(Consumer)。

消費者(Consumer)連接到RabbitMQ服務器並訂閱隊列。消費消息的消息體(payload)。在消息路由過程中,消息的標籤會丟棄,存入到隊列中的消息只有消息體。

Broker:消息中間件的服務節點。

對於RabbitMQ來說,一個RabbitMQ Broker可以簡單看作一個RabbitMQ服務節點或者RabbitMQ服務實例。

生產者將業務數據包裝成消息,發送(AMQP協議中這個動作對應命令爲Basic.Publish)到Broker中。消費者訂閱並接收消息(AMQP協議中這個動作對應的命令爲Basic.Consume 或Basic.Get),經過可能的解包處理得到原始的數據再進行業務處理邏輯。業務處理邏輯可以使用Java的BlockingQueue進一步解耦提高處理效率。

隊列(Queue):RabbitMQ內部對象用於存儲消息。RabbitMQ中消息都只能存儲在隊列中,這一點和Kafka相反,Kafka將消息存儲在topic(主題)這個邏輯層面,而相對應的隊列邏輯只是topic實際存儲文件中的位移標識。多個消費者可以訂閱同一個隊列,消息會被平均分攤(Round-Robin)。RabbitMQ不支持隊列層面的廣播消費。

交換器(Exchange):生產者將消息發送到Exchange,由交換器將消息路由到一個或者多個隊列中,如果路由不到或許返回給生產者,或許直接丟棄。RabbitMQ的交換器有四種類型,不同類型有不同的路由策略。

路由鍵(RoutingKey):生產者將消息發給交換器的時候,會指定一個RoutingKey,用來指定這個消息的路由規則,這個RoutingKey需要與交換器類型和綁定鍵(BindingKey)聯合使用才能最終生效。

綁定(Binding):RabbitMQ中通過綁定講交換器與隊列關聯起來,綁定時一般會指定給一個綁定鍵(BindingKey),這樣RabbitMQ就知道如何正確地將消息路由到隊列。生產者將消息發送給交換器,需要一個RoutingKey,當BindingKey和RoutingKey相匹配時,消息會被路由到對應隊列。

交換器類型

RabbitMQ常用的交換器由fanout、direct、topic、headers這四種。AMQP協議裏還提到另外兩種類型:System和自定義。

headers:不依賴於路由鍵的匹配規則來路由消息,而是根據發送消息內容中的headers屬性進行匹配。

fanout:會把所有發送到該交換器的消息路由到所有與該交換器綁定的隊列中。

direct:會把消息路由到哪些BindingKey和RoutingKey完全匹配的隊列中。

topic:與direct相似,是將消息路由到BindingKey和RoutingKey相匹配的隊列中,但是這裏匹配規則有些不同:

  • RountingKey爲一個點號 “.” 分隔的字符串,如“com.rabbitmq.client”

  • BindingKey 和 RoutingKey 一樣也是點號“.”分隔的字符串

  • BindingKey中可以存在兩種特殊字符“”和“#”,用於模糊匹配。用於匹配一個單詞,#用於匹配多個單詞(可以是零個)

運轉流程

生產者發送消息:

1. 生產者連接到RabbitMQ Broker,建立一個連接,開啓一個信道(Channel)

2. 生產者聲明一個交換器,設置相關屬性,比如交換機類型、是否持久化等

3. 生產者聲明一個隊列並設置相關屬性,比如是否排他,是否持久化,是否自動刪除等

4. 生產者通過路由鍵將交換器和隊列綁定起來

5. 生產者發送消息至RabbitMQ Broker,其中包含路由鍵、交換器等信息

6. 相應的交換器根據收到的路由鍵查找相匹配的隊列

7. 如果找到,則將從生產者發送過來的消息存入相應的隊列中。

8. 如果沒找到,根據生產者配置的屬性選擇丟棄還是回退給生產者

9. 關閉信道

10. 關閉連接

消費者接受消息:

1. 消費者連接到RabbitMQ Broker,建立一個連接(Connection),開啓一個信道(Channel)

2. 消費者向RabbitMQ Broker請求消費相應隊列中的消息,可能會設置相應的回調函數,以及做一些準備工作

3. 等待RabbitMQ Broker迴應並投遞相應隊列中的消息,消費者接收消息

4. 消費者確認(ack)接收到的消息

5. RabbitMQ從隊列中刪除相應已經被確認的消息。

6. 關閉信道

7. 關閉連接

生產者和消費者都需要和RabbitMQ Broker建立連接,這個連接就是一條TCP連接,也就是Connection。建立之後可以創建一個AMQP信道(Channel),每個信道都會被指派一個唯一的ID,信道時建立在Connection之上的虛擬連接,RabbitMQ處理的每條AMQP指令都是通過信道完成的。RabbitMQ採用了類似NIO的做法,TCP連接複用減少開銷。

AMQP協議

本身包含三層:

  • Module Layer:位於協議最高層,主要定義了一些供客戶端調用的命令,客戶端可以利用這些命令實現自己的業務邏輯。例如客戶端可以使用Queue.Declare命令聲明一個隊列或者使用Basic.Consume訂閱消費一個隊列中的消息。

  • Session Layer:位於中間層,主要負責將客戶端命令發送給服務器,再將服務器的應答返回給客戶端,主要爲客戶端與服務器之間的通信提供可靠性同步機制和錯誤處理。

  • Transport Layer:位於最底層,主要傳輸二進制數據流,提供幀的處理、信道複用、錯誤檢測和數據表示等。

AMQP說到底還是一個通信協議,通信協議都會涉及報文交互,從low-level來說,AMQP是應用層協議,其填充於TCP協議層的數據部分。從high-level來說,AMQP是通過協議命令進行交互的。AMQP協議可以看作一系列結構化命令的集合,這裏的命令代表一種操作。

生產消息

ConnectionFactory創建Connection

Connection創建Channel,Channel聲明交換器(channel.exchangeDeclare)和隊列(channel.queueDeclare)

exchangeDeclare方法

參數說明:

  • exchange:交換器名稱

  • type:交換器類型,fanout、direct、topic

  • durable:設置是否持久化,true時持久化

  • autoDelete:設置是否自動刪除。與這個交換器綁定的隊列或者交換器都與此解綁。

  • internal:設置是否內置交換器,true是內置交換器客戶端程序無法直接發送消息到這個交換器,只能通過交換器路由到交換器這種方式

  • argument:其他一些結構化參數,如atlernate-exchange

queueDeclare方法

不帶參數的queueDeclare方法默認創建一個由RabbitMQ命名的(類似amq.gen-Lhfjakjhdfawef2ij的匿名隊列)、排他的、自動刪除的、非持久化的隊列。

參數說明:

  • queue:隊列名稱

  • durable:設置是否持久化

  • exclusive:設置是否排他。true爲排他,則該隊列僅對首次聲明它的連接可見,並在斷開連接時自動刪除。注意三點:排他隊列基於連接,不同信道是可以同時訪問的。首次是指連接若已經聲明排他隊列其他連接不允許創建同名排他隊列。即使該隊列持久化,一旦連接關閉或者客戶端退出都會自動刪除。

  • autoDelete:自動刪除

  • argument:設置隊列其他參數,如:x-message-ttl、x-expires

queueBind方法

將隊列和交換器綁定的方法

參數說明:

  • queue:隊列名稱

  • exchange:交換器名稱

  • routingKey:用來綁定隊列和交換器的路由鍵

  • argument:頂一個綁定的一些參數

exchangeBind方法

將交換器和隊列綁定的方法

發送消息 basicPublish 方法


channel.basicPublish(

    exchangeName,

    routingKey,

    new AMQP.BasicProperties.Builder()

        .contentType("text/plain")

        .deliveryMode(2)

        .priority(1)

        .userId("hidden")

        .header(new HashMap<String, Object>())  

        .expiration("60000")  

        .build() ,

    messageBodyBytes

);

消費消息

RabbitMQ的消費模式分爲兩種,推(Push)和拉(Pull)。推模式採用Basic.Consume,拉模式調用Basic.Get進行消費

推模式

推模式接收消息一般通過實現Consumer接口或者繼承DefaultConsumer類來實現。當調用與Consumer相關API方法時,不同的訂閱採用不同的消費者標籤(consumerTag)來區分彼此,在同一個Channel中的消費者也需要通過唯一的消費者標籤以作區分。

basicConsume方法參數如下:

  • queue:隊列名稱

  • autoAck:設置是否自動確認,建議false

  • consumerTag: 消費者標籤,區分多個消費者

  • noLocal:設置true表示不能將同一個Connection中生產者發送的消息發送給這個Connection中的消費者

  • exclusive:是否排他

  • arguments:消費者其他參數

  • callback:消費者的回調函數,用來處理RabbitMQ推送過來的消息。比如DefaultConsumer,使用時需要客戶端重寫(override)其中的方法。

拉模式

通過channel.basicGet方法可以單條獲取消息,其返回值時GetReponse。


GetResponse response = channel.basicGet(QUEUE_NAME, false);

System.out.println(new String(reponse.getBody()));

channel.basicAck(reponse.getEnvelop().getDeliveryTag(),false);

爲了保證消息可靠到達消費者,RabbitMQ提供了消息確認機制(message acknowledge)。消費者在訂閱隊列時可以指定autoAck參數,表示是否需要顯示應答纔會刪除消息。未回覆確認時,RabbitMQ會一直等待,除非消費該消息的消費者連接已經斷開。消費者頁可以調用channel.basicReject方法拒絕單個消息。使用channel.basicNack方法批量拒絕消息。

RabbitMQ進階

mandatory 和 immediate 是 channel.basicPublish 方法中的兩個參數,它們都有當消息傳遞過程中不可達目的地時將消息返回給生產者的功能。

mandatory參數爲true,交換機無法根據自身類型和路由鍵找到一個符合條件的隊列會返回給生產者,如果爲false,則消息被丟棄。

immediate參數爲true,如果交換器將消息路由到隊列時發現隊列上不存在消費者,則這條消息不會存入隊列中。所有匹配的隊列都沒有消費者時,該消息返回生產者。3.0去除

備份交換器(alternate exchange),生產者在發送消息的時候如果不設置mandatory參數,那麼消息在未被路由的情況下會丟失,如果設置了mandatory,需要添加ReturnListener邏輯。但是可以使用備份交換器將未被路由的消息存儲在RabbitMQ中,需要時再去處理。


Map<String,Object> args = new HashMap<String,Object>();

args.put("alternate-exchange","myAe");

args.put("x-message-ttl",6000); // 設置過期時間

channel.exchangeDeclare("normalExchange","direct",true,false,args);

channel.exchangeDeclare("myAe","fanout",true,false,null);

channel.queueDeclare("normalQueue",true,false,false,null);

channel.queueBind("normalQueue","normalExchange","normalKey");

channel.queueDeclare("unroutedQueue",true,false,false,null);
channel.queueBind("unroutedQueue","myAe","");

RabbitMQ可以對消息和隊列設置TTL(Time To Live)過期時間。

目前有兩種方法設置消息的TTL,第一種方法通過隊列屬性設置,隊列中所有的消息都有相同的過期時間。第二種方法時對消息本身進行單獨設置,每條消息的TTL可以不同。如果兩種方法一起使用,則消息的TTL以兩者較小的那個數值爲準。消息在隊列中生存時間一旦超過設置的TTL值,就會變成死信(Dead Message)。

對於設置隊列TTL屬性的方法,一旦消息過期,就會從隊列中抹去,而在第二種方法中,即使消息過期,也不會馬上從隊列中抹去,因爲每條消息是否過期時在即將投遞到消費者之前判定的。

第一種方法中,隊列中已經過期的消息肯定在隊列的頭部,RabbitMQ只要定期從隊頭開始掃描是否有過期的消息即可。第二種方法裏,每條消息的過期時間不同,如果要刪除所有過期消息必須要掃描整個隊列,所以等到消息被消費的時候再判定是否過期在進行刪除。

死信隊列(DLX),全稱是Dead-Letter-Exchange。

消息變成死信有下面幾個原因:

  • 消息被拒絕(Basic.Reject/Basic.Nack),並設置requeue參數爲false;

  • 消息過期

  • 隊列達到最大長度


channel.exchangeDeclare("dlx_exchange","direct");

Map<String,Object> args = new HashMap<String,Object>();

args.put("x-dead-letter-exchange","dlx_exchange");

// 可以爲DLX指定路由鍵,如果沒有特殊指定,則使用原隊列的路由鍵

args.put("x-dead-letter-routing-key","dlx-routing-key");

channel.queueDeclare("myqueue",false,false,false,args);

通過設定TTL和DLX模擬出延遲隊列。消費者訂閱的是死信隊列而不是正常隊列。

可以通過設置隊列的x-max-priority參數來實現優先級隊列,優先級高的消息具備優先被消費的特權。

持久化

持久化可以提高RabbitMQ的可靠性,以防在異常情況(重啓,關閉,宕機等)下的數據丟失。持久化分爲三個部分:交換器的持久化、隊列的持久化、消息的持久化。可以引入鏡像隊列機制。

生產者確認

消息生產者消息發送之後,默認時不會返回任何信息給生產者。爲了解決這個問題,提供了兩種解決方式:

  • 通過事務機制實現

  • 通過發送方確認(publisher confirm)機制實現

RabbitMQ客戶端中與事務機制相關的方法有三個:channel.txSelect(設置爲事務模式)、channel.txCommit(提交事務)和channel.txRollback(回滾事務)。事務會很大影響RabbitMQ的消息吞吐量。

輕量級的方式:發送方確認機制。生產者將信道設置成confirm(確認)模式,一旦信道進入confirm模式,所有該信道上面發佈的消息都會被指派一個唯一的ID(從1開始),一旦消息被投遞到所有匹配隊列之後,RabbitMQ就發送一個確認(Basic.Ack)給生產者(包含消息的唯一ID),這就使得生產者知曉消息已經正確的到達了目的地。如果消息和隊列是可持久化的,那麼確認消息就會在消息寫入磁盤後發出。RabbitMQ回傳給生產者的確認消息中的deliveryTag包含了待確認消息的序號,此外RabbitMQ也可以設置channel.basicAck方法中的multiple參數,表示這個序號之前的所有消息都已經得到了處理。

事務機制是同步的,會阻塞發送端。發送方確認機制時異步的,可以通過回調方法來處理該確認消息。建議使用異步confirm模式,在Channel接口中提供的addConfirmListener方法可以添加ConfirmListener這個回調接口,包含兩個方法:handleAck和handleNack。我們要爲每個信道維護一個“unconfirm”的消息序號集合,每發送一條,集合中元素加1,回調時如果multiple爲false就一條,爲true時多條。

消費端要點

消費端有幾點要注意:

  • 消息分發

  • 消息順序性

  • 啓用QueueingConsumer

消息分發默認時輪詢(round-robin),可以加入流量控制,使用channel.basicQos(int prefetchCount),允許限制信道上的消費者所能保持的最大未確認消息數量。Basic.Qos的使用對於拉模式的消費方式無效。

消息順序性是指消費者消費到的消息順序和生產者發佈的順序是一致的。沒有高級特性的情況下,默認能夠保持順序性。

棄用QueueingConsumer,建議使用繼承DefaultConsumer的方式。

消息傳輸保障

消息中間件的消息傳輸保障分爲三個層級:

  • At most once:最多一次。消息可能會丟失,但絕不會重複傳輸(RabbitMQ支持)

  • At least once:最少一次,消息絕不會丟失,但可能會重複傳輸(RabbitMQ支持)

  • Exactly once:恰好一次,消息肯定會被傳輸一次且僅傳輸一次

RabbitMQ運維

集羣搭建

RabbitMQ集羣中的所有節點都會備份所有的元數據信息,包括:

  • 隊列元數據:隊列的名稱以及屬性

  • 交換器:交換器的名稱以及屬性

  • 綁定關係元數據:交換器與隊列或者交換器與交換器之間的綁定關係

  • vhost元數據:爲vhost內的隊列、交換器和綁定命名空間及安全屬性

在RabbitMQ集羣中創建隊列,集羣只會在單個節點而不是在所有節點上創建隊列的進程幷包含完整的隊列(元數據、狀態、內容)。這樣只有隊列的宿主節點(所有者)節點知道隊列的所有信息,所有其他非所有者只知道隊列的元數據和指向該隊列存在的那個節點的指針。節點崩潰時,該節點的隊列進程和關聯的綁定都會消失,附加在那些隊列上的消費者也會丟失其所訂閱的信息,並且任何匹配該隊列綁定信息的新消息也會丟失。

不同於隊列那樣有自己的進程,交換器實際上只是一個名稱和綁定列表。當消息發佈到交換器時,實際上是由所連接的信道將消息上的路由鍵同交換器的綁定列表進行比較,然後再路由消息。當創建一個新的交換器時,RabbitMQ所要做的就是將綁定列表添加到集羣中的所有節點上。這樣每個節點上的每個信道都可以訪問到新的交換器。

多機多節點是指每臺物理機器都安裝了RabbitMQ,應當只在局域網內使用,廣域網應當使用Federation或者Shovel。

命令行主要使用 rabbitmqctl join_cluster {nodename} 加入集羣節點

rabbitmqctl forget_cluester_node {nodename}

RabbitMQ要求集羣中至少有一個磁盤節點,其他節點可以是內存節點。當節點加入或者離開集羣的時候,它們必須將變更通知到至少一個磁盤節點。如果唯一一個磁盤節點崩潰,集羣可以繼續收發消息,但是不能執行創建隊列、交換器、綁定關係、用戶,以及更改權限、添加和刪除集羣節點的操作。所以集羣應該保障有兩個或者多個磁盤節點的存在。

/usr/lib64/erlang/erts-8.0.3/bin/beam.smp -W w -A 64 -P 1048576 -t 5000000 -stbt db -zdbbl 32000 -K true -B i -- -root /usr/lib64/erlang -progname erl -- -home /root -- -pa /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.6/ebin -noshell -noinput -s rabbit boot -sname rabbit@localhost -boot start_sasl -kernel inet_default_connect_options [{nodelay,true}] -sasl errlog_type error -sasl sasl_error_logger false -rabbit error_logger {file,"/var/log/rabbitmq/[email protected]"} -rabbit sasl_error_logger {file,"/var/log/rabbitmq/[email protected]"} -rabbit enabled_plugins_file "/etc/rabbitmq/enabled_plugins" -rabbit plugins_dir "/usr/lib/rabbitmq/lib/rabbitmq_server-3.6.6/plugins" -rabbit plugins_expand_dir "/var/lib/rabbitmq/mnesia/rabbit@localhost-plugins-expand" -os_mon start_cpu_sup false -os_mon start_disksup false -os_mon start_memsup false -mnesia dir "/var/lib/rabbitmq/mnesia/rabbit@localhost" -kernel inet_dist_listen_min 25672 -kernel inet_dist_listen_max 25672 status

RabbitMQ的日誌默認存放在 $RABBITMQ_HOME/var/log/rabbitmq文件夾內。一般會創建兩個日誌文件:RABBITMQ_NODENAME-sasl.log 和 RABBITMQ_NODENAME.log兩個日誌文件。SASL(System Application Support Libraries,系統應用程序支持庫)是庫的集合,RabbitMQ在記錄Erlang相關信息會寫入這個文件,例如可以找到Erlang的崩潰報告。後者就會有RabbitMQ的應用服務的日誌。

集羣遷移

集羣故障例如IDC整體停電、網線被挖斷等,這時候需要通過集羣遷移重新建立一個新的集羣。

RabbitMQ集羣遷移包括元數據重建、數據遷移、以及與客戶端連接的切換。

元數據重建是指新的集羣中創建原集羣的隊列、交換器、綁定關係、vhost、用戶、權限和Parameter等數據信息。元數據重建之後纔可以將原集羣中的消息以及客戶端連接遷移過來。

可以在原集羣管理界面上點擊 “Download broker definitions” 下載集羣元數據信息文件

集羣監控

RabbitMQ Management插件提供了管理界面,也提供了HTTP API接口來調用提供監控數據。

跨越集羣限制

Federation(聯邦交換器)

Federation插件可以讓多個交換器或者多個隊列進行聯邦,一個聯邦交換器(federated exchange)或者一個聯邦隊列(federated queue)接收上游(upstream)的消息,這裏的上游時指位於其他Broker上的交換器或者隊列聯邦交換器能夠將原本發送給上游交換器(upstream exchange)的消息路由到本地的某個隊列中;聯邦隊列則允許一個本地消費者接收到來自上游隊列(upstream queue)的消息。

Shovel

與Federation具備的數據轉發功能類似,Shovel能夠可靠、持續的從一個Broker的隊列拉取數據並轉發只另一個Broker的交換器。實際上是基於AMQP協議的轉發器。

Shovel可以部署在源端也可以部署在目的端。有兩種方式可以部署Shovel:靜態方式(static)和動態方式(dynamic)。靜態方式是指在RabbitMQ.config配置文件中設置,動態方式只指通過Runtime Parameter設置。

當集羣消息堆積嚴重時,可以通過Shovel將隊列中的消息移交給另一個集羣,這是一備一的情況。如果需要一備多,可以採用鏡像隊列或者引入Federation。

Shovel工作在Federation的更低一層。監獄Federation從一個交換器中轉發消息到另一個交換器(如果有必要可以確認消息是否被轉發),Shovel只是簡單地從某個Broker上的隊列消費消息,然後轉發消息到另一個Broker上的交換器而已。Shovel也可以再一臺單獨的服務器上去轉發消息,例如將一個隊列中的數據移動到另一個隊列中。

RabbitMQ高階

存儲機制

持久化的消息在到達消息隊列時就被寫入到磁盤,如果可以,在內存中也會保留一份備份。非持久化消息在內存中,內存緊張是會被換入磁盤。這些都是依靠持久層完成。

持久層時一個邏輯上的概念,包含兩部分:隊列索引(rabbit_queue_index)和消息存儲(rabbit_msg_store)。前者負責維護隊列中落盤消息的信息,包括消息的存儲地點,是否已經被交付給消費者,是否已經被消費者ack等。每個隊列都有這個信息。後者則以鍵值對的形式存儲消息,它被所有隊列共享,在每個節點中有且只有一個。技術層面上來說,rabbit_msg_store具體還分爲msg_store_persistent(負責持久化消息,重啓後消息不丟失)和msg_store_transient(負責非持久化消息,重啓後消息丟失)。默認在 /var/lib/mnesia/rabbit@hostname 路徑下包含 queues、msg_store_persistent、msg_store_transient三個文件夾下,建議較小的消息存儲在rabbit_queue_index中,而較大的消息存儲在rabbit_msg_store中。消息大小界定可以通過queue_index_embed_msgs_below來配置,默認大小爲4096單位B。

通常隊列由rabbit_amqqueue_process和backing_queue兩部分組成,前者負責協議相關的消息處理,即接收生產者發佈的消息、向消費者交付消息、處理消息的確認(包括生產端的confirm和消費端的ack)等。後者時消息存儲的具體形式和引擎,並向rabbit_amqqueue_process提供相關的接口以供調用。

如果消息投遞的目的隊列是空的,並且有消費者訂閱了這個隊列,那麼消息不會經過隊列直接發送給消費者。如果無法直接投遞暫存入隊列。消息入隊後會隨着系統負載在隊列中不斷流動,狀態也不斷變化,可能處於4種狀態:

  • alpha:消息內容(消息體、屬性和headers)和消息索引都存儲在內存中

  • beta:消息內容保存在磁盤中,消息索引保存在內存中

  • gamma:消息內容保存在磁盤中,消息索引保存在磁盤和內存中

  • delta:消息內容和索引都在磁盤上

普通的沒有設置優先級和鏡像的隊列,backing_queue的默認實現時rabbit_variable_queue,其內部通過5個子隊列Q1、Q2、Delta、Q3、Q4來體現消息的各個狀態。Q1,Q4只包含alpha狀態的消息,Q2,Q3包含beta和gamma狀態的消息,Delta只包含delta狀態的消息。一般情況下,消息按照Q1→Q2→Delta→Q3→Q4這樣順序步驟進行流動。

消費者消息首先會對Q4獲取消息,如果Q4爲空嘗試從Q3獲取,如果Q3爲空說明隊列爲空。

RabbitMQ從3.6.0開始引入惰性隊列(Lazy Queue)。惰性隊列儘可能地將消息存入磁盤中,在消費者消費相應消息會被加載到內存,這是爲了支持更長的隊列。default爲默認內存模式,lazy爲惰性隊列模式:


args.put("x-queue-mode","lazy");

流控

RabbitMQ可以對內存和磁盤使用量設置閾值,到達閾值時生產者會被阻塞直到對應項恢復正常。2.8.0還引入了流控(Flow Control)機制確保穩定性,爲了避免消息發送速率過快而導致服務器難以支撐的情形。

一個連接(Connection)觸發流控時會處於“flow”的狀態,也就意味着這個Connection的狀態每秒在blocked和unblocked之間來回切換數次,將消息發送的速率控制在服務器能夠支持的範圍之內。

鏡像隊列

引入鏡像隊列(Mirror Queue)的機制,可以將隊列鏡像到集羣中的其他Broker節點之上,如果集羣中的一個節點失效了,隊列能自動地切換到鏡像的另一個節點上以保證服務的可用性。每一個配置鏡像的隊列都包含一個主節點(master)和若干個從節點(slave),如果master失效,slave加入時間最長的會提升爲master。發送到鏡像隊列的所有消息會被同時發往master和其他所有的slave。除了發送消息(Basic.Publish)外所有動作都只會想master發送,然後由master將命令執行的結果廣播給各個slave。

消費者與slave建立連接消費時實質上都是從master上獲取消息,只不過看似從slave上消費而已。例如消費者與slave建立了TCP連接後執行Basic.Get操作,由slave轉發給master,再由master準備好數據返回給slave,投遞給消費者。這裏的master和slave針對隊列而言,隊列可以均勻地散落在集羣的各個Broker節點中以達到負載均衡地目的,真正的負載還是針對實際的物理機器而言,而不是內存中駐留的隊列進程。

網絡分區

網絡分區的恢復

首先選一個最信任的partition,Mnesia使用該partition中的狀態,其他partitions中發生的變化都將丟失。

停止其他partitions中的所有nodes,之後重啓這些nodes。當這些nodes重新加入cluster後將從信任的partition恢復狀態。

最後還需重啓信任的partition中的所有nodes以清除network partition的警告信息

Rabbitmq自動處理網絡分區的3種模式

RabbitMQ提供了3種自動處理network partitions的方式:默認爲ignore模式,也即需要手工處理

pause-minority mode:暫停少數模式;

pause-if-all-down mode:暫停-如果全部停止模式

autoheal mode:自動癒合模式

pause-minority mode:暫停少數模式

在pause-minority模式下,察覺其他nodes down掉後,RabbitMQ將自動暫停認爲自己是少數派的 nodes(例如小於或等於總nodes數的一半),network partition一旦發生,“少數派”的nodes將立刻暫停,直至partition結束後重新恢復。這可以保證在network partition發生時,至多隻有一個partition中的nodes繼續運行。(犧牲可用性保證一致性)

若所有分區的nodes個數都小於總nodes個數一半,則意味着所有分區的nodes都會認爲自己是少數派,即所有nodes都將暫停

pause-if-all-down mode:暫停-如果全部停止模式

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

autoheal模式

在autoheal模式下一旦發生了partition,RabbitMQ將自動確定一個優勝partition,然後重啓所有不在優勝partition中的nodes。

獲勝的partition爲擁有最多客戶端連接的partition(若連接相同則爲節點最多的partition)。

關於自動處理partitions的設置在配置文件的cluster_partition_handling參數中進行。

各自的適用場景

network partitions自動處理並不能保證cluster不出任何問題。

一般來說可作如下選擇:

ignore:若網絡非常可靠。所有nodes在同一機架,通過交換機連接,該交換機也是通往外部網絡的出口。在cluster的某一部分故障時不希望其餘部分受影響。或者cluster只有兩個node。

pause_minority:網絡較不可靠。cluster處於EC2的3個AZ中,假定每次至多隻有其中一個AZ故障,想要剩餘的AZ繼續提供服務而故障的AZ中的nodes在AZ恢復後重新自動加入到cluster。

autoheal:網絡很不可靠。與數據完整性相比更關注服務的持續性。cluster只有兩個node。

RabbitMQ擴展

消息追蹤

RabbitMQ可以使用Firehose來實現消息追蹤,Firehose的原理是將生產者投遞給RabbitMQ的消息或者RabbitMQ投遞給消費者的消息按照指定的格式發送到默認的交換器上,這個默認的交換器名稱爲:amp.rabbitmq.trace,它是一個topic類型的交換器。發送到這個交換器上的消息的路由鍵爲publish.{exchangename}和deliver.{queuename},其中exchange和queuename爲交換器和隊列的名稱,分別對應生產者投遞到交換器的消息和消費者從隊列中獲取的消息。

使用rabbitmqctl trace_on [-p vhost]開啓Firehose命令

rabbitmq_tracing插件相當於Firehose的GUI版本,會對流入流出的消息進行封裝,然後將封裝後的消息日誌存入相應的trace文件中。

使用rabbitmq-plugins enable rabbitmq_tracing命令來啓動插件。

負載均衡

除了應用內部實現負載均衡外,還可以使用HAProxy、Keepalived、LVS。

HAProxy提供高可用、負載均衡以及基於TCP和HTTP應用的代理,支持虛擬主機。HAProxy實現來一種事件驅動、單一進程模型,支持非常大的併發連接數。

Keepalived通過自身健康檢查、資源接管功能做高可用(雙機熱備),實現故障轉移。

Keepalived採用VRRP(Virtual Router Redundancy Protocol,虛擬路由冗餘協議),以軟件形式實現服務的熱備功能。通常情況下是將兩臺Linux服務器組成一個熱備組(Master和Backup),同一時間內熱備組只有一臺主服務器Master提供服務,同時Master會虛擬出一個公用的虛擬IP地址,簡稱VIP。這個VIP只存在於Master上並對外提供服務。如果Keepalived檢測到Master宕機或者服務故障,備份服務器Backup會自動接管VIP併成爲Master,Keepalived將原Master從熱備組中移除。當原Master恢復後,會自動加入到熱備組,默認再搶佔成爲Master啓到故障轉移的功能。

LVS是Linux Virtual Server的簡稱,是4層負載均衡,建立在OSI模型的傳輸層上。支持TCP/UDP的負載均衡非常高效。

LVS主要由3部分組成

  • 負載調度器(Load Balancer/Director):它是整個集羣對外面的前端機,負責將客戶的請求發送到一組服務器上執行,而客戶認爲服務是來自一個IP地址(VIP)上的

  • 服務器池(Server Pool/RealServer):一組真正執行客戶端請求的服務器

  • 共享存儲(Shared Storage):它爲服務器提供來一個共享的存儲區,這樣很容易使服務器池擁有相同的內容,提供相同的服務。

目前LVS的負載均衡地方式也分爲三種。

  • VS/NAT:Virtual Server via Network Address Translation的簡稱。是一種最簡單的方式,所有的RealServer只需要將自己的網關指向Director即可。但是一個Director可以帶動的RealServer比較有限

  • VS/TUN:Virtual Server via IP Tunneling的簡稱。IP隧道是將一個IP報文封裝在另一個IP報文的技術。這可以是目標爲一個IP地址的數據報文能夠被封裝和轉發到另一個IP地址,也稱之爲IP封裝技術(IP encapsulation)

  • VS/DR:Virtual Server via Direct Routing的簡稱。這是通過改寫報文中的MAC地址部分來實現,Director和RealServer必須在物理上由一個網卡通過不間斷的局域網相連。RealServer上綁定的VIP配置在各自的Non-ARP的網絡設備上(如lo或tunl),Director的VIP地址對外空間,而RealServer的VIP對外不可見。RealServer的地址既可以是內部地址,也可以是真實地址

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