Kafka不停機,如何無感知遷移ZooKeeper集羣?

Kafka 在 Yelp 的應用十分廣泛,Yelp 每天通過各種集羣發送數十億條消息,在這背後,Kafka 使用 Zookeeper 完成各種分佈式協調任務。
因爲Yelp 非常依賴 Kafka,那麼問題來了,它是否可以在不引起 Kafka 及其他 Zookeeper 用戶注意的情況下切換 Zookeeper 集羣呢?本文將揭曉答案。
Kafka 在 Yelp 的應用十分廣泛。事實上,我們 每天通過各種集羣發送數十億條消息。在這背後,Kafka 使用 Zookeeper 完成各種分佈式協調任務,例如決定哪個 Kafka broker 負責分配分區首領,以及在 broker 中存儲有關主題的元數據。
Kafka 在 Yelp 的成功應用說明了我們的集羣從其首次部署 Kafka 以來經歷了大幅的增長。與此同時,其他的 Zookeeper 重度用戶(例如 Smartstack 和 PaasTA)規模也在增長,給我們的共享 Zookeeper 集羣添加了很多負擔。爲了緩解這種情況,我們決定讓我們的 Kafka 集羣使用專門的 Zookeeper 集羣。
由於我們非常依賴 Kafka,因維護造成的任何停機都會導致連鎖反應,例如顯示給業務所有者的儀表盤出現延遲、日誌堆積在服務器上。那麼問題就來了:我們是否可以在不引起 Kafka 及其他 Zookeeper 用戶注意的情況下切換 Zookeeper 集羣?
Zookeeper 有絲分裂
經過團隊間對 Kafka 和 Zookeeper 的幾輪討論和頭腦風暴之後,我們找到了一種方法,似乎可以實現我們的目標:在不會導致 Kafka 停機的情況下讓 Kafka 集羣使用專門的 Zookeeper 集羣。
我們提出的方案可以比作自然界的 細胞有絲分裂:我們複製 Zookeeper 主機(即 DNA),然後利用防火牆規則(即細胞壁)把複製好的主機分成兩個獨立的集羣。

Kafka不停機,如何無感知遷移ZooKeeper集羣?

有絲分裂中的主要事件,染色體在細胞核中分裂
讓我們一步一步深入研究細節。在本文中,我們將會用到源集羣和目標集羣,源集羣代表已經存在的集羣,目標集羣代表 Kafka 將要遷移到的新集羣。我們要用到的示例是一個包含三個節點的 Zookeeper 集羣,但這個過程本身可用於任何數量的節點。
我們的示例將爲 Zookeeper 節點使用以下 IP 地址:
源 192.168.1.1-3
目標 192.168.1.4-6
第 1 階段:DNA 複製
首先,我們需要啓動一個新的 Zookeeper 集羣。這個目標集羣必須是空的,因爲在遷移的過程中,目標集羣中的內容將被刪除。
然後,我們將目標集羣中的兩個節點和源集羣中的三個節點組合在一起,得到一個包含五個節點的 Zookeeper 集羣。這麼做的原因是我們希望數據(最初由 Kafka 保存在源 Zookeeper 集羣中)被複制到目標集羣上。Zookeeper 的複製機制會自動執行復制過程。
Kafka不停機,如何無感知遷移ZooKeeper集羣?

把來自源集羣和目標集羣的節點組合在一起
每個節點的 zoo.cfg 文件現在看起來都像下面這樣,包含源集羣的所有節點和目標集羣中的兩個節點:
server.1=192.168.1.1:2888:3888
server.2=192.168.1.2:2888:3888
server.3=192.168.1.3:2888:3888
server.4=192.168.1.4:2888:3888
server.5=192.168.1.5:2888:3888
注意,來自目標集羣的一個節點(在上面的例子中是 192.168.1.6)在該過程中保持休眠狀態,沒有成爲聯合集羣的一部分,並且 Zookeeper 也沒有在其上運行,這是爲了保持源集羣的 quorum。
此時,聯合集羣必須重啓。確保執行一次滾動重啓(每次重啓一個節點,期間至少有 10 秒的時間間隔),從來自目標集羣的兩個節點開始。這個順序可以確保源集羣的 quorum 不會丟失,並在新節點加入該集羣時確保對其他客戶端(如 Kafka)的可用性。
Zookeeper 節點滾動重啓後,Kafka 對聯合集羣中的新節點一無所知,因爲它的 Zookeeper 連接字符串只有原始源集羣的 IP 地址:
zookeeper.connect=192.168.1.1,192.168.1.2,192.168.1.3/kafka
發送給 Zookeeper 的數據現在被複制到新節點,而 Kafka 甚至都沒有注意到。
現在,源集羣和目標集羣之間的數據同步了,我們就可以更新 Kafka 的連接字符串,以指向目標集羣:
zookeeper.connect=192.168.1.4,192.168.1.5,192.168.1.6/kafka
需要來一次 Kafka 滾動重啓,以獲取新連接,但不要進行整體停機。
第 2 階段:有絲分裂
拆分聯合集羣的第一步是恢復原始源 Zookeeper 及目標 Zookeeper 的配置文件(zoo.cfg),因爲它們反映了集羣所需的最終狀態。注意,此時不應重啓 Zookeeper 服務。
我們利用防火牆規則來執行有絲分裂,把我們的聯合集羣分成不同的源集羣和目標集羣,每個集羣都有自己的首領。在我們的例子中,我們使用 iptables 來實現這一點,但其實可以兩個 Zookeeper 集羣主機之間強制使用的防火牆系統應該都是可以的。
對每個目標節點,我們運行以下命令來添加 iptables 規則:
$source_node_list = 192.168.1.1,192.168.1.2,192.168.1.3
sudo /sbin/iptables -v -A INPUT -p tcp -d $source_node_list -j REJECT
sudo /sbin/iptables -v -A OUTPUT -p tcp -d $source_node_list -j REJECT
這將拒絕從目標節點到源節點的任何傳入或傳出 TCP 流量,從而實現兩個集羣的分隔。

Kafka不停機,如何無感知遷移ZooKeeper集羣?

通過防火牆規則分隔源集羣和目標集羣,然後重啓
分隔意味着現在兩個目標節點與其他節點是分開的。因爲它們認爲自己屬於一個五節點的集羣,而且無法與集羣的大多數節點進行通信,所以它們無法進行首領選舉。
此時,我們同時重啓目標集羣中每個節點的 Zookeeper,包括那個不屬於聯合集羣的休眠節點。這樣 Zookeeper 進程將使用步驟 2 中提供的新配置,而且還會強制在目標集羣中進行首領選舉,從而每個集羣都會有自己的首領。
從 Kafka 的角度來看,目標集羣從發生網絡分區那一刻起就不可用,直到首領選舉結束後纔可用。對 Kafka 來說,這是整個過程中 Zookeeper 不可用的唯一一個時間段。從現在開始,我們有了兩個不同的 Zookeeper 集羣。
現在我們要做的是清理。源集羣仍然認爲自己還有兩個額外的節點,我們需要清理一些防火牆規則。
接下來,我們重啓源集羣,讓只包含原始源集羣節點的 zoo.cfg 配置生效。我們現在可以安全地刪除防火牆規則,因爲集羣之間不再需要相互通信。下面的命令用於刪除 iptables 規則:
$source_node_list = 192.168.1.1,192.168.1.2,192.168.1.3
sudo /sbin/iptables -v -D INPUT -p tcp -d $source_node_list -j REJECT
sudo /sbin/iptables -v -D OUTPUT -p tcp -d $source_node_list -j REJECT
樹立信心分佈式壓力測試
我們用於測試遷移過程正確性的主要方法是分佈式壓力測試。在遷移過程中,我們通過腳本在多臺機器上運行數十個 Kafka 生產者和消費者實例。當流量生成完成後,所有被消費的數據有效載荷被聚集到單臺主機上,以便檢測是否發生數據丟失。
分佈式壓力測試的工作原理是爲 Kafka 生產者和消費者創建一組 Docker 容器,並在多臺主機上並行運行它們。所有生成的消息都包含了一個序列號,可以用於檢測是否發生消息丟失。
臨時集羣
爲了證明遷移的正確性,我們需要構建一些專門用於測試的集羣。我們不是通過手動創建 Kafka 集羣,然後在測試完以後再關掉它們,而是構建了一個工具,可以在我們的基礎架構上自動生成和關閉集羣,從而可以通過腳本來執行整個測試過程。
這個工具連接到 AWS EC2 API 上,並用特定的 EC2 實例標籤激活多臺主機,允許我們的 puppet 代碼配置主機和安裝 Kafka(通過 External Node Classifiers
這個臨時集羣腳本後來被用於創建臨時 Elasticsearch 集羣進行集成測試,這證明了它是一個非常有用的工具。
zk-smoketest
我們發現,phunt 的 Zookeeper smoketest 腳本 Zookeeper 集羣的狀態。在遷移的每個階段,我們在後臺運行 smoketest,以確保 Zookeeper 集羣的行爲符合預期。
zkcopy
我們的第一個用於遷移的計劃涉及關閉 Kafka、把 Zookeeper 數據子集複製到新集羣、使用更新過的 Zookeeper 連接重啓 Kafka。遷移過程的一個更精細的版本——我們稱之爲“阻止和複製(block & copy)”——被用於把 Zookeeper 客戶端遷移到存有數據的集羣,這是因爲“有絲分裂”過程需要一個空白的目標 Zookeeper 集羣。用於複製 Zookeeper 數據子集的工具是 zkcopy,它可以把 Zookeeper 集羣的子樹複製到另一個集羣中。
我們還添加了事務支持,讓我們可以批量管理 Zookeeper 操作,並最大限度地減少爲每個 znode 創建事務的網絡開銷。這使我們使用 zkcopy 的速度提高了約 10 倍。
另一個加速遷移過程的核心功能是“mtime”支持,它允許我們跳過複製早於給定修改時間的節點。我們因此避免了讓 Zookeeper 集羣保持同步的第 2 個“catch-up”複製所需的大部分工作。Zookeeper 的停機時間從 25 分鐘減少爲不到 2 分鐘。
經驗教訓
Zookeeper 集羣是輕量級的,如果有可能,儘量不要在不同服務之間共享它們,因爲它們可能會引起 Zookeeper 的性能問題,這些問題很難調試,並且通常需要停機進行修復。
我們可以在 Kafka 不停機的情況下讓 Kafka 使用新的 Zookeeper 集羣,但是,這肯定不是一件小事。
如果在進行 Zookeeper 遷移時允許 Kafka 停機,那就簡單多了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章