Kafka 在 Yelp 的應用十分普遍。事實上,咱們 天天經過各類集羣發送數十億條消息。在這背後,Kafka 使用 Zookeeper 完成各類分佈式協調任務,例如決定哪一個 Kafka broker 負責分配分區首領,以及在 broker 中存儲有關主題的元數據。java
Kafka 在 Yelp 的成功應用說明了咱們的集羣從其首次部署 Kafka 以來經歷了大幅的增加。與此同時,其餘的 Zookeeper 重度用戶(例如 Smartstack 和 PaasTA)規模也在增加,給咱們的共享 Zookeeper 集羣添加了不少負擔。爲了緩解這種狀況,咱們決定讓咱們的 Kafka 集羣使用專門的 Zookeeper 集羣。node
因爲咱們很是依賴 Kafka,因維護形成的任何停機都會致使連鎖反應,例如顯示給業務全部者的儀表盤出現延遲、日誌堆積在服務器上。那麼問題就來了:咱們是否能夠在不引發 Kafka 及其餘 Zookeeper 用戶注意的狀況下切換 Zookeeper 集羣?安全
通過團隊間對 Kafka 和 Zookeeper 的幾輪討論和頭腦風暴以後,咱們找到了一種方法,彷佛能夠實現咱們的目標:在不會致使 Kafka 停機的狀況下讓 Kafka 集羣使用專門的 Zookeeper 集羣。bash
咱們提出的方案能夠比做天然界的 細胞有絲分裂:咱們複製 Zookeeper 主機(即 DNA),而後利用防火牆規則(即細胞壁)把複製好的主機分紅兩個獨立的集羣。服務器
有絲分裂中的主要事件,染色體在細胞核中分裂網絡
讓咱們一步一步深刻研究細節。在本文中,咱們將會用到源集羣和目標集羣,源集羣表明已經存在的集羣,目標集羣表明 Kafka 將要遷移到的新集羣。咱們要用到的示例是一個包含三個節點的 Zookeeper 集羣,但這個過程自己可用於任何數量的節點。架構
咱們的示例將爲 Zookeeper 節點使用如下 IP 地址:tcp
源 192.168.1.1-3分佈式
目標 192.168.1.4-6工具
首先,咱們須要啓動一個新的 Zookeeper 集羣。這個目標集羣必須是空的,由於在遷移的過程當中,目標集羣中的內容將被刪除。
而後,咱們將目標集羣中的兩個節點和源集羣中的三個節點組合在一塊兒,獲得一個包含五個節點的 Zookeeper 集羣。這麼作的緣由是咱們但願數據(最初由 Kafka 保存在源 Zookeeper 集羣中)被複制到目標集羣上。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 滾動重啓,以獲取新鏈接,但不要進行總體停機。
拆分聯合集羣的第一步是恢復原始源 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 流量,從而實現兩個集羣的分隔。
經過防火牆規則分隔源集羣和目標集羣,而後重啓
分隔意味着如今兩個目標節點與其餘節點是分開的。由於它們認爲本身屬於一個五節點的集羣,並且沒法與集羣的大多數節點進行通訊,因此它們沒法進行首領選舉。
此時,咱們同時重啓目標集羣中每一個節點的 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 集羣進行集成測試,這證實了它是一個很是有用的工具。
咱們發現,phunt 的 Zookeeper smoketest 腳本在遷移過程當中可用於監控每一個 Zookeeper 集羣的狀態。在遷移的每一個階段,咱們在後臺運行 smoketest,以確保 Zookeeper 集羣的行爲符合預期。
咱們的第一個用於遷移的計劃涉及關閉 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 停機,那就簡單多了。
歡迎學Java和大數據的朋友們加入java架構交流: 855835163
加羣連接:jq.qq.com/?_wv=1027&a…羣內提供免費的架構資料還有:Java工程化、高性能及分佈式、高性能、深刻淺出。高架構。性能調優、Spring,MyBatis,Netty源碼分析和大數據等多個知識點高級進階乾貨的免費直播講解 能夠進來一塊兒學習交流哦