Tips for deploy and manage high availability RabbitMQ Cluster

漸強實驗室透過 RabbitMQ 每天在不同服務中遞送數百萬則的訊息,來完成許多的工作,雖然日均量看起來並不多,但是因為功能的關係,RabbitMQ 上流動的訊息量常常是一波又一波的峰值(大約是平常的 10~20 倍左右),所以能夠有一個高可用的 RabbitMQ 來確保系統正常運作就非常重要了,本篇文章淺談我在漸強部署以及維運 HA RabbitMQ Cluster 的一些經驗與看法跟大家分享。

以下分為四個部分來聊一聊

1. Why RabbitMQ?

2. Experience and opinions on deploying high availability RabbitMQ cluster

3. Best Practice

4. Conclusion

1. Why RabbitMQ?

如果不熟悉這個服務,稍微簡介一下,它就是個 MQ (Message Queue),至於兔子的部份,恩… 所以我想可以部分回答 Why RabbitMQ? 基本上就是系統上有異步隊列的使用需求,再多去查一下,你會發現他是個用 Erlang 寫的 Message broker 服務,實現了 AMQP (Advanced Message Queueing Protocol) ,也支援 MQTT 跟 STOMP 等等協議,這神秘的語言跟神秘的協議,是不是讓你覺得這兔子非常有魅力呢 XD,真心建議如果真的要用 RabbitMQ 一定要去讀一下 AMQP,會對你了解這個服務非常有幫助!

話說回來為什麼用 RabbitMQ 呢,簡單的說就是我們使用了 Python 的 Celery 這個套件來實作這個異步任務隊列的需求,而 Celery 的 broker 官方當時提供的選擇不多,大部分的人應該都會在 Redis 或 RabbitMQ 中選一個,Celery 天生默認就是完全集成 RabbitMQ,有些集成的功能非常好用(雖然是另一個災難的開始,Celery 太多坑了…),然而 Redis 是因為資料結構的支援,所以也被拿來當成 broker,因此就服務場景與需求來說,RabbitMQ 成了我們的選擇 。

2. Experience and opinions on deploying high availability RabbitMQ cluster

當釐清了需求,也挑選好了實現方式,接下來就是需要兩台 RabbitMQ (staging & production 兩個環境),負責設計或搭建架構的人,這時應該都有個很重要的問題要問:要買現成的服務?還是要自己搭? 背後牽涉到的問題就包括了金錢跟時間以及維護成本等等。

最有名的現成服務應該就是 CloudAMQP 了吧,當時的我先去看了看價目表,算一下系統大概需要哪個等級的方案,在對比過去也有稍微玩過 RabbitMQ 的經驗之後,不囉唆把手弄髒 yaml 檔寫起來,直接 apply 到 Kubernetes 裡面,很快的就有 RabbitMQ 可以用了,幫公司省錢省一波!

說句實在話,我覺得單台 RabbitMQ 效能真的不錯,傻傻的衝動型部署就開始用了,這服務應該也撐了有半年之久,但夜路走多了總會遇到鬼👻,公司的客戶數量快速的成長,終於有一天,孤單的 RabbitMQ 被流量打爆了,做了些緊急處理之後,花了兩週時間研究部署了一個 HA RabbitMQ Cluster,用到現在應該有超過一年了,蠻穩定的,途中也有遇到一些出乎意料的流量,看起來還非常堪用!

因為我是部署在 Kubernetes 內,所以這些經驗主要是基於 Kubernetes 上的配置,首先是 Helm 有兩個 RabbitMQ 的 Chart,合用的話就拿去用吧~而我當初是覺得沒有那麼合用,改著改著就自己寫了一版,後來又改著改著就改回純 yaml… 我是覺得這樣比較一眼瞬間,反正也沒有要弄第二座…,以下幾條列幾個部署 HA RabbitMQ Cluster 經驗的分享:

  1. 單純部署 Cluster 只是把 Queue 分到不同機器去,當機器掛了,上面的 Queue 也就跟著掛,所以並不會讓你有 HA 的功用,要讓你的 Queue 有 HA,必須要對 Queue 下 Mirror (讓同一個 Queue 複製到其他的 Node 上),才能達成 Queue 的 HA (大概是下這類指令 ex. rabbitmqctl set_policy ha….)。
  2. RabbitMQ 可以隨意在 Cluster 內,對某個 Queue 下 HA,且要 Mirror 到哪些 node 上也可以選,但建議就全部的 Queue 都下 Mirror 到全部的 Node (除非你部署超過3個 Node),來以防 Queue 跟 Mirror Queue 的結構長得太複雜。
  3. 基於 RabbitMQ Mirror Queue 的實現原理,Mirror 到越多 Node 的 Queue 效能越差,根據我當時爬到的文章,跟測試結果,我就直接理解成,如果你需要較高的 Throughput 又要兼顧可用性,權衡之下就是 Mirror 到 1 個 Node(一個 Queue 存在兩個 Node),如果需要 HA 那就是 Mirror 到 2 個 Node (一個 Queue 存在三個 Node),有時間當然是自己壓力測試一下,而 3.8 版後似乎提供了比 Mirror Queue 更好的選擇 Quorum Queues 大家也可以參考一下。
  4. RabbitMQ Node有分 Disk Node 跟 RAM Node,簡單來說建議都用 Disk Node,不然最少最少也要有兩個 Disk Node。
  5. RabbitMQ 在 memory usage 太高或是 free disk space 太低,就會開始不接客了,建議根據你給的資源去設定 vm_memory_high_watermark 跟 disk_free_limit,有相對值跟絕對值可以設定,知道這個值有助於你理解服務是不是快要關門了。
  6. 如果是部署在 Kubernetes 請一定要下 affinity ,來確保你的 Pod (RabbitMQ Node)不要長在同一個 VM (K8S Node)上,不然你 VM 倒了,有幾個 Pod 在 HA 都是假的…
  7. 在 Kubernetes 中 RabbitMQ 是透過 rabbitmq_peer_discovery_k8s 這個插件來實現 Cluster 的,而他只需要 get endpoints 權限,RBAC 切記權限不要多給。
  8. rabbitmq_management plugin 非常之好用,拜託一定要裝或是選已經裝好的 image 版本。rabbitmq_management 提供 Web UI 跟一些 API 接口可以得到非常多資訊,也可以用來作為 Kubernetes 的 health check 機制。
  9. rabbitmq_management 提供的 API 可以用來監控 RabbitMQ 的資源,但是如果有建置 Prometheus ,建議還是加個 exporter 讓 Prometheus 集中監控是比較好的; 如果是使用 3.8.0 以上版本,則建議直接裝 rabbitmq_prometheus plugin,可以直接 expose metrics endpoint 來給 Prometheus 刮一波~

3. Best Practice

以下列舉一些用血與淚換來,在開發中我認為幾個非常重要的 best practice…

RabbitMQ 部分

  1. Queue 一定要設定最大長度,悲劇通常都是從這裡發生的!
  2. 訊息大小的極限應該是 2GB ( 3.7 版之前,3.8 版是 512MB),但是如果你傳的訊息大小已經 >100MB,你要非常擔心你 MQ 的 Memory 跟 Performance,一包訊息太大絕對是不建議的。
  3. 在打包訊息的大小跟數量做一些取捨,以取得最好的 Performance,此問題會發生在另一極端,就是巨量且小的訊息,很可能會快速的把你的 Queue 佔滿 。
  4. 若設定 Priority Queues,Priority 數值夠用就好,可能是 10 或是 5 就夠了,這會一定程度的影響 Queue 的效能,建議如果可以,還是把不同 Priority 的 Message 丟到不同的 Queue 吧(不過這也取決 Queue 的設計,有些人用業務場景切分,有些人用 Priority 切分,大家使用場景不同,可能要自己考量)。
  5. Connections Channels 跟 Queues 都是資源,數量請嚴格控管(廢話…但重要…
  6. 如果你的訊息不能丟失的話,Queue 切記聲明成 durable,且搭配 HA Cluster 使用。
  7. 如果你真的不幸需要在 RabbitMQ 的 Queue 上堆放很多訊息,那請聲明為 Lazy Queue(意指 Message 發到 Queue 後就會存到 Disk 內,用到的時候才讀回 Memory,效能會因為 Disk I/O 而變差,但是有助於你堆放訊息,且不會造成 Memory 耗盡)

Client 部分

  1. Client 端的 prefetch value,是個有趣的問題,設太小 Worker 可能太閒,而設太大可能會讓 Worker loading 不平均(Messages 都被某個 Worker 一次拿走),但肯定的是不要不設定,搞到 Worker 拿太多 Messages 做不完直接爆炸,可以從 Worker 數量跟處理訊息的型態下手,是短時間的 Tasks 還是長時間的 Tasks,如果是長時間的 Tasks 其實可以設成 1,這樣可以讓 Worker 簡單的平分這些 Tasks。
  2. 如果你的訊息不能丟失,Producer 在發送訊息給 RabbitMQ 的時候分為 persistent mode 跟 transient mode,請選擇 persistent mode,然後搭配上方 RabbitMQ 部分 第六點使用,可以最大程度的保全你的訊息在 Queue 上不丟失。
  3. 接到訊息時請確認做完該做的事後再 ack!襪靠這簡直是這種 Queue System 共同的重點啊,當然也可能是使你訊息丟失的因素之一。
  4. 如果 Worker 是用 Celery 可以關掉不必要的 Event,不然啟動多個 Celery Workers 後就會傳遞不少預設型的 Event 在 RabbitMQ 上,導致消耗掉不必要的資源。
    (ex. — without-gossip, — without-mingle and — without-heartbeat)

4. Conclusion

這篇文章希望能給觀望 RabbitMQ 的人一點方向,或是已經在用的人一些提醒,現在回過頭看,這些注意事項都是蠻基本的事情,但往往疏忽了就會造成嚴重的後果。不過我覺得此篇經驗也只限於中型以下的系統,如果是要部署巨大的 RabbitMQ Cluster 甚至用到 Shovel 或 Federation plugins 來跨外網做使用,很抱歉浪費了你的時間…

我認為 RabbitMQ 的強項應該還是在它靠著不同 type exchange 的運用,可以支援非常複雜的 routing,適合一些後台異步工作的分配,或是用於微服務的解耦。

但是作為一個 Queue Service,RabbitMQ 感覺比較像是一個郵局的概念,信件通常會需要在時限內配發出去,專注在複雜的配送上,所以如果是大量的資料流 pipeline 可能還是從善如流選擇 Kafka 會是比較好的(如果 RabbitMQ 是郵局,那 Kafka 是什麼呢… 運河!?…)。

總之如果想要自己搭建 RabbitMQ,我覺得是很值得投資的,因為 RabbitMQ 確實是個很穩定且效能不錯的服務。

文章最後也希望各路有 RabbitMQ 使用經驗或是 MQ 大神們一起來交流,如果我有經驗跟認知不足,或寫錯的部分還麻煩糾正一波,謝謝大家的耐心。

--

--