引言
最近冒著變禿的危險學習kafka,看起我的小視頻,記起我的小筆記~在此之前,只是聽說過kafka,并沒有系統的學習過。如果是小白,可以進來和我一起摸索;如果是大佬,歡迎復習并糾正我的錯誤~有關分布式和消息隊列共性的一點理論這里就不展示了,主要是看kafka相關的特性和知識。
文章導讀
Kafka基礎概述(設計目標,架構,概念)
Kafka的高可用機制(數據備份,ISR,Commit,fail-over容錯機制)
Kafka的Leader Election
一、Kafka基礎概述
Kafka是一種高性能跨語言的分布式發布/訂閱消息系統。
1.1 Kafka的設計目標
高吞吐率
配置良好的Kafka集群甚至可以做到每秒幾十萬、上百萬的超高并發寫入。Kafka在大數據領域比較有用武之地,學習Flink的時候必然離不開Kafka。這么牛的性能其實也依賴于順序讀寫,零拷貝,充分利用頁緩存,批處理和壓縮等。這些我在后續都會分析。
消息持久化
所有消息均被持久化到磁盤,無消息丟失,支持消息重放。
完全分布式
支持消息分區以及分布式消費,并保證分區內的消息順序,還支持Producer,Broker,Consumer的水平擴展(這些概念后續會寫,這里先混個眼熟)。
數據處理
同時滿足適應在線流處理和離線批處理。
1.2 Kafka的架構
可以看出,Kafka的幾個角色,其中Broker就是Kafka的Server,在這幅圖中暫且不研究Broker里面的細節,從全局角度,能得到下面幾個結論:
1.Producer將消息Push給Broker,而Consumer是以主動Pull的方式獲取消息,而不是Broker主動Push給Consumer。
2.Producer并沒有通過Zookeeper來獲取集群的信息。注:0.8.2.1之后以這種架構方式。
3.Consumer依賴于Zookeeper來感知集群信息。
4.Broker與Broker之間的通信依賴于Zookeeper。
下面主要根據這幾個結論解答幾個小疑問。
Q1:為什么要設計成Consumer主動Pull消息的模式?
主要是兩個,Broker不需要感知到哪些Consumer存在;如果是以Push的方式就可能導致應用程序處理不過來,延遲甚至數據丟失。
Q2:Producer如何感知Broker集群的存在的?
0.8版本后,Producer不需要依賴Zookeeper獲取Broker集群的元信息。通過IP+Port的方式,只要能連上一個Kafka,就能獲得整個集群的信息。
其中,Producer獲取Broker集群元信息的方法有:
Producer發送消息失敗后,主動觸發刷新集群元信息;
周期性地刷新緩存元信息(可配置)。
Q3:Producer將消息分發給Broker的策略?
通過指定topic和partition來發送消息。
1.3 Kafka的基本概念
Topic
1.邏輯概念,同一個Topic的消息可以分布在多個Broker(節點)上。
2.一個Topic包含一個或者多個Partition。
3.每條消息僅屬于一個Topic。
4.Producer發布數據時,必須指定將該消息發布到哪一個Topic。
5.Consumer訂閱消息時,也必須指定訂閱哪個Topic的消息。
注:為了便于理解,這里將replica-factor設置成1。
Partition
?圖中描述了一個寫的而過程,可以看出,數據是以Segment為單位存儲在Partition中的,每次都以追加的方式插入。
1.物理概念,一個Partition只分布于一個Broker上(不考慮備份)。
注:這很重要,也是Broker id能代替Leader id的原因。
2.一個Partition物理上對應一個文件夾。
3.一個Partition包含多個Segment,一個Segment對應一個文件。Segment由一個個不可變記錄組成。Segment對用戶是透明的,用戶并不需要感知Segment的存在。
4.記錄只會被append到Segment中,不會被單獨刪除或者修改。刪除的話是刪除整個Segment。
5.清除過期日志時,直接刪除一個或多個Segment。
Broker就是一個Kafka節點。可以類比為一個Broker就是一個微信號,微信號里的微信群就像是一個個Topic,Partition就是微信群中的人。都由Broker來做同一的管理。由Broker來接收Consumer和Producer的請求,并把消息持久化到本地磁盤。
Offset消息在Partition中的編號,編號順序不跨Partition。
Replication是消息的備份。Kafka是以Partition為單位對消息進行冗余備份的,某個Replica就是基于某個Partition的備份,每個Partition至少有一個備份。
Leader每個Replication集合中的Partition都會選出一個唯一的Leader,所有的讀寫請求都由leader處理。其他Replica從Leader處把請求數據更新同步到本地。
Producer按消息真正發送的時機來劃分,有兩種Producer,Sync Producer和Aync Producer。
Sync Producer(同步)低延遲,低吞吐率,無數據丟失。發送消息失敗后,進行重試,幾次重試失敗可以將消息放到磁盤。
Aync Producer(異步)高延遲,高吞吐率,有數據丟失。調用后將消息放入隊列,后臺線程從隊列中獲取消息,批量發送給對應的broker如果隊列滿了并且阻塞超過了一定的時間,kafka會將新的數據直接丟掉。
二、Kafka的高可用機制
2.1 數據備份
首先來看一下Kafka是如何通過Replica做數據備份的?
1.當某個Topic的replication-factor為N且N>1時,每個Partition都會有N個副本。
2.Replica的個數必須小于等于Broker數,否則會報錯。即對每個Partition而言,每個Broker上最多只會有一個Replic,因此可用Broker ID表示某個Replica。
3.所有Partition的所有Replica默認情況會均勻分配到所有Broker上。
可以通過一張圖來直觀地感受數據備份的過程:
大概過程是:數據先寫入某個Leader,然后(由Follower周期性的發起復制)再復制到各個Follower中去。讀取時,從Leader處讀取。
不過,光有上面的過程還不夠,需要一種機制去解決Follower和Leader的延遲問題,以及Leader宕機后對Follower的選舉策略,那么,Kafka是如何保證數據最終的一致性的?
2.2 Commit機制
為了保障數據一致性,就需要Commit機制來打輔助。Kafka規定,只有Commit過的數據,Consumer才能讀取。這個步驟就相當于Leader告訴Producer客戶端寫成功了,數據被Commit了。只要被Commit的數據就說明有備份了。
對于Commit的數據,如果使用同步,則不會有消息丟失,一致性高,但會使可用性較差;如果使用異步,則可用性較高但消息丟失概率增大。
于是乎,Kafka采用了折衷的辦法,使用ISR機制。
ISR(In-Sync Replica)機制
這里先解釋一下幾個概念,分區中的所有副本統稱為AR(Assigned Repllicas)。ISR是AR的子集。三句話解釋ISR:
每一個Leader都會維護一個與其基本保持同步的Replica列表,稱為ISR列表。
如果一個Follower比一個Leader落后太多,或者超過一定時間未發起數據復制請求,則leader將其從ISR中移除。
當ISR中所有Replica都向Leader發送ACK時,Leader就Commit。
Commit的具體策略
Commit的具體策略是可配置的,具體可同過Server,Topic,Producer3種角度如下配置:
Server
replica.lag.time.max.ms=10000
replica.lag.max.messages=4000
解釋:超過10秒或滯后4000條數據時,將此Replica移除ISR。
Topic
min.insync.replicas=1
解釋:ISR中的元素最少為1個。
Producer
request.required.acks=0
解釋:默認為0時,異步,不需要Leader等待ACK就返回。為1時,同步,Leader必須等待ACK返回。為-1時,才會觸發ISR機制 。
2.3 Kafka的fail-over的過程
?fail-over就是一種常見的容錯機制之一,意為失效轉移。見上圖,最典型的就是Leader宕機,大概過程是:
1.m3在Leader中,還未Commit。
2.Leader宕機,此時選舉B為Leader(具體選舉過程下一章會講,默認應該是ISR中第一個Replica)。
3.存活的B,C繼續接收消息同步m4,m5。
4.A回歸集群,執行它宕機時的ISR。
5.A執行B中維護的ISR,保持最終的一致性。
此時,問題來了,丟失的m3怎么辦?這個消息只能靠Producer Retry。超過固定次數失敗后,就真的丟失了。
此時萌生一個恐怖的想法,如果Replica全部宕機,會怎么樣?對于Kafka來說,有兩種策略:
等待ISR中任意Replica恢復,并選舉它為Leader。缺點:等待時間較長,降低可用性。如果ISR中所有Replica都無法恢復或者數據丟失,則該Partition將永不可用。優點:保證數據的完整性。
選擇第一個恢復的Replica為新的Leader,無論它是否在ISR中。缺點:并未包含所有已被之前Leader Commit過的消息,因此會造成數據丟失。優點:可用性高。
注:默認配置是選第一個恢復的Replica為新的Leader。
三、Kafka的Leader Election
我們知道,Kafka中每一種Partition都由Replica構成,可以理解為Replica=Leader+Follower。
所以Leader的選舉有一個很容易象到的辦法,就是以Partition為一個維度,每個Partition的多個Replica同時競爭Leader。意思是由Replica競爭,最終選一個Leader,但是一個Partition中的那么多Replica都參與選舉,勢必會加重Zookeeper的負載。導致每一次的選舉“粒度”都很高,延遲也較大。所以應該想一個延遲低,效率高,負載均衡,實時的更好的方法。
下面要說的,也是Kafka中的選舉策略,基于Controller的Leader Election。
按我的理解來說,解決的思想其實就是“整合”。整個集群中選舉出一個Broker作為Controller Leader。把Leader Election的實現細節都交給這個Controller Leader完成,讓它為所有Topic的所有Partition指定Leader及Follower,并且當ISR列表發生變化時,Controller通知集群中所有Broker更新對應的緩存。Controller與Leader和Follower之間借助Zookeeper,通過RPC通信。
選舉的過程:
前提:
Broker啟動時,都會在Zookeeper路徑下創建臨時節點(/controller節點),只有第一個成功創建節點的Controller才是真正的Controller,并且在節點中寫入當前Broker的信息。包括version,brokerid,timestamp。
當Controller故障后,利用Zookeeper的強一致性,所有的Broker都會監聽該節點的變化,并且收到通知,再次競爭在該路徑下創建節點,從而選舉新的controller。
(1)Controller在Zookeeper中注冊watch,一旦有Broker宕機。Broker在Zookeeper上的znode就會被自動刪除。Zookeeper會fire Controller注冊的watch,意思就是watch在fire之后會被取消,不會再關注該節點的變化。Controller讀取最新的幸存Broker的信息。
(2)Controller整理一個Set集合,這個集合包含了宕機的所有Broker上的所有Partition(也就是Partition上所有的Replica)。
(3)對Set中的每一個Partition,讀取其當前的ISR。決定該Partition的新Leader。如果ISR中有一個Replica還幸存,就選擇其中一個作為新Leader,新的ISR包含當前所有幸存的Replica。如果Partition的所有Replica都宕機,則將新Leader的id(對應于源碼中的controllerId)設置為-1。此時具體的選舉策略有兩種,上一章說過,即所有Replica宕機的情況。
(4)將新的Leader,ISR和新的leader_epoch及controller_epoch(舊的+1)寫入新的controller節點的state。注:leader_epoch和controller_epoch是Zookeeper中的概念,利用這個值來確定是否為最新的Leader或Controller,因為每次選舉都會加1。
(5)直接通過RPC向Set相關的Broker發送LeaderAndISRRequest命令。這個過程其實就是告知他們新的Leader都是誰,要聽從它的指揮。
最后
學習Kafka可能需要一些分布式的基礎,以后有機會寫一些。下一篇寫Kafka的Consumer以及高性能的實現原理。
如果感覺寫的還OK不妨點個贊~
參考文章:kafka leader選舉機制原理