拋棄Kafka的Zookeeper,不停機遷移到統一集羣

Kafka在Yelp的應用十分廣泛。事實上,我們每天通過各種集羣發送數十億條消息。在這背後,Kafka使用Zookeeper完成各種分佈式協調任務,例如決定哪個Kafka broker負責分配分區首領,以及在broker中存儲有關主題的元數據。

Kafka在Yelp的成功應用說明了我們的集羣從其首次部署Kafka以來經歷了大幅的增長。與此同時,其他的Zookeeper重度用戶(例如SmartstackPaasTA)規模也在增長,給我們的共享Zookeeper集羣添加了很多負擔。爲了緩解這種情況,我們決定讓我們的Kafka集羣使用專門的Zookeeper集羣。

由於我們非常依賴Kafka,因維護造成的任何停機都會導致連鎖反應,例如顯示給業務所有者的儀表盤出現延遲、日誌堆積在服務器上。那麼問題就來了:我們是否可以在不引起Kafka及其他Zookeeper用戶注意的情況下切換Zookeeper集羣?

Zookeeper有絲分裂

經過團隊間對Kafka和Zookeeper的幾輪討論和頭腦風暴之後,我們找到了一種方法,似乎可以實現我們的目標:在不會導致Kafka停機的情況下讓Kafka集羣使用專門的Zookeeper集羣。

我們提出的方案可以比作自然界的細胞有絲分裂:我們複製Zookeeper主機(即DNA),然後利用防火牆規則(即細胞壁)把複製好的主機分成兩個獨立的集羣。

image

有絲分裂中的主要事件,染色體在細胞核中分裂

讓我們一步一步深入研究細節。在本文中,我們將會用到源集羣和目標集羣,源集羣代表已經存在的集羣,目標集羣代表Kafka將要遷移到的新集羣。我們要用到的示例是一個包含三個節點的Zookeeper集羣,但這個過程本身可用於任何數量的節點。

我們的示例將爲Zookeeper節點使用以下IP地址:

源 192.168.1.1-3
目標 192.168.1.4-6

第1階段:DNA複製

首先,我們需要啓動一個新的Zookeeper集羣。這個目標集羣必須是空的,因爲在遷移的過程中,目標集羣中的內容將被刪除。

然後,我們將目標集羣中的兩個節點和源集羣中的三個節點組合在一起,得到一個包含五個節點的Zookeeper集羣。這麼做的原因是我們希望數據(最初由Kafka保存在源Zookeeper集羣中)被複制到目標集羣上。Zookeeper的複製機制會自動執行復制過程。

image
把來自源集羣和目標集羣的節點組合在一起

每個節點的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流量,從而實現兩個集羣的分隔。

image

通過防火牆規則分隔源集羣和目標集羣,然後重啓

分隔意味着現在兩個目標節點與其他節點是分開的。因爲它們認爲自己屬於一個五節點的集羣,而且無法與集羣的大多數節點進行通信,所以它們無法進行首領選舉。

此時,我們同時重啓目標集羣中每個節點的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 Classifiershttps://puppet.com/docs/puppet/5.5/nodes_external.html)。這樣我們就可以重新運行遷移腳本,並多次模擬遷移過程。

這個臨時集羣腳本後來被用於創建臨時Elasticsearch集羣進行集成測試,這證明了它是一個非常有用的工具。

zk-smoketest

我們發現,phunt的Zookeeper smoketest腳本(https://github.com/phunt/zk-smoketest)在遷移過程中可用於監控每個Zookeeper集羣的狀態。在遷移的每個階段,我們在後臺運行smoketest,以確保Zookeeper集羣的行爲符合預期。

zkcopy

我們的第一個用於遷移的計劃涉及關閉Kafka、把Zookeeper數據子集複製到新集羣、使用更新過的Zookeeper連接重啓Kafka。遷移過程的一個更精細的版本——我們稱之爲“阻止和複製(block & copy)”——被用於把Zookeeper客戶端遷移到存有數據的集羣,這是因爲“有絲分裂”過程需要一個空白的目標Zookeeper集羣。用於複製Zookeeper數據子集的工具是zkcopyhttps://github.com/ksprojects/zkcopy),它可以把Zookeeper集羣的子樹複製到另一個集羣中。

我們還添加了事務支持,讓我們可以批量管理Zookeeper操作,並最大限度地減少爲每個znode創建事務的網絡開銷。這使我們使用zkcopy的速度提高了約10倍。

另一個加速遷移過程的核心功能是“mtime”支持,它允許我們跳過複製早於給定修改時間的節點。我們因此避免了讓Zookeeper集羣保持同步的第2個“catch-up”複製所需的大部分工作。Zookeeper的停機時間從25分鐘減少爲不到2分鐘。

經驗教訓

Zookeeper集羣是輕量級的,如果有可能,儘量不要在不同服務之間共享它們,因爲它們可能會引起Zookeeper的性能問題,這些問題很難調試,並且通常需要停機進行修復。

我們可以在Kafka不停機的情況下讓Kafka使用新的Zookeeper集羣,但是,這肯定不是一件小事。

如果在進行Zookeeper遷移時允許Kafka停機,那就簡單多了。

閱讀英文原文:Migrating Kafka’s Zookeeper With No Downtime;https://engineeringblog.yelp.com/2019/01/migrating-kafkas-zookeeper-with-no-downtime.html

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