1.概述
Kafka 快速穩定的發展,得到越來越多開發者和使用者的青睞。它的流行得益於它底層的設計和操作簡單,儲存系統高效,以及充分利用磁碟順序讀寫等特性,和其實時線上的業務場景。對於Kafka來說,它是一個分散式的,可分割槽的,多副本,多訂閱者的,基於Zookeeper統一協調的分散式日誌系統。常見的可以用於系統日誌,業務日誌,訊息資料等。那今天筆者給大家分析Kafka的儲存機制和副本的相關內容。
2.Replication
Replication是Kafka的重要特性之一,針對其Kafka Brokers進行自動調優Replication數,是比較有難度的。原因之一在於要知道怎麼避免Follower進入和退出同步 ISR (In-Sync Replicas)。再訊息生產的過程當中,在有一大批海量資料寫入時,可能會引發Broker告警。如果某些Topic的部分Partition長期處於 “under replicated”,這樣是會增加丟失資料的機率的。Kafka 通過多副本機制實現高可用,確保當Kafka叢集中某一個Broker當機的情況下,仍然可用。而 Kafka 的複製演算法保證,如果Leader發生故障或者當機,一個新的Leader會被重新選舉出來,並對外提供服務,供客戶端寫入訊息。Kafka 在同步的副本列表中選舉一個副本為Leader。
在Topic中,每個分割槽有一個預寫式日誌檔案,每個分割槽都由一系列有序,不可變的訊息組成,這些訊息被連續的追加到分割槽中,分割槽中的每個訊息都包含一個連續的序列號,即:offset。它用於確定在分割槽中的唯一位置。如下圖所示:
在Kafka中,假如每個Topic的分割槽有N個副本,由於Kafka通過多副本機制實現故障自動轉移,這裡需要說明的是,當KafkaController出現故障,進而不能繼續管理叢集,則那些KafkaController Follower開始競選新的Leader,而啟動的過程則是在KafkaController的startup方法中完成的,如下所示:
def startup() = { inLock(controllerContext.controllerLock) { info("Controller starting up") registerSessionExpirationListener() isRunning = true controllerElector.startup info("Controller startup complete") } }
然後啟動ZookeeperLeaderElector,在建立臨時節點,進行session檢查,更新leaderId等操作完成後,會呼叫故障轉移函式onBecomingLeader,也就是KafkaController中的onControllerFailover方法,如下所示:
def onControllerFailover() { if(isRunning) { info("Broker %d starting become controller state transition".format(config.brokerId)) readControllerEpochFromZookeeper() incrementControllerEpoch(zkUtils.zkClient) // before reading source of truth from zookeeper, register the listeners to get broker/topic callbacks registerReassignedPartitionsListener() registerIsrChangeNotificationListener() registerPreferredReplicaElectionListener() partitionStateMachine.registerListeners() replicaStateMachine.registerListeners() initializeControllerContext() // We need to send UpdateMetadataRequest after the controller context is initialized and before the state machines // are started. The is because brokers need to receive the list of live brokers from UpdateMetadataRequest before // they can process the LeaderAndIsrRequests that are generated by replicaStateMachine.startup() and // partitionStateMachine.startup(). sendUpdateMetadataRequest(controllerContext.liveOrShuttingDownBrokerIds.toSeq) replicaStateMachine.startup() partitionStateMachine.startup() // register the partition change listeners for all existing topics on failover controllerContext.allTopics.foreach(topic => partitionStateMachine.registerPartitionChangeListener(topic)) info("Broker %d is ready to serve as the new controller with epoch %d".format(config.brokerId, epoch)) maybeTriggerPartitionReassignment() maybeTriggerPreferredReplicaElection() if (config.autoLeaderRebalanceEnable) { info("starting the partition rebalance scheduler") autoRebalanceScheduler.startup() autoRebalanceScheduler.schedule("partition-rebalance-thread", checkAndTriggerPartitionRebalance, 5, config.leaderImbalanceCheckIntervalSeconds.toLong, TimeUnit.SECONDS) } deleteTopicManager.start() } else info("Controller has been shut down, aborting startup/failover") }
正因為有這樣的機制存在,所示當Kafka叢集中的某個Broker當機後,仍然保證服務是可用的。在Kafka中發生複製操作時,確保分割槽的預寫式日誌有序的寫到其他節點,在N個複製因子中,其中一個複製因子角色為Leader,那麼其他複製因子的角色則為Follower,Leader處理分割槽的所有讀寫請求,同時,Follower會被動的定期去複製Leader上的資料。以上分析可以總結為以下幾點,如下所示:
- Leader負責處理分割槽的所有讀寫請求。
- Follower會複製Leader上資料。
- Kafka 的故障自動轉移確保服務的高可用。
3.儲存
對於訊息對應的效能評估,其檔案儲存機制設計是衡量的關鍵指標之一,在分析Kafka的儲存機制之前,我們先了解Kafka的一些概念:
- Broker:Kafka訊息中介軟體節點,一個節點代表一個Broker,多個Broker可以組建成Kafka Brokers,即:Kafka叢集。
- Topic:訊息儲存主題,即可以理解為業務資料名,Kafka Brokers能夠同時負責多個Topic的處理。
- Partition:針對於Topic來說的,一個Topic上可以有多個Partition,每個Partition上的資料是有序的。
- Segment:對於Partition更小粒度,一個Partition由多個Segment組成。
- Offset:每個Partition上都由一系列有序的,不可變的訊息組成,這些訊息被連續追加到Partition中。而在其中有一個連續的序列號offset,用於標識訊息的唯一性。
3.1 Topic儲存
在Kafka檔案儲存中,同一個Topic下有多個不同的Partition,每個Partition為一個單獨的目錄,Partition的命名規則為:Topic名稱+有序序號,第一個Partition序號從0開始,序號最大值等於Partition的數量減1,如下圖所示:
3.2 分割槽檔案儲存
每個分割槽相當於一個超大的檔案被均分到多個大小相等的Segment資料檔案中,但是每個Segment訊息數量不一定相等,正因為這種特性的存在,方便了Old Segment File快速被刪除。而對於每個分割槽只需要支援順序讀寫即可,Segment檔案生命週期由服務端配置的引數決定。這樣即可快速刪除無用資料檔案,有效提高磁碟利用率。
3.3 Segment檔案儲存
這裡,Segment檔案由Index File和Data File組成,檔案是一一對應的,字尾為 .index 表示索引檔案, .log 表示資料檔案,如下圖所示:
如上圖所示,Segment檔案命名規則由分割槽全域性第一個Segment從0開始,後續每一個Segment檔名為上一個Segment檔案最後一個訊息的Offset值。這裡Segment資料檔案由許多訊息組成,訊息物理結構如下所示:
Key | Describer |
offset | 用於標識每個分割槽中每條訊息的唯一性,Offset的數值標識該分割槽的第幾條訊息 |
message Size | 訊息大小 |
CRC32 | 用CRC32校驗訊息 |
“magic” | 當前釋出Kafka服務程式的協議版本號 |
“attribute” | 獨立版本,或標識壓縮型別,或者編碼型別 |
key length | key的長度 |
key | 可選 |
payload length | 實際訊息資料 |
3.4 分割槽中查詢訊息
在分割槽中,可以通過offset偏移量來查詢訊息,如上圖中,檔案00000000000046885905.index的訊息起始偏移量為46885906=46885905+1,其他檔案依此類推,以起始偏移量命名並排序這些檔案,這樣能夠快速的定位到具體的檔案。通過segment file,當offset為46885906時,我們可以定位到00000000000046885905.index後設資料物理位置和00000000000046885905.log物理偏移地址。
4.總結
通過對副本和儲存機制的分析,我們可以清楚的知道,Kafka通過自動故障轉移來確保服務的高可用,Leader負責分割槽的所有讀寫操作,Follower會複製Leader上的資料。Kafka針對Topic,使某一個分割槽中的大檔案分成多個小檔案,通過多個小的segment file,使之便捷定期清理或刪除已經消費的檔案,減少磁碟佔用。另外,通過索引檔案稀疏儲存,可以大幅度降低索引檔案後設資料所佔用的空間。
5.結束語
這篇部落格就和大家分享到這裡,如果大家在研究學習的過程當中有什麼問題,可以加群進行討論或傳送郵件給我,我會盡我所能為您解答,與君共勉。