天天看點

RabbitMQ原理、叢集、基本操作及常見故障處理

本次學習主要針對運維人員,和對rabbitmq不熟悉的開發人員。通過本次學習你将掌握rabbitmq 的基本原理、叢集、基本運維操作、常見故障處理。

1、原理與概念

簡介

AMQP,即Advanced Message Queuing Protocol,進階消息隊列協定,是應用層協定的一個開放标準,為面向消息的中間件設計。消息中間件主要用于元件之間的解耦,消息的發送者無需知道消息使用者的存在,反之亦然。

AMQP的主要特征是面向消息、隊列、路由(包括點對點和釋出/訂閱)、可靠性、安全。RabbitMQ是一個開源的AMQP實作,伺服器端用Erlang語言編寫,支援多種用戶端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支援AJAX。用于在分布式系統中存儲轉發消息,在易用性、擴充性、高可用性等方面表現不俗。

解決的問題

RabbitMQ就是目前最主流的消息中間件之一。

  • 兩個(多個)系統間需要通過定時任務來同步某些資料
  • 異構系統的不同程序間互相調用、通訊的問題

Queue

Queue(隊列)是RabbitMQ的内部對象,用于存儲消息,用下圖表示。

RabbitMQ原理、叢集、基本操作及常見故障處理

RabbitMQ中的消息都隻能存儲在Queue中,生産者(下圖中的P)生産消息并最終投遞到Queue中,消費者(下圖中的C)可以從Queue中擷取消息并消費。

RabbitMQ原理、叢集、基本操作及常見故障處理

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

RabbitMQ原理、叢集、基本操作及常見故障處理

技術術語

  • Broker:簡單來說就是消息隊列伺服器實體。
  • producer:消息生産者,就是投遞消息的程式。
  • consumer:消息消費者,就是接受消息的程式。
  • vhost:虛拟主機,一個broker裡可以開設多個vhost,用作權限分離,把不同的系統使用的rabbitmq區分開,共用一個消息隊列伺服器,但看上去就像各自在用不用的rabbitmq伺服器一樣。
  • Connection:一個網絡連接配接,比如TCP/IP套接字連接配接。
  • channel:消息通道,是建立在真實的TCP連接配接内的虛拟連接配接(是我們與RabbitMQ打交道的最重要的一個接口)。僅僅建立了用戶端到Broker之間的連接配接後,用戶端還是不能發送消息的,需要為每一個Connection建立Channel,AMQP協定規定隻有通過Channel才能執行AMQP的指令。AMQP的指令都是通過信道發送出去的(我們大部分的業務操作是在Channel這個接口中完成的,包括定義Queue、定義Exchange、綁定Queue與Exchange、釋出消息等。)。每條信道都會被指派一個唯一ID。在用戶端的每個連接配接裡,可建立多個channel,每個channel代表一個會話任務,理論上無限制,減少TCP建立和銷毀的開銷,實作共用TCP的效果。之是以需要Channel,是因為TCP連接配接的建立和釋放都是十分昂貴的,如果一個用戶端每一個線程都需要與Broker互動,如果每一個線程都建立一個TCP連接配接,暫且不考慮TCP連接配接是否浪費,就算作業系統也無法承受每秒建立如此多的TCP連接配接。注1:一個生産者或一個消費者與MQ伺服器之間隻有一條TCP連接配接 注2:RabbitMQ建議用戶端線程之間不要共用Channel,至少要保證共用Channel的線程發送消息必須是串行的,但是建議盡量共用Connection。
  • Exchange:消息交換機,生産者不是直接将消息投遞到Queue中的,實際上是生産者将消息發送到Exchange(交換器,下圖中的X),由Exchange将消息路由到一個或多個Queue中(或者丢棄)。
RabbitMQ原理、叢集、基本操作及常見故障處理
  • Exchange Types RabbitMQ常用的Exchange Type有fanout、direct、topic、headers這四種(AMQP規範裡還提到兩種Exchange Type,分别為system與自定義,這裡不予以描述),之後會分别進行介紹。
  • Queue:消息隊列載體,每個消息都會被投入到一個或多個隊列。
  • Binding:綁定,它的作用就是把exchange和queue按照路由規則綁定起來,這樣RabbitMQ就知道如何正确地将消息路由到指定的Queue了。
RabbitMQ原理、叢集、基本操作及常見故障處理
  • Routing Key:路由關鍵字,生産者在将消息發送給Exchange的時候,一般會指定一個routing key,來指定這個消息的路由規則,而這個routing key需要與Exchange Type及binding key聯合使用才能最終生效。
RabbitMQ原理、叢集、基本操作及常見故障處理
  • 在Exchange Type與binding key固定的情況下(在正常使用時一般這些内容都是固定配置好的),我們的生産者就可以在發送消息給Exchange時,通過指定routing key來決定消息流向哪裡。
  • Prefetch count 前面我們講到如果有多個消費者同時訂閱同一個Queue中的消息,Queue中的消息會被平攤給多個消費者。這時如果每個消息的處理時間不同,就有可能會導緻某些消費者一直在忙,而另外一些消費者很快就處理完手頭工作并一直空閑的情況。我們可以通過設定prefetchCount來限制Queue每次發送給每個消費者的消息數,比如我們設定prefetchCount=1,則Queue每次給每個消費者發送一條消息;消費者處理完這條消息後Queue會再給該消費者發送一條消息。
RabbitMQ原理、叢集、基本操作及常見故障處理
RabbitMQ原理、叢集、基本操作及常見故障處理

消息隊列的使用過程

在AMQP模型中,Exchange是接受生産者消息并将消息路由到消息隊列的關鍵元件。ExchangeType和Binding決定了消息的路由規則。是以生産者想要發送消息,首先必須要聲明一個Exchange和該Exchange對應的Binding。

在Rabbit MQ中,聲明一個Exchange需要三個參數:ExchangeName,ExchangeType和Durable。ExchangeName是該Exchange的名字,該屬性在建立Binding和生産者通過publish推送消息時需要指定。ExchangeType,指Exchange的類型,在RabbitMQ中,有三種類型的Exchange:direct ,fanout和topic,不同的Exchange會表現出不同路由行為。Durable是該Exchange的持久化屬性,這個會在消息持久化章節讨論。

RabbitMQ原理、叢集、基本操作及常見故障處理

聲明一個Binding需要提供一個QueueName,ExchangeName和BindingKey。

RabbitMQ原理、叢集、基本操作及常見故障處理

下面是消息發送的過程

RabbitMQ原理、叢集、基本操作及常見故障處理
  1. 建立連接配接Connection。由producer和consumer建立連接配接,連接配接到broker的實體節點上。
  2. 建立消息Channel。Channel是建立在Connection之上的,一個Connection可以建立多個Channel。producer連接配接Virtual Host 建立Channel,Consumer連接配接到相應的queue上建立Channel。
  3. 發送消息。由Producer發送消息到Broker中的Exchange中。
  4. 路由轉發。生産者Producer在發送消息時,都需要指定一個RoutingKey和Exchange,Exchange收到消息後可以看到消息中指定的RoutingKey,再根據目前Exchange的ExchangeType,按一定的規則将消息轉發到相應的queue中去。
  5. 消息接收。Consumer會監聽相應的queue,一旦queue中有可以消費的消息,queue就将消息發送給Consumer端。
  6. 消息确認。當Consumer完成某一條消息的處理之後,需要發送一條ACK消息給對應的Queue。Queue收到ACK資訊後,才會認為消息處理成功,并将消息從Queue中移除;如果在對應的Channel斷開後,Queue沒有收到這條消息的ACK資訊,該消息将被發送給另外的Channel。至此一個消息的發送接收流程走完了。消息的确認機制提高了通信的可靠性。

exchange 與 Queue 的路由機制

RabbitMQ原理、叢集、基本操作及常見故障處理
  • exchange 将消息發送到哪一個queue是由exchange type 和bing 規則決定的,目前常用的有3種exchange,Direct exchange, Fanout exchange, Topic exchange 。Direct exchange 直接轉發路由,其實作原理是通過消息中的routkey,與queue 中的routkey 進行比對,若二者比對,則将消息發送到這個消息隊列。通常使用這個。
RabbitMQ原理、叢集、基本操作及常見故障處理
  • 以上圖的配置為例,我們以routingKey=”error”發送消息到Exchange,則消息會路由到Queue1(amqp.gen-S9b…,這是由RabbitMQ自動生成的Queue名稱)和Queue2(amqp.gen-Agl…);如果我們以routingKey=”info”或routingKey=”warning”來發送消息,則消息隻會路由到Queue2。如果我們以其他routingKey發送消息,則消息不會路由到這兩個Queue中。
  • Fanout exchange 複制分發路由,該路由不需要routkey,當exchange收到消息後,将消息複制多份轉發給與自己綁定的消息隊列。
RabbitMQ原理、叢集、基本操作及常見故障處理
  • 上圖中,生産者(P)發送到Exchange(X)的所有消息都會路由到圖中的兩個Queue,并最終被兩個消費者(C1與C2)消費。topic exchange 通配路由,是direct exchange的通配符模式,消息中的routkey可以寫成通配的模式,exchange支援“#”和“*” 的通配。收到消息後,将消息轉發給所有符合比對表達式的queue。
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。

需要注意的一點隻有queue具有 保持消息的功能,exchange不能儲存消息。

  • headers headers類型的Exchange不依賴于routing key與binding key的比對規則來路由消息,而是根據發送的消息内容中的headers屬性進行比對。在綁定Queue與Exchange時指定一組鍵值對;當消息發送到Exchange時,RabbitMQ會取到該消息的headers(也是一個鍵值對的形式),對比其中的鍵值對是否完全比對Queue與Exchange綁定時指定的鍵值對;如果完全比對則消息會路由到該Queue,否則不會路由到該Queue。該類型的Exchange沒有用到過(不過也應該很有用武之地),是以不做介紹。

durability 持久化與非持久化隊列

RabbitMQ原理、叢集、基本操作及常見故障處理
  1. 如何識别?如上圖,在Features字段裡有一個D,就是持久化隊列,英文durable(持久的)
  2. 持久化隊列和非持久化隊列的差別是什麼?持久化隊列會被儲存在磁盤中,固定并持久的存儲,當Rabbit服務重新開機後,該隊列會保持原來的狀态在RabbitMQ中被管理,而非持久化隊列不會被儲存在磁盤中,Rabbit服務重新開機後隊列就會消失。
  3. 如何選擇?如果需要隊列的完整性,資料在隊列中的儲存是必須不允許丢失的,那麼可以使用持久化。而當需要擷取的資訊是實時的,或者是随機的資訊,不需要資訊的精确性或完整性,但是追求擷取性能,可以選擇非持久化隊列。

2、分布式叢集架構和高可用性

設計叢集的目的

  • 允許消費者和生産者在RabbitMQ節點崩潰的情況下繼續運作
  • 通過增加更多的節點來擴充消息通信的吞吐量

叢集配置方式

RabbitMQ可以通過三種方法來部署分布式叢集系統,分别是:cluster,federation,shovel

  • cluster:
  1. 不支援跨網段,用于同一個網段内的區域網路
  2. 可以随意的動态增加或者減少
  3. 節點之間需要運作相同版本的RabbitMQ和Erlang
  • federation:應用于廣域網,允許單台伺服器上的交換機或隊列接收釋出到另一台伺服器上交換機或隊列的消息,可以是單獨機器或叢集。federation隊列類似于單向點對點連接配接,消息會在聯盟隊列之間轉發任意次,直到被消費者接受。通常使用federation來連接配接internet上的中間伺服器,用作訂閱分發消息或工作隊列。
  • shovel:連接配接方式與federation的連接配接方式類似,但它工作在更低層次。可以應用于廣域網

RabbitMQ cluster 叢集同步原理

RabbitMQ原理、叢集、基本操作及常見故障處理

上面圖中采用三個節點組成了一個RabbitMQ的叢集,Exchange A的中繼資料資訊在所有節點上是一緻的,而Queue(存放消息的隊列)的完整資料則隻會存在于它所建立的那個節點上。,其他節點隻知道這個queue的metadata資訊和一個指向queue的owner node的指針。RabbitMQ叢集中繼資料的同步

RabbitMQ叢集會始終同步四種類型的内部中繼資料(類似索引):

  1. 隊列中繼資料:隊列名稱和它的屬性;
  2. 交換器中繼資料:交換器名稱、類型和屬性;
  3. 綁定中繼資料:一張簡單的表格展示了如何将消息路由到隊列;
  4. vhost中繼資料:為vhost内的隊列、交換器和綁定提供命名空間和安全屬性;是以,當使用者通路其中任何一個RabbitMQ節點時,通過rabbitmqctl查詢到的queue/user/exchange/vhost等資訊都是相同的。

為何RabbitMQ叢集僅采用中繼資料同步的方式?

一,存儲空間,如果每個叢集節點都擁有所有Queue的完全資料拷貝,那麼每個節點的存儲空間會非常大,叢集的消息積壓能力會非常弱(無法通過叢集節點的擴容提高消息積壓能力);

二,性能,消息的釋出者需要将消息複制到每一個叢集節點,對于持久化消息,網絡和磁盤同步複制的開銷都會明顯增加。

RabbitMQ cluster 叢集的兩種模式

  1. 普通模式:預設的叢集模式。
  2. 鏡像模式:把需要的隊列做成鏡像隊列,存在于多個節點,屬于RabbitMQ的HA方案
RabbitMQ原理、叢集、基本操作及常見故障處理

普通模式:當消息進入A節點的Queue中後,consumer從B節點拉取時,RabbitMQ會臨時在A、B間進行消息傳輸,把A中的消息實體取出并經過B發送給consumer,是以consumer應平均連接配接每一個節點,從中取消息。該模式存在一個問題就是當A節點故障後,B節點無法取到A節點中還未消費的消息實體。如果做了隊列持久化或消息持久化,那麼得等A節點恢複,然後才可被消費,并且在A節點恢複之前其它節點不能再建立A節點已經建立過的持久隊列;如果沒有持久化的話,消息就會失丢。這種模式更适合非持久化隊列,隻有該隊列是非持久的,用戶端才能重新連接配接到叢集裡的其他節點,并重新建立隊列。假如該隊列是持久化的,那麼唯一辦法是将故障節點恢複起來。為什麼RabbitMQ不将隊列複制到叢集裡每個節點呢?這與它的叢集的設計本意相沖突,叢集的設計目的就是增加更多節點時,能線性的增加性能(CPU、記憶體)和容量(記憶體、磁盤)。當然RabbitMQ新版本叢集也支援隊列複制(有個選項可以配置)。比如在有五個節點的叢集裡,可以指定某個隊列的内容在2個節點上進行存儲,進而在性能與高可用性之間取得一個平衡(應該就是指鏡像模式)。

鏡像模式:其實質和普通模式不同之處在于,消息實體會主動在鏡像節點間同步,而不是在consumer取資料時臨時拉取。該模式帶來的副作用也很明顯,除了降低系統性能外,如果鏡像隊列數量過多,加之大量的消息進入,叢集内部的網絡帶寬将會被這種同步通訊大大消耗掉。是以在對可靠性要求較高的場合中适用.

節點類型

  • RAM node:記憶體節點将所有的隊列、交換機、綁定、使用者、權限和vhost的中繼資料定義存儲在記憶體中,好處是可以使得像交換機和隊列聲明等操作更加的快速。
  • Disk node:将中繼資料存儲在磁盤中,單節點系統隻允許磁盤類型的節點,防止重新開機RabbitMQ的時候,丢失系統的配置資訊。
RabbitMQ原理、叢集、基本操作及常見故障處理

如果是記憶體結點這裡就顯示為RAM注意

  • RabbitMQ要求在叢集中至少有一個磁盤節點,所有其他節點可以是記憶體節點,當節點加入或者離開叢集時,必須要将該變更通知到至少一個磁盤節點。
  • 如果叢集中唯一的一個磁盤節點崩潰的話,叢集仍然可以保持運作,但是無法進行其他操作(包括建立隊列、交換器、綁定,添加使用者、更改權限、添加和删除叢集結點),直到節點恢複。
  • 解決方案:設定兩個磁盤節點,至少有一個是可用的,可以儲存中繼資料的更改。

Erlang Cookie

Erlang Cookie是保證不同節點可以互相通信的密鑰,要保證叢集中的不同節點互相通信必須共享相同的Erlang Cookie。具體的目錄存放在/var/lib/rabbitmq/.erlang.cookie。

3、基本操作

rabbitmq叢集必要條件

綁定實體ip,即ifconfig所能查詢到的綁定到網卡上的ip,以下是綁定方法

#編輯配置路徑 /etc/rabbitmq/rabbitmq-env.conf
NODE_IP_ADDRESS=172.16.136.133
複制代碼           

複制

配置域名映射到實體ip

#配置檔案1所在路徑 /etc/rabbitmq/rabbitmq.config (如果是叢集,每台機器都需要修改這個綁定本機實體ip)
#其中rabbit@master是建立叢集時所配置的參數,@後面的參數為主機名,示例中為master
[
{rabbit, [
{cluster_nodes, {['rabbit@master'], disc}},
{cluster_partition_handling, ignore},
{default_user, <<"guest">>},
{default_pass, <<"guest">>},
{tcp_listen_options, [binary,
{packet, raw},
{reuseaddr, true},
{backlog, 128},
{nodelay, true},
{exit_on_close, false},
{keepalive, true}]}
]},
{kernel, [
{inet_dist_listen_max, 44001},
{inet_dist_listen_min, 44001}
]}
].
複制代碼
#配置檔案2 所在路徑 /etc/hosts (如果是叢集,每台機器都需要修改這個綁定本機實體ip,而且hosts檔案的映射不得重複,如果重複linux系統為以最下面一條記錄為準)
172.16.136.133 master
172.16.136.134 venus
172.16.136.135 venus2           

複制

啟動停止

停止
#機器A
service rabbitmq-server stop
epmd -kill
#機器B
service rabbitmq-server stop
epmd -kill
#機器C
service rabbitmq-server stop
epmd -kill

啟動方式1
#機器A
service rabbitmq-server start
#機器B
service rabbitmq-server start
#機器C
service rabbitmq-server start

啟動方式2
rabbitmq-server -detached           

複制

叢集重新開機順序

叢集重新開機的順序是固定的,并且是相反的。如下所述:

啟動順序:磁盤節點 => 記憶體節點 關閉順序:記憶體節點 => 磁盤節點 最後關閉必須是磁盤節點,不然可能會造成叢集啟動失敗、資料丢失等異常情況。

重建叢集

注1:此處的mq叢集重建是比較快速和有效的方法,面向的是初次安裝或者可以接受mq中所存有的資料丢失的情況下,必須先有mq的.json字尾的配置檔案或者有把握寫入叢集中exchange、queue等配置。

按順序停止所有機器中的rabbitmq

#機器A
service rabbitmq-server stop
epmd -kill
#機器B
service rabbitmq-server stop
epmd -kill
#機器C
service rabbitmq-server stop
epmd -kill           

複制

移除rabbitmq配置記錄與存儲檔案

#位于 /var/lib/rabbitmq/mensia
mv /var/lib/rabbitmq/mensia /var/lib/rabbitmq/mensia.bak           

複制

按順序啟動所有機器中的rabbitmq

#機器C
service rabbitmq-server start
#機器B
service rabbitmq-server start
#機器A
service rabbitmq-server start           

複制

停止被加入叢集節點app

比如A、B、C三台機器,将B和C加入到A中去,需要執行以下指令
#機器B
rabbitmqctl stop_app
#機器C
rabbitmqctl stop_app           

複制

建立叢集

注意此處master為唯一沒有執行rabbitmqctl stop_app的機器
#機器B
rabbitmqctl join_cluster rabbit@master
#機器C
rabbitmqctl join_cluster rabbit@master

啟動叢集
#機器B
rabbitmqctl start_app
#機器C
rabbitmqctl start_app           

複制

檢查叢集狀态

在任意一台機器上執行rabbitmqctl cluster_status指令即可檢查,輸出包含叢集中的節點與運作中的節點,兼以主機名标志

添加叢集配置

建立使用者

例子中建立了兩個使用者 添加使用者add_user,設定角色set_user_tags,添加rabbitmq虛拟主機add_vhost,設定通路權限set_permissions,以下是詳細用法

# 建立第一個使用者
/usr/sbin/rabbitmqctl add_user 使用者名 密碼
/usr/sbin/rabbitmqctl set_user_tags 使用者名 administrator
/usr/sbin/rabbitmqctl set_permissions -p / 使用者名 ".*" ".*" ".*"
# 建立第二個使用者
/usr/sbin/rabbitmqctl add_user 使用者名2 密碼
/usr/sbin/rabbitmqctl set_user_tags 使用者名2 management
/usr/sbin/rabbitmqctl add_vhost sip_ext
/usr/sbin/rabbitmqctl set_permissions -p sip_ext 使用者名2 '.*' '.*' '.*'

備注:RabbitMQ 虛拟主機,RabbitMQ 通過虛拟主機(vhost)來分發消息。擁有自己獨立的權限控制,不同的vhost之間是隔離的,單獨的。
權限控制的基本機關:vhost。
使用者隻能通路與之綁定的vhost。
vhost是AMQP中唯一無法通過協定來建立的基元。隻能通過rabbitmqctl工具來建立。           

複制

打開15672網頁管理端,通路mq

/usr/sbin/rabbitmq-plugins enable rabbitmq_management 備注:如果發現指令執行完畢沒有打開此服務,15672端口沒有監聽,則是由于沒有重新開機mq導緻的

在底部導入.json字尾的配置檔案即可

http://localhost:4000/first-blog/rabbitmq.jpg

RabbitMQ原理、叢集、基本操作及常見故障處理

如果覆寫了使用者需要使用以下指令修改mq使用者密碼 /usr/sbin/rabbitmqctl change_password 使用者名 密碼

修改節點類型

rabbitmqctl stop_app
rabbitmqctl change_cluster_node_type dist
rabbitmqctl change_cluster_node_type ram
rabbitmqctl start_app           

複制

常用指令

RabbitMQ原理、叢集、基本操作及常見故障處理

4、常見故障

叢集狀态異常

  1. rabbitmqctl cluster_status檢查叢集健康狀态,不正常節點重新加入叢集
  2. 分析是否節點挂掉,手動啟動節點。
  3. 保證網絡連通正常

隊列阻塞、資料堆積

  1. 保證網絡連通正常
  2. 保證消費者正常消費,消費速度大于生産速度
  3. 保證伺服器TCP連接配接限制合理

腦裂

  1. 按正确順序重新開機叢集
  2. 保證網絡連通正常
  3. 保證磁盤空間、cpu、記憶體足夠