你必定不能錯過的Kafka控制器

控制器組件(Controller),是 Apache Kafka 的核心組件。它的主要做用是在 ApacheZooKeeper 的幫助下管理和協調整個 Kafka 集羣。集羣中任意一臺 Broker 都能充當控制器的角色,可是,在運行過程當中,只能有一個 Broker 成爲控制器,行使其管理和協調的職責。換句話說,每一個正常運轉的 Kafka 集羣,在任意時刻都有且只有一個控制器。官網上有個名爲 activeController 的 JMX 指標,能夠幫助咱們實時監控控制器的存活狀態。這個JMX 指標很是關鍵,你在實際運維操做過程當中,必定要實時查看這個指標的值。下面,咱們就來詳細說說控制器的原理和內部運行機制。node

在開始以前,我先簡單介紹一下 Apache ZooKeeper 框架。要知道,控制器是重度依賴ZooKeeper 的,所以,咱們有必要花一些時間學習下 ZooKeeper 是作什麼的。緩存

Apache ZooKeeper 是一個提供高可靠性的分佈式協調服務框架。它使用的數據模型相似於文件系統的樹形結構,根目錄也是以「/」開始。該結構上的每一個節點被稱爲 znode,用來保存一些元數據協調信息。安全

若是以 znode 持久性來劃分,znode 可分爲持久性 znode 和臨時 znode。持久性znode 不會由於 ZooKeeper 集羣重啓而消失,而臨時 znode 則與建立該 znode 的ZooKeeper 會話綁定,一旦會話結束,該節點會被自動刪除。多線程

ZooKeeper 賦予客戶端監控 znode 變動的能力,即所謂的 Watch 通知功能。一旦 znode節點被建立、刪除,子節點數量發生變化,抑或是 znode 所存的數據自己變動,ZooKeeper 會經過節點變動監聽器 (ChangeHandler) 的方式顯式通知客戶端。併發

依託於這些功能,ZooKeeper 常被用來實現集羣成員管理、分佈式鎖、領導者選舉等功能。Kafka 控制器大量使用 Watch 功能實現對集羣的協調管理。咱們一塊兒來看一張圖片,它展現的是 Kafka 在 ZooKeeper 中建立的 znode 分佈。你不用瞭解每一個 znode 的做用,但你能夠大體體會下 Kafka 對 ZooKeeper 的依賴。框架

1240

掌握了 ZooKeeper 的這些基本知識,如今咱們就能夠開啓對 Kafka 控制器的討論了。運維

控制器是如何被選出來的?

你必定很想知道,控制器是如何被選出來的呢?咱們剛剛在前面說過,每臺 Broker 都能充當控制器,那麼,當集羣啓動後,Kafka 怎麼確認控制器位於哪臺 Broker 呢?異步

實際上,Broker 在啓動時,會嘗試去 ZooKeeper 中建立 /controller 節點。Kafka 當前選舉控制器的規則是:第一個成功建立 /controller 節點的 Broker 會被指定爲控制器。分佈式

控制器是作什麼的?

咱們常常說,控制器是起協調做用的組件,那麼,這裏的協調做用究竟是指什麼呢?我想了一下,控制器的職責大體能夠分爲 5 種,咱們一塊兒來看看。ide

1. 主題管理(建立、刪除、增長分區)

這裏的主題管理,就是指控制器幫助咱們完成對 Kafka 主題的建立、刪除以及分區增長的操做。換句話說,當咱們執行kafka-topics 腳本時,大部分的後臺工做都是控制器來完成的。關於 kafka-topics 腳本,我會在專欄後面的內容中,詳細介紹它的使用方法。

2. 分區重分配

分區重分配主要是指,kafka-reassign-partitions 腳本(關於這個腳本,後面我也會介紹)提供的對已有主題分區進行細粒度的分配功能。這部分功能也是控制器實現的。

3. Preferred 領導者選舉

Preferred 領導者選舉主要是 Kafka 爲了不部分 Broker 負載太重而提供的一種換Leader 的方案。在專欄後面說到工具的時候,咱們再詳談 Preferred 領導者選舉,這裏你只須要了解這也是控制器的職責範圍就能夠了。

4.集羣成員管理(新增 Broker、Broker 主動關閉、Broker 宕機)

這是控制器提供的第 4 類功能,包括自動檢測新增 Broker、Broker 主動關閉及被動宕機。這種自動檢測是依賴於前面提到的 Watch 功能和 ZooKeeper 臨時節點組合實現的。

好比,控制器組件會利用Watch 機制檢查 ZooKeeper 的 /brokers/ids 節點下的子節點數量變動。目前,當有新 Broker 啓動後,它會在 /brokers 下建立專屬的 znode 節點。一旦建立完畢,ZooKeeper 會經過 Watch 機制將消息通知推送給控制器,這樣,控制器就能自動地感知到這個變化,進而開啓後續的新增 Broker 做業。

偵測 Broker 存活性則是依賴於剛剛提到的另外一個機制:臨時節點。每一個 Broker 啓動後,會在 /brokers/ids 下建立一個臨時 znode。當 Broker 宕機或主動關閉後,該 Broker 與ZooKeeper 的會話結束,這個 znode 會被自動刪除。同理,ZooKeeper 的 Watch 機制將這一變動推送給控制器,這樣控制器就能知道有 Broker 關閉或宕機了,從而進行「善後」。

5. 數據服務

控制器的最後一大類工做,就是向其餘 Broker 提供數據服務。控制器上保存了最全的集羣元數據信息,其餘全部 Broker 會按期接收控制器發來的元數據更新請求,從而更新其內存中的緩存數據。

控制器保存了什麼數據?

接下來,咱們就詳細看看,控制器中到底保存了哪些數據。我用一張圖來講明一下。

1240

怎麼樣,圖中展現的數據量是否是不少?幾乎把咱們能想到的全部 Kafka 集羣的數據都囊括進來了。這裏面比較重要的數據有:

  • 全部主題信息。包括具體的分區信息,好比領導者副本是誰,ISR 集合中有哪些副本等。

  • 全部 Broker 信息。包括當前都有哪些運行中的 Broker,哪些正在關閉中的 Broker 等。

  • 全部涉及運維任務的分區。包括當前正在進行 Preferred 領導者選舉以及分區重分配的分區列表。

值得注意的是,這些數據其實在 ZooKeeper 中也保存了一份。每當控制器初始化時,它都會從 ZooKeeper 上讀取對應的元數據並填充到本身的緩存中。有了這些數據,控制器就能對外提供數據服務了。這裏的對外主要是指對其餘 Broker 而言,控制器經過向這些Broker 發送請求的方式將這些數據同步到其餘 Broker 上。

控制器故障轉移(Failover)

咱們在前面強調過,在 Kafka 集羣運行過程當中,只能有一臺 Broker 充當控制器的角色,那麼這就存在單點失效(Single Point of Failure)的風險,Kafka 是如何應對單點失效的呢?答案就是,爲控制器提供故障轉移功能,也就是說所謂的 Failover。

故障轉移指的是,當運行中的控制器忽然宕機或意外終止時,Kafka 可以快速地感知到,並當即啓用備用控制器來代替以前失敗的控制器。這個過程就被稱爲 Failover,該過程是自動完成的,無需你手動干預。

接下來,咱們一塊兒來看一張圖,它簡單地展現了控制器故障轉移的過程。

1240

最開始時,Broker 0 是控制器。當 Broker 0 宕機後,ZooKeeper 經過 Watch 機制感知到並刪除了 /controller 臨時節點。以後,全部存活的 Broker 開始競選新的控制器身份。Broker 3 最終贏得了選舉,成功地在 ZooKeeper 上重建了 /controller 節點。以後,Broker 3 會從 ZooKeeper 中讀取集羣元數據信息,並初始化到本身的緩存中。至此,控制器的 Failover 完成,能夠行使正常的工做職責了。

控制器內部設計原理

在 Kafka 0.11 版本以前,控制器的設計是至關繁瑣的,代碼更是有些混亂,這就致使社區中不少控制器方面的 Bug 都沒法修復。控制器是多線程的設計,會在內部建立不少個線程。好比,控制器須要爲每一個 Broker 都建立一個對應的 Socket 鏈接,而後再建立一個專屬的線程,用於向這些 Broker 發送特定請求。若是集羣中的 Broker 數量不少,那麼控制器端須要建立的線程就會不少。另外,控制器鏈接 ZooKeeper 的會話,也會建立單獨的線程來處理 Watch 機制的通知回調。除了以上這些線程,控制器還會爲主題刪除建立額外的I/O 線程。

比起多線程的設計,更糟糕的是,這些線程還會訪問共享的控制器緩存數據。咱們都知道,多線程訪問共享可變數據是維持線程安全最大的難題。爲了保護數據安全性,控制器不得不在代碼中大量使用ReentrantLock 同步機制,這就進一步拖慢了整個控制器的處理速度。

鑑於這些緣由,社區於 0.11 版本重構了控制器的底層設計,最大的改進就是,把多線程的方案改爲了單線程加事件隊列的方案。我直接使用社區的一張圖來講明。

1240

從這張圖中,咱們能夠看到,社區引入了一個事件處理線程,統一處理各類控制器事件,而後控制器將原來執行的操做所有建模成一個個獨立的事件,發送到專屬的事件隊列中,供此線程消費。這就是所謂的單線程 + 隊列的實現方式。

值得注意的是,這裏的單線程不表明以前提到的全部線程都被「幹掉」了,控制器只是把緩存狀態變動方面的工做委託給了這個線程而已。

這個方案的最大好處在於,控制器緩存中保存的狀態只被一個線程處理,所以再也不須要重量級的線程同步機制來維護線程安全,Kafka 不用再擔憂多線程併發訪問的問題,很是利於社區定位和診斷控制器的各類問題。事實上,自 0.11 版本重構控制器代碼後,社區關於控制器方面的 Bug 明顯少多了,這也說明了這種方案是有效的。

針對控制器的第二個改進就是,將以前同步操做 ZooKeeper 所有改成異步操做。ZooKeeper 自己的 API 提供了同步寫和異步寫兩種方式。以前控制器操做 ZooKeeper 使用的是同步的 API,性能不好,集中表現爲,當有大量主題分區發生變動時,ZooKeeper容易成爲系統的瓶頸。新版本 Kafka 修改了這部分設計,徹底摒棄了以前的同步 API 調用,轉而採用異步 API 寫入 ZooKeeper,性能有了很大的提高。根據社區的測試,改爲異步以後,ZooKeeper 寫入提高了 10 倍!

除了以上這些,社區最近又發佈了一個重大的改進!以前 Broker 對接收的全部請求都是一視同仁的,不會區別對待。這種設計對於控制器發送的請求很是不公平,由於這類請求應該有更高的優先級。

舉個簡單的例子,假設咱們刪除了某個主題,那麼控制器就會給該主題全部副本所在的Broker 發送一個名爲StopReplica的請求。若是此時 Broker 上存有大量積壓的 Produce請求,那麼這個 StopReplica 請求只能排隊等。若是這些 Produce 請求就是要向該主題發送消息的話,這就顯得很諷刺了:主題都要被刪除了,處理這些 Produce 請求還有意義嗎?此時最合理的處理順序應該是,賦予 StopReplica 請求更高的優先級,使它可以獲得搶佔式的處理

這在 2.2 版本以前是作不到的。不過自 2.2 開始,Kafka 正式支持這種不一樣優先級請求的處理。簡單來講,Kafka 將控制器發送的請求與普通數據類請求分開,實現了控制器請求單獨處理的邏輯。鑑於這個改進仍是很新的功能,具體的效果咱們就拭目以待吧。

總結

好了,有關 Kafka 控制器的內容,我已經講完了。最後,我再跟你分享一個小竅門。當你以爲控制器組件出現問題時,好比主題沒法刪除了,或者重分區 hang 住了,你不用重啓Kafka Broker 或控制器。有一個簡單快速的方式是,去 ZooKeeper 中手動刪除/controller 節點。具體命令是 rmr /controller。這樣作的好處是,既能夠引起控制器的重選舉,又能夠避免重啓 Broker 致使的消息處理中斷。

相關文章
相關標籤/搜索