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

banq發表於2018-08-24
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倍。好極了!


Why We Chose Kafka For The Trello Socket Architect

相關文章