爲什麼我們從RabbitMQ切換到apache kafka?

Trello過去三年一直在使用RabbitMQ,在RabbitMQ之前,我們還使用了於Redis Pub-Sub實現。最近,由於RabbitMQ在發生網絡分區時出現了可疑行為,我們已經切換到了Kafka。

這篇博文深入介紹了我們的RabbitMQ實現,為什麼我們最終選擇選擇了Kafka和基於Kafka的架構。

當前狀況

Trello使用15個RabbitMQ實例的集群進行所有websocket更新,我們的服務器將消息發佈到集群,然後我們的websocket實例從隊列中提取消息,但是會涉及一些配置特性。

插曲:RabbitMQ的工作原理

RabbitMQ能讓你使用一個帶key的路由將消息發佈到交換機,每個交換機都有一個與之關聯的路由策略:fanout、單個路由key、前綴等。隊列使用路由ley綁定到交換機,交換機嘗試根據路由key和交換機配置將已發佈的消息與隊列進行匹配。

創建隊列時,你可以將它們指定為瞬態; 一旦TCP連接創建後就會關閉,並且所有關聯的綁定都被刪除,它們立即被銷燬。

插曲2:Trello Websocket協議

我們使用的websocket協議非常簡單; 有一個最小的請求/響應機制,我們支持的唯一命令是對通道進行訂閱和取消訂閱。

訂閱命令包含Trello模型類型(board,memeber,組織,card)及其各自的模型ID。(banq注:Trello是提供敏捷看板項目管理的網站)

消息路由

我們讓每個websocket進程(每實例8個進程)連接到RabbitMQ併為自己創建一個臨時隊列來設置這個系統,當進程獲得websocket連接並接收訂閱命令時,它將對這個訂閱創建一個綁定,以便更新交換機。

RabbitMQ Sharding

通過RabbitMQ的消息按其模型ID進行16個以上的分片。

Trello Server使用客戶端計算的分片鍵將所有消息發佈到3個實例的rabbitmq入站群集的單個交換機上,這16個不同的分片鍵都有自己的綁定,綁定到16個不同的隊列上。然後我們使用 shovel插件將這16個隊列分配給4個不同的rabbitmq-outbound出站集群(每個集群有3個實例),每個集群包含4個隊列。websocket客戶端服務器連接到所有RabbitMQ集群,訂閱所需的隊列,這取決於連接用戶的請求方式。

這背後的理論是負載分配並水平擴展RMQ基礎設施,但是,由於群集本身不可靠(單實例故障或網絡中斷可能導致整個群集完全失敗),入站群集仍然是單點故障。

問題

Rabbit的主要問題體現在它處理分區和通常集群中斷上,結果略有不同,但範圍基本都是從裂腦到完全集群失敗,更糟糕的是,從死群中恢復通常需要完全重置它,在我們的例子中,這意味著我們必須刪除所有socket並強制Web客戶端重新連接以確保它們可以重新檢索錯過的更新,然而,這可能還不足以在裂腦情況下完全恢復 - 網絡客戶端可能已經錯過了一條消息而收到了後面的一條消息,一切就無法知道了。

此外,還有另一個問題 - 在RabbitMQ中創建隊列和進行綁定既緩慢又昂貴,銷燬隊列和綁定也很昂貴,每次我們丟失套接字服務器時,我們都會看到取消訂閱和重新訂閱的風暴,因為客戶端websockets被丟棄並嘗試重新連接,這需要RMQ花費一些時間來處理。雖然我們可以重新啟動一個服務器的簡單情況下處理它,但如果我們丟失了所有的websocket連接並且必須重新連接它們(發生的次數比我們想要的多),那麼大量的綁定的添加/刪除命令將導致RMQ群集變得無響應,甚至對監視命令或正常進程信號也無視,這會導致集群故障。

為了解決這個問題,我們在將斷開連接傳播到RMQ服務器時引入了一些抖動。這對大規模套接字丟棄有很大幫助,但網絡分區仍然是一個問題。

可用解決方案

比較了多個候選方案後,我們認為kafka是最好的選擇。希望Redis流將在未來實際可用; Redis是一個簡潔的工具,可以實現更高效的架構。

然後比較了Kafka驅動器kafka-node和node-rdkafka,

因為我們需要故障轉移,所以選擇node-rdkafka,當我們對這兩個進行故障測試時,發現kafka-node故障轉移不起作用,我們感到非常困惑,我們發現node-rdkafka是我們想要的一切,並沒有進一步調查為什麼會這樣。

重要的是要注意,node-rdkafka它實際上是一個包裝librdkafka,“官方”(如:由Confluent員工開發)Kafka的C ++客戶端。

結果

Socket服務器現在具有主-客架構,主服務器訂閱整個主題並接收所有增量更新,根據客戶端向用戶轉發所需的模型在本地進行過濾。這種方式從一開始就給我們的服務器帶來了更多的負擔,但是擴展它相對容易(通過獲得更大的CPU)。當客戶端收到訂閱請求時,它會檢查權限,然後將請求轉發給主服務器,從而將模型ID保存在映射中。

“客戶端”實際上接受來自用戶的套接字連接,處理其身份驗證,並將訂閱請求轉發給主服務器。

當增量更新進入時,主服務器檢查是否有任何客戶端對該特定模型感興趣並將消息轉發給它,然後分發給用戶。

度量

現在,卡夫卡的所有情況都有非常好的指標!以前,RabbitMQ儀表板中只提供了一些指標,如消息速率。現在我們將所有Kafka指標導入我們自己的存儲,這使我們可以對所有內容發出警報。

消費者滯後(consumer lag)的指標(從隊列服務器和客戶端的角度來看!)以前RMQ沒有以這種有組織的方式提供給我們。雖然可以為Rabbit構建,但我們只是在重寫過程中添加了它。

與以前相比,內存使用量下降了大約33%,而CPU使用率增加到大約2倍。內存減少是由於所需隊列數量減少,而CPU增加是由於本地過濾造成的。

停機

幸運的是,我們只經歷過一個小小的停機!雖然我們最近才切換到新的基於Kakfa的架構,但該集群已經啟用並已發佈超過一個月,我們還沒有停電!與轉換前RabbitMQ在一個月內造成的4次中斷相比,這是一個好消息。

在RabbitMQ升級期間(trusty→ xenial),我們設法崩潰並重新連接整個服務器場,kafka的max_open_file的限制數值未正確設置也導致某些進程無法連接。

成本

少了很多!雖然不是主要的激勵因素,但降低成本非常簡潔。

RMQ由大量c3.2xlarge實例組成。現在卡夫卡由幾個Zookeeper的m4.large和kafka的i3.large組成。這些變化導致成本降低了5倍。好極了!

為什麼我們從RabbitMQ切換到apache kafka?


分享到:


相關文章: