應用.Net+Consul維護RabbitMq的高可用性

  懶人學習的過程就是工做中老大讓幹啥讓作啥就研究研究啥,國慶放假回來的週末老大經過釘釘給我佈置了個任務, RabbitMQ高可用解決方案,我想說釘釘太坑了:html

這是國慶事後9號週日晚上下班給的任務,我週一看到的時候一看,下週五,那豈不是21號,時間是如此的充裕!那不還早呢麼。。恰巧同窗要面試了9號晚上一塊兒吃飯,而後問了我幾個算法,而後被鄙視了。。他說我一個前端都比你作後臺的算法牛逼,你請客吧-。-因而週一到週三光學算法了(程序員爲了吹牛逼,哪有啥節操啊)直到週四老大說,明天任務到期了!研究咋樣了!此時才恍然大悟,釘釘你個坑貨,下週五是14號!!前端

RabbitMQ 高可用集羣

簡單配置

  對於RabbitMQ 高可用集羣的說明,我以爲這篇文章講的挺詳細的,就不說了。配置集羣的方式看官網就能夠了 ,爲了採起所謂的Active/Active方案,因此只能選鏡像模式了(3.x版本以上才支持.再抄一段解釋過來(與普通集羣相比,其實質和普通模式不一樣之處在於,消息實體會主動在鏡像節點間同步,而不是在 consumer 取數據時臨時拉取。該模式帶來的反作用也很明顯,除了下降系統性能外,若是鏡像隊列數量過多,加之大量的消息進入,集羣內部的網絡帶寬將會被這種同步通信大大消耗掉。因此在對可靠性要求較高的場合中適用),在搭建好RabbitMq集羣之後,node

鏡像模式能夠經過命令行爲隊列添加同步策略,好比python

爲全部隊列應用鏡像模式的策略git

rabbitmqctl set_policy ha-all "^" '{"ha-mode":"all"}'程序員

或者指定指定隊列名的:面試

rabbitmqctl set_policy yu-ha "^yu" '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'算法

或者直接在管理頁面添加後端

點擊Admin菜單-->右側的Policies選項-->左側最下下邊的Add / update a policyapi

 

 

name就是隊列名,Pattern就是匹配的規則,好比寫個^yu就是以yu開頭的隊列,ha-mode=啥就是會同步什麼隊列,好比=all的話就是同步全部匹配的隊列。

而後新建隊列的時候就能夠指定那臺機器上跑主隊列了。

所謂的坑

       仍是抄一下這篇文章的內容,經常使用的手段就是經過HAProxy+KeepAlive保證RabbitMq的集羣高可用:

建立 queue 的過程:

  1. LB 將 client request 分發到 node 2,client 建立隊列 「NewQueue」,而後開始向其中放入 message。
  2. 最終,後端服務會對 node 2 上的 「NewQueue」 建立一個快照,並在一段時間內將其拷貝到node 1 和 3 上。這時候,node2 上的隊列是 master Queue,node 1 和 3 上的隊列是 slave queue。

假如如今 node2 宕機了:

  • node 2 再也不響應心跳,它會被認爲已經被從集羣中移出了
  • node 2 上的 master queue 再也不可用
  • RabbitMQ 將 node 1 或者 3 上的 salve instance 升級爲 master instance

假設 master queue 還在 node 2 上,客戶端經過 LB 訪問該隊列:

  1. 客戶端鏈接到集羣,要訪問 「NewQueue」 隊列
  2. LB 根據配置的輪詢算法將請求分發到一個節點上
  3. 假設客戶端請求被轉到 node 3 上
  4. RabbitMQ 發現 「NewQueue」 master node 是 node 2
  5. RabbitMQ 將消息轉到 node 2 上
  6. 最終客戶端成功鏈接到 node 2 上的 master 隊列

可見,這種配置下,2/3 的客戶端請求須要重定向,這會形成大機率的訪問延遲,可是終究訪問仍是會成功的。要優化的話,總共有兩種方式:

  • 直接連到 master queue 所在的節點,這樣就不須要重定向了。可是對這種方式,須要提早計算,而後告訴客戶端哪一個節點上有 master queue。
  • 儘量地在全部節點間平均分佈隊列,減小重定向機率

 爲了不這種n-1/n的這種重定向,知道Master queue所在的節點很重要啊,接下來就不抄了。

思路

     大體的意思就是這張圖:

 

  1.將RabbitMq註冊到Consul中(步驟1),經過Consul對RabbitMq進行健康監測,同時Consul提供配置中心的服務,能夠存儲一些RabbitMq的配置信息,好比隊列帳號,密碼,隊列主機名,所在Ip等,舉個例子:

  將RabbitMq服務註冊到Consul:  

{
   "services": [{
     "id":"rabbit@rabbitmq1",
     "name":"RabbitMqServer",
     "tags":["rabbitMq"],
     "address": "192.168.1.101",
     "port": 15672,
     "checks": [
       {
         "Http": "http://192.168.1.101:15672/",
         "interval": "10s"
       }
     ]
   },
   {
     "id":"rabbit@rabbitmq2",
     "name":"RabbitMqServer",
     "tags":["rabbitMq"],
     "address": "192.168.1.102",
     "port": 15672,
     "checks": [
       {
         "Http": "http://192.168.1.102:15672/",
         "interval": "10s"
       }
     ]
   }
   ]
 } 
View Code

  將RabbitMq的隊列信息存入到Consul中:

  2.業務服務須要配置QueueName+VirthHost,經過步驟2從RabbitMq網關進行隊列信息的獲取,而後才能經過步驟5與隊列進行推拉操做,這裏能夠獲取可用隊列對應的Master隊列所在的節點信息,避免n-1/n這種接受推送轉發的問題。

  3.RabbitMq網關接受到業務服務的請求後,經過Consul獲取集羣中任意一個健康的RabbitMq隊列的信息(Consul提供的健康監測功能),而後根據該隊列得到與RabbitMq通訊的WebApi,RabbitMq的Http Api文檔提供了獲取隊列詳情的接口,好比獲取隊列對用的Master信息是可用經過接口:http://127.0.0.1:15672/api/queues/%2F/yu_queue,%2F對應的是VirthHost,是/的轉碼,yu_queue是隊列名,這倆參數經過業務服務請求Api時提供,經過Api返回的Json字符串中包含了該隊列Master的節點的對用信息,其中node屬性對應的就是rabbitmq的節點名,如:rabbit@rabbitmq1,而後能夠經過以前在consul中配置的RabbitMq信息來查找該節點對用的隊列信息返回給業務服務。

  這裏會有個坑須要注意:

  訪問RabbitMq的Http Api是須要身份驗證的,這個Basic驗證的Token獲取查了查文檔沒找到- -

  

  後來驚奇發現。。 Convert.ToBase64String(Encoding.ASCII.GetBytes(userName + ":" + password))); 哎。。不想多說了。。。

  還有個Api參數中帶/的問題,.net 4.5以上的版本用HttpClient沒啥問題,4.5如下版本或者用HttpWebRequest的時候須要對Uri作下處理

public static void ForceCanonicalPathAndQuery(Uri uri)
        {
            string paq = uri.PathAndQuery; // need to access PathAndQuery 
            FieldInfo flagsFieldInfo = typeof(Uri).GetField("m_Flags", BindingFlags.Instance | BindingFlags.NonPublic);
            ulong flags = (ulong)flagsFieldInfo.GetValue(uri);
            flags &= ~((ulong)0x30); // Flags.PathNotCanonical|Flags.QueryNotCanonical 
            flagsFieldInfo.SetValue(uri, flags);
        } 
View Code

附加說明:

  其實思路很簡單,不過明明能夠經過封裝一個SDK(何況原本就得封裝- -)就完成的事情爲何還要牽扯出Consul和多餘的一個RabbitMq網關呢,何況有了RabbitMq網關豈不是說還要單點問題了?!

  針對單點問題。。我以爲部署幾份無狀態的網關仍是沒啥壓力的吧。。SKD封裝的時候本身輪訓去吧!

  對於爲何要用到Consul,一方面是爲了健康監測,健康監測可讓Consul經過Consul Http Api直接獲取可用的RabbitMq的Http Api信息,還有就是配置中心,業務服務經過配置隊列名,VirthHost,和獲取隊列服務的RabbitMq網關便可,RabbirMq網關負責經過配置中心獲取隊列信息,配置中心的數據是動態的,更新起來也比較方便。

  對於爲啥不經過SDK直連RabbitMq的Api而是經過網關做爲一箇中間代理,在經過Consul獲取隊列信息時能夠作個定時緩存,並且像隊列的用戶名密碼的這種信息經過業務服務配置的話維護不方便,業務服務經過SDK直接經過Consul獲取的話,依賴關係也會變得略微錯綜複雜。經過RabbitMq的Api也能夠動態獲取隊列的集羣節點信息,權限信息等,在業務服務SDK裏按期更新也何嘗不可,但SDK終究變複雜了,你幹那個多不累麼。。

  爲何參數要VirthHost+QueueName,不一樣部門的人總歸不是同一我的。。

正在糾結的問題:

  針對這種思路,對於RabbitMq中的Routing模式Topic模式會有問題,單routekey對應單隊列時能夠經過隊列獲取Exhange下該routekey有效發送到某臺服務器上的隊列上,假如該routekey綁定的隊列分佈在多臺服務器上,而且這些隊列的主從分佈在多臺服務器上時,我經過獲取到的「Master隊列」只能針對某一隊列時真Master,對於其餘隊列若是Master不在該ip上仍是會存在轉發的問題。這個問題在拉數據時沒啥問題(拉數據我是須要隊列名的!),推數據時只用到Exchange+RouteKey,誰還管你的隊列是主是從呢?

 

 

參考連接:

http://www.cnblogs.com/sammyliu/p/4730517.html

https://insidethecpu.com/2014/11/17/load-balancing-a-rabbitmq-cluster/

相關文章
相關標籤/搜索