標籤:style ice typeof 成功 可靠性 att 同步 nod load
懶人學習的過程就是工作中老大讓幹啥讓做啥就研究研究啥,國慶放假回來的周末老大通過DingTalk給我布置了個任務, RabbitMQ高可用解決方案,我想說DingTalk太坑了:
這是國慶過後9號周日晚上下班給的任務,我周一看到的時候一看,下周五,那豈不是21號,時間是如此的充裕!那不還早呢麼。。恰巧同學要面試了9號晚上一起吃飯,然後問了我幾個演算法,然後被鄙視了。。他說我一個前端都比你做背景演算法牛逼,你請客吧-。-於是周一到周三光學演算法了(程式員為了吹牛逼,哪有啥節操啊)直到周四老大說,明天任務到期了!研究咋樣了!此時才恍然大悟,DingTalk你個坑貨,下周五是14號!!
RabbitMQ 高可用叢集
簡單配置
對於RabbitMQ 高可用叢集的說明,我覺得這篇文章講的挺詳細的,就不說了。配置叢集的方式看官網就可以了 ,為了採取所謂的Active/Active方案,所以只能選鏡像模式了(3.x版本以上才支援).再抄一段解釋過來(與普通叢集相比,其實質和普通模式不同之處在於,訊息實體會主動在鏡像節點間同步,而不是在 consumer 取資料時臨時拉取。該模式帶來的副作用也很明顯,除了降低系統效能外,如果鏡像隊列數量過多,加之大量的訊息進入,叢集內部的網路頻寬將會被這種同步通訊大大消耗掉。所以在對可靠性要求較高的場合中適用),在搭建好RabbitMq叢集以後,
鏡像模式可以通過命令列為隊列添加同步策略,比如
為所有隊列應用鏡像模式的策略
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 policy
name就是隊列名,Pattern就是匹配的規則,比如寫個^yu就是以yu開頭的隊列,ha-mode=啥就是會同步什麼隊列,比如=all的話就是同步所有匹配的隊列。
然後建立隊列的時候就可以指定那台機器上跑主隊列了。
所謂的坑
還是抄一下這篇文章的內容,常用的手段就是通過HAProxy+KeepAlive保證RabbitMq的叢集高可用:
建立 queue 的過程:
- LB 將 client request 分發到 node 2,client 建立隊列 “NewQueue”,然後開始向其中放入 message。
- 最終,後端服務會對 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 訪問該隊列:
- 用戶端串連到叢集,要訪問 “NewQueue” 隊列
- LB 根據配置的輪詢演算法將請求分發到一個節點上
- 假設用戶端請求被轉到 node 3 上
- RabbitMQ 發現 “NewQueue” master node 是 node 2
- RabbitMQ 將訊息轉到 node 2 上
- 最終用戶端成功串連到 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":"[email protected]", "name":"RabbitMqServer", "tags":["rabbitMq"], "address": "192.168.1.101", "port": 15672, "checks": [ { "Http": "http://192.168.1.101:15672/", "interval": "10s" } ] }, { "id":"[email protected]", "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的節點名,如:[email protected],然後可以通過之前在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/
應用.Net+Consul維護RabbitMq的高可用性