RabbitMQ的腦裂踩坑 - ryanrodemoyer

banq發表於2022-07-17

我的手錶嗡嗡作響,在黎明前的昏迷中,我無法辨認這是警報還是電話。時間是凌晨 4 點 45 分:我們最大的客戶報告說他們的請求需要兩個多小時才能返回結果。我們認為這是因為我們的RabbitMQ訊息系統。

近三年來,我們一直在為我們的生產系統執行 RabbitMQ,並且 99.5% 的時間完全沒有問題。在那段時間裡,我們已經擴充套件到 200 多個併發消費者,在十幾個虛擬機器上執行,​​同時協調訊息處理(1 個佇列到 N 個消費者),並在我們的 .NET 應用程式中處理了數億條訊息。

我們的主要用例是對另一個 Web 服務進行 HTTP 呼叫,以檢索 JSON 資料或下載 PDF 文件。我會告訴你我推薦 RabbitMQ,那是因為我推薦。在大多數情況下,使用它非常棒,並且在我們的應用程式中表現良好。
但是,這裡有一個很大的問題,所有這些都付出了我們在做出架構決策時不知道的代價。

RabbitMQ 是我們檢查作業結果的輪詢架構的骨幹。典型的動作順序是:使用者透過 Web 應用程式提交請求,後端透過向 RabbitMQ 新增訊息來處理該訊息。消費者獲得該訊息並對另一個 Web 服務進行 HTTP 呼叫以實際提交請求。從那裡開始,輪詢邏輯接手,佇列上的後續訊息每條都代表了一次輪詢嘗試,以檢索出結果。如果一項工作沒有結果,消費者就會把訊息放回佇列,這樣我們就可以把下一次輪詢嘗試延遲一定的時間(客戶可配置)。我們的延遲邏輯使用了一個具有生存時間(TTL)和死信定義的佇列網路。

我們的非開發叢集使用兩個或三個節點,而生產叢集使用三個節點。每個叢集都有一個負載平衡器,應用程式嚴格來說只針對負載平衡器。在執行時,釋出者和消費者使用同一個負載平衡器。

在實施三年後,這是我在編寫與 RabbitMQ 互動的任何一行程式碼之前會告訴自己的。

在開始時聘請一位專家
大概花費 2000-3000 美元(猜測),您可以聘請一家 RabbitMQ 諮詢公司並獲得與專家交流的時間。利用這個機會來審查和驗證您的假設、計劃、提問、獲得建議並進行盡職調查,這樣您就可以透過現在做出正確的決定來減少未來的麻煩和問題,並很可能在長期內節省資金。或者你可以採取我們的方法,在情況不妙的時候請專家幫忙。

使用像EasyNetQ或NServiceBus這樣的庫
我們的應用程式使用 RabbitMQ 的 RabbitMQ.Client 庫,這些抽象庫(如 EasyNetQ、NServiceBus)也使用它。然而,他們比我更出色,在與 RabbitMQ 在如此低的水平上進行互動方面,他們比我知道得多。來自 RabbitMQ 的驅動程式是低階的、原始的,並期望您瞭解有關 RabbitMQ 的細微差別。如果這是你第一次使用 RabbitMQ,那麼我保證你將沒有經驗來理解這種細微差別。

在你問 "你為什麼不使用包裝庫?"之前,讓我告訴你。在我的案例中,我們的 RabbitMQ 專案在實施接近尾聲時落到了我的頭上,原開發人員離開了公司,他決定直接使用 RabbitMQ.Client 庫。我沒有足夠的時間來進行這種交換(我也不知道我應該提出一個交換包裝庫的理由!)。

有這個網路分割槽的事情,它是一種大問題
就通用術語而言,你的 RabbitMQ 系統被稱為一個叢集。一個叢集是由一個或多個節點組成的。一個節點只是一個執行 RabbitMQ 軟體的伺服器/容器。叢集中的所有節點必須執行完全相同的 RabbitMQ 版本。

RabbitMQ 提供了一種稱為叢集的機制,因此您可以將其他 RabbitMQ 例項連線起來,以便它們作為單個邏輯代理一起執行。您可以向叢集中的任何節點發出任何請求,這些節點將合作釋出訊息或將訊息傳送到消費者手中。

節點之間透過交換關於訊息、佇列、交換等的資料不斷進行通訊。如果(以及何時)該通訊被中斷,即使只有幾毫秒,那麼 RabbitMQ 將進入分割槽狀態並檢視配置檔案以確定如何處理該通訊中斷。

預設的分割槽處理策略是ignore ,這意味著直接進入分割槽狀態,並在這種 "分裂的大腦 "模式下繼續前進,從而將您的叢集推入完全混亂的狀態。這對我們來說是地獄。退出分割槽的唯一方法是重新啟動分割槽一側的節點,這樣它就會重新加入另一側並承擔他們的資料,從而丟棄它自己在叢集被分割槽時積累的資料集。

我曾親身經歷過網路分割槽以兩種方式發生:叢集中的所有節點透過Windows更新和防火牆規則同時被更新。對Windows更新的修復是確保叢集中的節點在不同時間打補丁。

我必須停下自己的腳步,因為我可以繼續就這一話題滔滔不絕地說上無數的話。正確的配置是將partion_handling策略設定為pause_minority。當叢集被分割槽時,分割槽的一邊將簡單地關閉自己,從而完全避免了大腦分裂的情況發生。關閉的那一邊將繼續監視叢集的恢復通訊,並在那時重新加入自己。現在你所要做的就是確保你的程式碼正確地處理斷開的連線,你將擁有一個相當強大的佇列解決方案。

從CAP規範來看,ignore 意味著以犧牲一致性獲得可用性,而pause_minority是以犧牲可用性代價來獲得一致性。如果你問我的話,後者是相當值得的。

您打算如何升級 RabbitMQ 版本?
當您的 RabbitMQ 版本已達到壽命終點時,這一天將會到來。然後您會怎麼做?繼續執行不支援的版本?建立一個新的叢集?您將有什麼計劃將流量從舊叢集遷移到新叢集?回顧我的說明(上文),叢集中的所有節點都需要執行相同的確切版本。希望你能明白,如果你的計劃是就地升級節點,這將是很棘手的。

我留給你的只有問題,沒有答案。這是因為每個決定都高度依賴於你的組織和運營策略。換句話說,每個人可能有一點不同的方法來解決這些問題。

如果您丟失了 RabbitMQ 中的所有訊息,您有什麼計劃?
如果您丟失 RabbitMQ 中的所有(甚至三分之一)訊息,您會有多大的損失?RabbitMQ 是您的記錄系統嗎?您是否有一個恢復策略來使您的應用程式恢復到正常狀態?當您將您的內部伺服器轉移到雲端時會發生什麼 - 您如何讓您的 RabbitMQ 訊息再次流動起來?

構建您的應用程式以支援釋出者和消費者的不同連線地址
在未來的某個時間點(也許是在升級期間),您將希望能夠靈活地從不同的叢集和/或負載均衡器獨立釋出和消費。這是一個零風險高回報的模式,你可以儘早在你的應用程式中建立,你會在未來拍拍屁股走人。

日誌檔案將增長到消耗幾十千兆位元組的磁碟空間
隨著時間的推移,來自 RabbitMQ 的日誌檔案將增長到消耗數千兆位元組的磁碟空間。使用 rabbitmqctl rotate_logs 輪迴記錄這些檔案很容易,但要努力實現流程自動化,以便 "磁碟空間耗盡 "不會導致中斷。



Reddit:
1、 RabbitMQ 是一個很棒的工具包,根據用例,它可能是您組織的最佳通用訊息傳遞解決方案。即使在雲部署中,我可能更喜歡在容器或 VM 中執行 RabbitMQ,而不是執行 AQS 或 Azure ServiceBus 之類的東西,如果沒有其他原因,只是因為您可以在本地開發機器上快速輕鬆地執行 rabbit 以進行測試。
也就是說,RabbitMQ 有點特殊。由於訊息傳遞領域的特定需求或 Erlang 的特定語義,Rabbit 中的工作方式往往與您的直覺所暗示的有所不同。叢集中的一個示例,其中佇列(可能)僅存在於一臺機器上,因此如果分割槽將該機器排除在叢集之外,則這些佇列根本不可用。也就是說,您可以與看起來健康的節點建立活動連線,但仍然無法傳送或接收訊息,因為您未連線的另一個節點處於離線狀態。(您可以透過佇列複製在一定程度上改善這一點,但這並非在所有佇列上都可用,預設情況下也不可用)。
正確配置和調整 Rabbit 是戰鬥的重要組成部分,確保您有足夠的受過培訓的員工來解決常見錯誤是另一回事。

2、我已經執行RabbitMQ十多年了,有很多事情我希望在我接受它之前有人告訴我,但這些事情都不是那些。(從規模上看,我沒有處理任何瘋狂的事情,也許每天有5千萬條訊息,有大約90個佇列和大約300個消費者在一個本地資料中心環境中。)

像這樣的事情,叢集對於併發和效能來說比它對於HA來說更有用。我最終編寫了客戶端連線庫,它將連線到幾個獨立的 RabbitMQ 伺服器。訂閱者將同時訂閱所有的伺服器。編寫者將隨機寫到任何一個伺服器上。這些庫將處理斷開連線等問題。因此,升級到一個新的 RabbitMQ 伺服器只是一次升級一個的問題,只要至少有一個伺服器是有效的,客戶端就可以繼續執行。

像這樣的事情,你不能期望訊息以特定的順序被處理,因此,以這樣一種方式設定你的佇列是很重要的,你會得到你所期望的東西。為了簡單起見,我傾向於畫一個訊息處理流程圖,併為每個步驟使用一個單獨的佇列,而不是試圖在每個訊息中編碼狀態或使用訂閱者/消費者鍵來引導訊息。這種設計的簡單性使得在出現問題時更容易處理。

例如,當你有一個會使消費者崩潰的訊息,而你使用的是手動ack,該訊息會使_所有的消費者崩潰,直到你修復導致崩潰的東西或手動從佇列中刪除該訊息。(我稱這些訊息為 "毒藥"。)

像這樣的事情,消費者真的應該正確地關閉他們的連線,當有人忘記這樣做的時候,你可以很快地用完插座。

除非你在一個資源受限的環境中工作,否則像這樣的事情你現在並不真正需要佇列。在現代的雲環境中,你可以直接發射lambdas進行非同步處理,而忘記佇列管理。

綜上所述,資料庫不是佇列,我不會將它們互換使用。


3、我在生產環境中執行 rabbitmq 已經 6 年了,上週才第一次中斷。伺服器的正常執行時間超過 800 天(當然,它與公共 Internet 隔離了防火牆)。另一方面,我有幾十次 Postgres 中斷。並不是說 Postgres 不可靠,它堅如磐石,但我們比 RMQ 更努力地推動它。我的觀點很簡單,RabbitMQ 真的不難維護和使用。