Kafka 日誌存儲及其清除策略

日誌存儲結構

Kafka存儲結構圖:

img

kafka 中消息是以主題 topic 爲基本單位進行歸類的,這裏的 topic 是邏輯上的概念,實際上在磁盤存儲是根據分區存儲的,每個主題可以分爲多個分區、分區的數量可以在主題創建的時候進行指定。例如下面 kafka 命令創建了一個 topic 爲 test 的主題、該主題下有 4 個分區、每個分區有兩個副本保證高可用。

./bin/kafka-topics.sh --create --zookeeper 127.0.0.1:2181 --replication-factor 2 --partitions 4 --topic test

分區的修改除了在創建的時候指定。還可以動態的修改。如下將 kafka 的 test 主題分區數修改爲 12 個

./kafka-topics.sh --alter --zookeeper 127.0.0.1:2181 --topic test --partitions 12

分區內每條消息都會被分配一個唯一的消息 id,也就是我們通常所說的 offset, 因此 kafak 只能保證每一個分區內部有序性,不能保證全局有序性。

如果分區設置的合理,那麼所有的消息都可以均勻的分佈到不同的分區中去,這樣可以實現水平擴展。不考慮多副本的情況下,一個分區對應一個 log 日誌、如上圖所示。爲了防止 log 日誌過大,kafka 又引入了日誌分段(LogSegment)的概念,將 log 切分爲多個 LogSegement,相當於一個巨型文件被平均分配爲相對較小的文件,這樣也便於消息的維護和清理。事實上,Log 和 LogSegement 也不是純粹物理意義上的概念,Log 在物理上只是以文件夾的形式存儲,而每個 LogSegement 對應於磁盤上的一個日誌文件和兩個索引文件,以及可能的其他文件(比如以".txindex"爲後綴的事務索引文件)。

kafak 中的 Log 對應了一個命名爲<topic>-<partition> 的文件夾。舉個例子、假如有一個 test 主題,此主題下游 3 個分區,那麼在實際物理上的存儲就是 "test-0","test-1","test-2" 這三個文件夾。

向 Log 中寫入消息是順序寫入的。只有最後一個 LogSegement 才能執行寫入操作,在此之前的所有 LogSegement 都不能執行寫入操作。爲了方便描述,我們將最後一個 LogSegement 成爲"ActiveSegement",即表示當前活躍的日誌分段。隨着消息的不斷寫入,當 ActiveSegement 滿足一定的條件時,就需要創建新的 activeSegement,之後在追加的消息寫入新的 activeSegement。

img

爲了便於消息的檢索,每個 LogSegement 中的日誌文件(以".log" 爲文件後綴)都有對應的兩個文件索引:偏移量索引文件(以".index" 爲文件後綴)和時間戳索引文件(以".timeindex"爲文件後綴)。每個 LogSegement 都有一個“基準偏移量” baseOffset,用來標識當前 LogSegement 中第一條消息的 offset。偏移量是一個 64 位的長整形。日誌文件和兩個索引文件都是根據基準偏移量(baseOffset)命名的,名稱固定爲 20 位數字,沒有達到的位數則用 0 填充。比如第一個 LogSegment 的基準偏移量爲 0,對應的日誌文件爲 00000000000000000000.log

img

示例中第 2 個 LogSegment 對應的基準位移是 256,也說明了該 LogSegment 中的第一條消息的偏移量爲 256,同時可以反映出第一個 LogSegment 中共有 256 條消息(偏移量從 0 至 255 的消息)。

注意每個 LogSegment 中不只包含“.log”“.index”“.timeindex”這 3 種文件,還可能包含“.deleted”“.cleaned”“.swap”等臨時文件,以及可能的“.snapshot”“.txnindex”“leader-epoch-checkpoint”等文件。

日誌索引

kafka的日誌文件索引是用來快速檢索日誌的,在kafka中日誌索引分爲2種類,kafka中索引以稀疏索引的方式構建索引,它不保證每個消息在索引文件中都存在索引。

每當寫入一定數量log.index.interval.bytes default(4KB = 4096)的時候,偏移量索引以及時間戳索引各自創建一個對應的索引項,我們可以通過該參數調整索引的密度,通過MappedByteBuffer將索引文件映射到內存中。

日誌分段文件切分條件如下

  • 當日志分段文件的大小超過log.segment.bytes=1073741824(1GB)時;
  • 當日志分段中的最大時間戳與當前系統的差值大於log.roll.ms或log.roll.hours,默認只配置了log.roll.hours =168(7天),前者優先級高
  • 偏移量索引文件或者時間戳索引文件大小超過brokerlog.index.size.max.bytes=10MB
  • 新追加消息的offset-baseOffset > Integer.MAX_VALUE時,也就是相對位移過大,用Integer-4個字節存不下了。

offset索引

offset索引格式

偏移量索引分爲2個部分,總共佔8個字節

具體的偏移量索引項如下圖

在這裏插入圖片描述

  • relativeOffset(4B)
    • 消息的相對偏移量,即offset - baseOffset,其中baseOffset爲整個segmentLogFile的起始消息的offset。
    • 平常的offset佔用8個Byte,而ralativeOffset只需要佔用4個Byte
  • position(4B)
    • 物理地址,也就是日誌在分段日誌文件中的實際位置。

查看日誌以及索引文件的方式

#以下2種方式都行
bin/kafka-run-class.sh kafka.tools.DumpLogSegments --files  /cxxxxxx.log
bin/kafka-dump-log.sh --files /xxxxx.index

>>>>>>
(base) bogon:topic1-0 shufang$ kafka-dump-log.sh --files 00000000000000000000.index 
Dumping 00000000000000000000.index
offset: 45 position: 4140  #代表團一個RecordBatch
offset: 90 position: 8266  #代表另一個RecordBatch

offset索引的檢索流程

假如有以下索引文件與分段日誌文件,我們該如何找到偏移量爲23的消息數據?

在這裏插入圖片描述

  • 首先通過二分法找到不大於23的最大偏移量索引【22,656】;
  • 然後從position(656)開始順序查找偏移量爲23的消息。

注意⚠️:log.index.size.max.bytes必須是8的整數倍,如果你設置成67,那麼系統會默認幫你糾正成64。

時間戳索引

時間戳索引格式

時間戳索引分爲2部分,公佔12個字節,具體索引樣式如下

在這裏插入圖片描述

  • timestamp
    • 當前日誌分段文件中建立索引的消息的時間戳
    • 爲了保證時間戳大的單調遞增,我們可以將log.message.timestamp.type設置成logApendTime,而CreateTime不能保證
  • relativeoffset
    • 時間戳對應消息的相對偏移量

具體時間戳索引的檢索流程

在這裏插入圖片描述

  • 首先在時間戳索引文件中找到不大於該時間戳的最大時間戳對應的最大索引項【1526384718283,28】;
  • 然後在偏移量索引文件中檢索不超過對應relativeoffset(28)的最大偏移量索引的項【26,838】;
  • 然後按照偏移量索引的檢索方式找到對應的具體消息。

注意⚠️:時間戳索引文件的大小必須爲12B的倍數。

Kafka消息格式

一文看懂Kafka消息格式的演變

日誌清除策略

Kafka將消息存儲在磁盤中,爲了控制磁盤佔用空間的不斷增加就需要對消息做一定的清理操作。Kafka中每一個分區partition都對應一個日誌文件,而日誌文件又可以分爲多個日誌分段文件,這樣也便於日誌的清理操作。Kafka提供了兩種日誌清理策略:

日誌刪除(Log Deletion):按照一定的保留策略來直接刪除不符合條件的日誌分段。
日誌壓縮(Log Compaction):針對每個消息的key進行整合,對於有相同key的的不同value值,只保留最後一個版本。

我們可以通過broker端參數log.cleanup.policy來設置日誌清理策略,此參數默認值爲delete,即採用日誌刪除的清理策略。如果要採用日誌壓縮的清理策略的話,就需要將log.cleanup.policy設置爲“compact”,並且還需要將log.cleaner.enable(默認值爲true)設定爲true。通過將log.cleanup.policy參數設置爲“delete,compact”還可以同時支持日誌刪除和日誌壓縮兩種策略。

日誌清理的粒度可以控制到topic級別,比如與log.cleanup.policy對應的主題級別的參數爲cleanup.policy,爲了簡化說明,本文只採用broker端參數做陳述,如若需要topic級別的參數可以查看官方文檔。

日誌刪除-Log Deletion

Kafka日誌管理器中會有一個專門的日誌刪除任務來週期性檢測和刪除不符合保留條件的日誌分段文件,這個週期可以通過broker端參數log.retention.check.interval.ms來配置,默認值爲300,000,即5分鐘。

基於日誌大小

日誌刪除任務會檢查當前日誌的大小是否超過設定的閾值retentionSize來尋找可刪除的日誌分段的文件集合deletableSegments

retentionSize可以通過broker端參數log.retention.bytes來配置,默認值爲-1,表示無窮大。注意log.retention.bytes配置的是日誌文件的總大小,而不是單個的日誌分段的大小,一個日誌文件包含多個日誌分段。

img

  • 先計算日誌文件的總大小size和retentionSize的差值diff,即計算需要刪除的日誌總大小

  • 然後從日誌文件中的第一個日誌分段開始進行查找可刪除的日誌分段的文件集合deletableSegments。

  • 查找出deletableSegments之後就執行刪除操作

    這個刪除操作和基於時間的保留策略的刪除操作相同,這裏不再贅述。

基於時間

日誌刪除任務會檢查當前日誌文件中是否有保留時間超過設定的閾值retentionMs來尋找可刪除的的日誌分段文件集合deletableSegments

retentionMs可以通過broker端參數log.retention.hours、log.retention.minutes以及log.retention.ms來配置,其中log.retention.ms的優先級最高,log.retention.minutes次之,log.retention.hours最低。

默認情況下只配置了log.retention.hours參數,其值爲168,故默認情況下日誌分段文件的保留時間爲7天。

img

Kafka會查找日誌段的時間戳索引文件中的最後一條記錄,如果最後一條largestTimeStamp小於0,則取最近修改時間lastModifiedTime。

使用時間戳largestTimeStamp,因爲最後修改時間lastModifiedTime可以被修改,比如touch,或分區副本重分配。

當確認完需要刪除的日誌段以後,需要進行以下刪除操作:

  1. 從日誌對象中所維護日誌分段的跳躍表ConcurrentSkipListMap中移除待刪除的日誌分段,保證沒有線程對這些日誌分段進行讀取操作。
  2. 爲日誌段中的所有文件加上.delete後綴,也包含日誌分段對應的索引文件。
  3. Kafka中會有一個命名爲"delete-file"的延遲任務來刪除這些無效的日誌數據,該任務延遲執行時間可以通過file.delete.delay.ms參數來設置,默認值爲60000,即1分鐘

假如當前日誌段中也有需要刪除的數據,那麼kafka會先進行分段,創建一個新的活躍日誌段,然後執行刪除操作。

基於日誌起始偏移量

一般情況下日誌文件的起始偏移量logStartOffset等於第一個日誌分段的baseOffset,但是這並不是絕對的,logStartOffset的值可以通過DeleteRecordsRequest請求、日誌的清理和截斷等操作修改。

img

刪除策略:

某日誌分段的下一個日誌分段的baseOffset是否小於等於logStartOffset,若是則可以刪除此日誌分段。

參考上圖,假設logStartOffset等於25,日誌分段1的起始偏移量爲0,日誌分段2的起始偏移量爲11,日誌分段3的起始偏移爲23,那麼我們通過如下動作收集可刪除的日誌分段的文件集合deletableSegments:

  • 從頭開始遍歷每個日誌分段,日誌分段1的下一個日誌分段的起始偏移量爲11,小於logStartOffset的大小,將日誌分段1加入到deletableSegments中;
  • 日誌分段2的下一個日誌偏移量的起始偏移量爲23,也小於logStartOffset的大小,將日誌分段2頁加入到deletableSegments中;
  • 日誌分段3的下一個日誌偏移量的baseOffset爲30,在logStartOffset的右側,故從日誌分段3開始的所有日誌分段都不會被加入到deletableSegments中。
    收集完可刪除的日誌分段的文件集合之後的刪除操作同基於日誌大小的保留策略和基於時間的保留策略相同,這裏不再贅述。

Kafka日誌清理之Log Deletion

日誌壓縮-Log Compaction

topic __consumer_offsets 默認爲日誌壓縮策略

對於有相同key的的不同value值,只保留最後一個版本。

如果應用只關心key對應的最新value值,可以開啓Kafka的日誌清理功能,Kafka會定期將相同key的消息進行合併,只保留最新的value值。

img

Log Compaction 有如下特點:

  • messages的順序仍然是保留的,log compaction 僅移除一些messages,但不會重新對它們進行排序
  • 一條message的offset是無法改變的(immutable),如果一條message缺失,則offset會直接被跳過
  • 被刪除的records在一段時間內仍然可以被consumers訪問到,這段時間由參數delete.retention.ms(默認爲24小時)控制,日誌壓縮只有在一個segment被commit的時候執行。

Log Compaction執行過後的日誌分段的大小會比原先的日誌分段的要小,爲了防止出現太多的小文件,Kafka在實際清理過程中並不對單個的日誌分段進行單獨清理,而是會將日誌文件中offset從0至firstUncleanableOffset的所有日誌分段進行分組,每個日誌分段只屬於一組,分組策略爲:按照日誌分段的順序遍歷,每組中日誌分段的佔用空間大小之和不超過segmentSize(可以通過broker端參數log.segments.bytes設置,默認值爲1GB),且對應的索引文件佔用大小之和不超過maxIndexSize(可以通過broker端參數log.index.interval.bytes設置,默認值爲10MB)。同一個組的多個日誌分段清理過後,只會生成一個新的日誌分段。

img

參考上圖,假設所有的參數配置都爲默認值,在Log Compaction之前checkpoint的初始值爲0。執行第一次Log Compaction之後,每個非活躍的日誌分段的大小都有所縮減,checkpoint的值也有所變化。執行第二次Log Compaction時會將組隊成[0.4GB, 0.4GB]、[0.3GB, 0.7GB]、[0.3GB]、[1GB]這4個分組,並且從第二次Log Compaction開始還會涉及墓碑消息的清除。同理,第三次Log Compaction過後的情形可參考上圖尾部。Log Compaction過程中會將對每個日誌分組中需要保留的消息拷貝到一個以“.clean”爲後綴的臨時文件中,此臨時文件以當前日誌分組中第一個日誌分段的文件名命名,例如:00000000000000000000.log.clean。Log Compaction過後將“.clean”的文件修改爲以“.swap”後綴的文件,例如:00000000000000000000.log.swap,然後刪除掉原本的日誌文件,最後才把文件的“.swap”後綴去掉,整個過程中的索引文件的變換也是如此,至此一個完整Log Compaction操作纔算完成。

參考:

Kafka日誌清理之Log Compaction

kafka 日誌存儲以及清理機制

kafka-之分段日誌文件索引(偏移量索引、時間戳索引)

Apache Kafka(十二)Log Cleanup 策略

Kafka日誌清理

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