rabbitmq模式
單一模式
rabbitmq 普通的集群模式
鏡像隊列模式(Mirror)
這里我們重點說下鏡像隊列模式:
鏡像隊列是基於普通的集群模式的,所以你還是得先配置普通集群,然后才能設置鏡像隊列。
鏡像隊列可以同步queue和message,當主queue掛掉,從queue中會有一個變為主queue來接替工作。 鏡像隊列設置后,會分一個主節點和多個從節點,如果主節點宕機,從節點會有一個選為主節點,原先的主節點起來后會變為從節點。 master和slave是針對queue而言,node節點不存在master和slave一說,只是說queue創建在哪台node上則這台node則為這個queue的master。 queue和message雖然會存在所有鏡像隊列中,但客戶端讀取時不論物理面連接的主節點還是從節點,都是從主節點讀取數據,然后主節點再將queue和message的狀態同步給從節點,因此多個客戶端連接不同的鏡像隊列不會產生同一message被多次接受的情況。
鏡像有三種類型:
all表示在集群所有的節點上進行鏡像。
exactly表示在指定個數的節點上進行鏡像,節點的個數由ha-params指定。 nodes表示在指定的節點上進行鏡像,節點名稱通過ha-params指定。
所以,集群模式是為了提高擴展性,鏡像模式提高高可用性。
rabbitmq的節點存儲類型
RabbitMQ的集群節點包括內存節點、磁盤節點。顧名思義內存節點就是將所有數據放在內存,磁盤節點將數據放在磁盤。不過,如前文所述,如果在投遞消息時,打開了消息的持久化,那么即使是內存節點,數據還是安全的放在磁盤。RAM 模式性能高,DISK模式可用性更高。
如果集群中只有內存節點,那么不要輕易停止它們,否則所有的狀態,消息等都會丟失。
根據存儲類型集群分為三種:
全內存節點
部分disk節點(一般1~2台),其它內存節點
全部disk節點
可用性依次遞增,可擴展性依次遞減。
整個集群中只有一個Disk磁盤服務節點的話,如果集群中的Disk磁盤服務器節點宕機那整個集群都不能做以下操作: 補充:
創建queue隊列; 創建exchange交換機; 創建binding; 添加用戶; 修改用戶權限; 添加或刪除節點。
有了以上缺點,那為什么還要在集群中特別設置一些Disk磁盤服務器節點呢?這是因為,要想集群重啟后元數據可以恢復,就需要把集群元數據持久化到磁盤,這也是為什么建議在rabbitmq的內置集群中為什么要設置1 ~2個Disk磁盤服務器節點的原因。RAM節點重啟后只有鏈接到Disk節點獲取元數據信息才能重新配置自己的節點。
基於以上我們選用了鏡像隊列集群+全DISK存儲模式。
queue的master和slave
queue有master 節點和slave節點。 但是要說明的是,在rabbitmq中master和slave是針對一個queue而言的,而不是一個node作為所有queue的master,其它node作為slave。 一個queue第一次創建的node為它的master節點,其它node為slave節點。
rabbitmq queue讀取過程
在鏡像隊列(Mirrored Queue)中,只有master的copy對外提供服務,而其他slave copy只提供備份服務,在master copy所在節點不可用時,選出一個slave copy作為新的master繼續對外提供服務。
無論客戶端的請求打到master還是slave最終數據都是從master節點獲取。
當請求打到master節點時,master節點直接將消息返回給client,同時master節點會通過GM(Guaranteed Multicast)協議將queue的最新狀態廣播到slave節點。GM保證了廣播消息的原子性,要么都更新要么都不更新
當請求打到slave節點時,slave節點需要將請求先重定向到master節點,master節點將將消息返回給client,同時master節點會通過GM協議將queue的最新狀態廣播到slave節點。
所以,如果master分布不均勻的話,所有節點的負載就不會均勻。
rabbitmq中消息同步的過程
gm(Guaranteed Multicast) 原子性
rabbitmq中將所有的節點形成一個循環鏈表,每個節點都會監控位於自己左右兩邊的節點,當有節點新增時,相鄰的節點保證當前廣播的消息會到新的節點上;當有節點失效時,相鄰的節點會接管保證本次廣播的消息會到所有節點。 在master節點和slave節點上的這些gm形成一個group,group的信息會記錄在mnesia中。不同的鏡像隊列形成不同的group。 消息從master節點對應的gm發出后,順着鏈表依次傳送到所有節點,由於所有節點組成一個循環鏈表,master節點對應的gm最終會收到自己發送的消息,這個時候master節點就知道消息已經到所有slave節點了。
配置鏡像隊列的時候有個ha-sync-mode屬性,這個有什么用呢? 新節點加入到group后,最多能從左邊節點獲取到當前正在廣播的消息內容,加入group之前已經廣播的消息則無法獲取到。如果此時master節點不幸失效,而新節點有恰好成為了新的master,那么加入group之前已經廣播的消息則會全部丟失。
注意:這里的消息具體是指新節點加入前已經發布並到所有slave節點的消息,並且這些消息還未被消費者消費或者未被消費者確認。如果新節點加入前,所有廣播的消息被消費者消費並確認了,master節點刪除消息的同時會通知slave節點完成相應動作。這種情況等同於新節點加入前沒有發布任何消息。
避免這種問題的解決辦法就是對新的slave節點進行消息同步。當ha-sync-mode配置為自動同步(automatic)時,新節點加入group時會自動進行消息的同步;如果配置為manually則需要手動操作完成同步。
rabbitmq對於新增節點的處理流程
如果我們要在運行時添加一個新的節點到集群中,消息復制會怎么處理?如果有新節點加入,RabbitMQ不會同步之前的歷史數據,新結點只會復制該結點加入到集群之后新增消息.這里的假設是隨着消息的被consumer取走,最終所有的節點的數據都會對齊一致。
接下來,一個自然的追問就“誕生”了:
既然master節點退出集群會選一個slave作為master,那么如果不幸選中了一個剛剛加入集群的節點怎么辦?那消息不就丟了嗎!?這里您可以把心放到肚子里,RabbitMQ集群內部會維護節點的狀態是否已經同步,使用rabbitmqctl的synchronised_slave_pids參數,就可以查看狀態.如果slave_pids和synchronised_slave_pids里面的節點是一致的,那說明全都同步了.如果不一致很容易比較出來哪些還沒有同步,集群只會在“最老”的slave節點之間選一個出來作為新的master節點。 為了減少這種情況的發生rabbitmq已經有了這個解決方案。
對於node的重啟也是作為新節點處理。
rabbitmq中鏡像隊列注意點
RabbitMQ的mirror queue(鏡像隊列)機制是最簡單的隊列HA方案,它通過在cluster的基礎上增加ha-mode、ha-param等policy選項,可以根據需求將cluster中的隊列鏡像到多個節點上,從而實現高可用,消除cluster模式中隊列內容單點帶來的風險。
在使用鏡像隊列之前,有幾點注意事項必須熟記於心:
鏡像隊列不能作為負載均衡使用,因為每個操作在所有節點都要做一遍。
ha-mode參數和durable declare對exclusive隊列都不生效,因為exclusive隊列是連接獨占的,當連接斷開,隊列自動刪除。所以實際上這兩個參數對exclusive隊列沒有意義。 將新節點加入已存在的鏡像隊列時,默認情況下ha-sync-mode=manual,鏡像隊列中的消息不會主動同步到新節點,除非顯式調用同步命令。當調用同步命令(via rabbitmqctl or web-based ui)后,隊列開始阻塞,無法對其進行操作,直到同步完畢。當ha-sync-mode=automatic時,新加入節點時會默認同步已知的鏡像隊列。由於同步過程的限制,所以不建議在生產環境的active隊列(有生產消費消息)中操作。 每當一個節點加入或者重新加入(例如從網絡分區中恢復回來)鏡像隊列,之前保存的隊列內容會被清空。 鏡像隊列有主從之分,一個主節點(master),0個或多個從節點(slave)。當master宕掉后,會在slave中選舉新的master。選舉算法為最早啟動的節點。 當所有slave都處在(與master)未同步狀態時,並且ha-promote-on-shutdown policy設置為when-syned(默認)時,如果master因為主動的原因停掉,比如是通過rabbitmqctl stop命令停止或者優雅關閉OS,那么slave不會接管master,也就是說此時鏡像隊列不可用;但是如果master因為被動原因停掉,比如VM或者OS crash了,那么slave會接管master。這個配置項隱含的價值取向是優先保證消息可靠不丟失,放棄可用性。如果ha-promote-on-shutdown policy設置為alway,那么不論master因為何種原因停止,slave都會接管master,優先保證可用性。 鏡像隊列中最后一個停止的節點會是master,啟動順序必須是master先起,如果slave先起,它會有30秒的等待時間,等待master啟動,然后加入cluster。當所有節點因故(斷電等)同時離線時,每個節點都認為自己不是最后一個停止的節點。要恢復鏡像隊列,可以嘗試在30秒之內同時啟動所有節點。 對於鏡像隊列,客戶端basic.publish操作會同步到所有節點;而其他操作則是通過master中轉,再由master將操作作用於salve。比如一個basic.get操作,假如客戶端與slave建立了TCP連接,首先是slave將basic.get請求發送至master,由master備好數據,返回至slave,投遞給消費者。 由8可知,當slave宕掉時,除了與slave相連的客戶端連接全部斷開之外,沒有其他影響。當master宕掉時,會有以下連鎖反應: 1)與master相連的客戶端連接全部斷開。 2)選舉最老的slave為master。若此時所有slave處於未同步狀態,則未同步部分消息丟失。 3)新的master節點requeue所有unack消息,因為這個新節點無法區分這些unack消息是否已經到達客戶端,亦或是ack消息丟失在到老master的通路上,亦或是丟在老master組播ack消息到所有slave的通路上。所以處於消息可靠性的考慮,requeue所有unack的消息。此時客戶端可能受到重復消息。 4)如果客戶端連着slave,並且basic.consume消息時指定了x-cancel-on-ha-failover參數,那么客戶端會收到一個Consumer Cancellation Notification通知,Java SDK中會回調Consumer接口的handleCancel()方法,故需覆蓋此方法。如果不指定x-cancel-on-ha-failover參數,那么消費者就無法感知master宕機,會一直等待下去。 上面列出的注意事項整理自官方的HA文檔。http://www.rabbitmq.com/ha.html
rabbitmq中鏡像隊列的故障恢復
前提:兩個節點(A和B)組成一個鏡像隊列。
場景1:A先停,B后停。 該場景下B是master(disk,A是ram),只要先啟動B,再啟動A即可。或者先啟動A,再在30秒之內啟動B即可恢復鏡像隊列。 場景2: A, B同時停。 該場景可能是由掉電等原因造成,只需在30秒之內連續啟動A和B即可恢復鏡像隊列。 場景3:A先停,B后停,且A無法恢復。 該場景是場景1的加強版,因為B是master,所以等B起來后,在B節點上調用rabbitmqctl forget_cluster_node A,解除與A的cluster關系,再將新的slave節點加入B即可重新恢復鏡像隊列。 場景4:A先停,B后停,且B無法恢復。 該場景是場景3的加強版,比較難處理,早在3.1.x時代之前貌似都沒什么好的解決方法,但是現在已經有解決方法了,在3.4.2版本親測有效(我們當前使用的是3.3.5)。因為B是master,所以直接啟動A是不行的,當A無法啟動時,也就沒辦法在A節點上調用rabbitmqctl forget_cluster_node B了。新版本中,forget_cluster_node支持–offline參數,offline參數允許rabbitmqctl在離線節點上執行forget_cluster_node命令,迫使RabbitMQ在未啟動的slave節點中選擇一個作為master。當在A節點執行rabbitmqctl forget_cluster_node –offline B時,RabbitMQ會mock一個節點代表A,執行forget_cluster_node命令將B剔出cluster,然后A就能正常啟動了。最后將新的slave節點加入A即可重新恢復鏡像隊列。 場景5: A先停,B后停,且A、B均無法恢復,但是能得到A或B的磁盤文件。 該場景是場景4的加強版,更加難處理。將A或B的數據庫文件(默認在$RABBIT_HOME/var/lib目錄中)拷貝至新節點C的目錄下,再將C的hostname改成A或B的hostname。如果拷過來的是A節點磁盤文件,按場景4處理方式;如果拷過來的是B節點磁盤文件,按場景3處理方式。最后將新的slave節點加入C即可重新恢復鏡像隊列。 場景6:A先停,B后停,且A、B均無法恢復,且無法得到A或B的磁盤文件。該場景下已無法恢復A、B隊列中的內容了