kafka認知3

接上篇kafka文章

消息的存儲,消息的持久化

消息發送端發送消息到broker上以后,消息是如何持久化的呢?那么接下來去分析下消息的存儲首先我們需要了解的是,kafka是使用日志文件的方式來保存生產者和發送者的消息,每條消息都有一個offset值來表示它在分區中的偏移量。Kafka中存儲的一般都是海量的消息數據,為了避免日志文件過大,Log并不是直接對應在一個磁盤上的日志文件,而是對應磁盤上的一個目錄,這個目錄的命名規則是<topic_name>-<partition_id>

消息的文件存儲機制

一個topic的多個partition在物理磁盤上的保存路徑,路徑保存在 /tmp/kafka-logs/topic-partition,包含日志文件、索引文件和時間索引文件

image

kafka是通過分段的方式將Log分為多個LogSegment,LogSegment是一個邏輯上的概念,一個LogSegment對應磁盤上的一個日志文件和一個索引文件,其中日志文件是用來記錄消息的。索引文件是用來保存消息的索引。
segment的常用配置有:

#server.properties

#segment文件的大小,默認為 1G
log.segment.bytes=1024*1024*1024
#滾動生成新的segment文件的最大時長
log.roll.hours=24*7
#segment文件保留的最大時長,超時將被刪除
log.retention.hours=24*7

那么這個LogSegment是什么呢?

LogSegment

假設kafka以partition為最小存儲單位,那么我們可以想象當kafka producer不斷發送消息,必然會引起partition文件的無線擴張,這樣對于消息文件的維護以及被消費的消息的清理帶來非常大的挑戰,所以kafka 以segment為單位又把partition進行細分。每個partition相當于一個巨型文件被平均分配到多個大小相等的segment數據文件中(每個segment文件中的消息不一定相等),這種特性方便已經被消費的消息的清理,提高磁盤的利用率。

  • log.segment.bytes=107370 (設置分段大小),默認是1gb,我們把這個值調小以后,可以看到日志分段的效果

  • 抽取其中3個分段來進行分析

    image

    segment file由2大部分組成,分別為index file和data file,此2個文件一一對應,成對出現,后綴".index"和“.log”分別表示為segment索引文件、數據文件.
    segment文件命名規則:partion全局的第一個segment從0開始,后續每個segment文件名為上一個segment文件最后一條消息的offset值進行遞增。數值最大為64位long大小,20位數字字符長度,沒有數字用0填充

查看segment文件命名規則

通過下面這條命令可以看到kafka消息日志的內容,注意grep必須加-a參數

grep -a 'logId' 00000000000000000000.log

假如第一個log文件的最后一個offset為:5376,所以下一個segment的文件命名為:
00000000000000005376.log。對應的index為00000000000000005376.index

segment中index和log的對應關系

從所有分段中,找一個分段進行分析
為了提高查找消息的性能,為每一個日志文件添加2個索引索引文件:OffsetIndex 和 TimeIndex,分別對應.index以及.timeindex, TimeIndex索引文件格式:它是映射時間戳和相對offset

查看索引內容:

sh kafka-run-class.sh kafka.tools.DumpLogSegments --files /tmp/kafka-logs/test-0/00000000000000000000.index --print-data-log

展示如下

offset: 4561 position: 683858
offset: 4566 position: 688769
offset: 4573 position: 693871
offset: 4578 position: 700261
offset: 4583 position: 704568
offset: 4586 position: 714114
offset: 4591 position: 720123
offset: 4594 position: 727926
offset: 4601 position: 733000
offset: 4603 position: 742220
offset: 4605 position: 753894
offset: 4607 position: 764212
offset: 4609 position: 771133
offset: 4614 position: 776029
offset: 4617 position: 780633
offset: 4622 position: 785519
offset: 4628 position: 796098
offset: 4633 position: 1198989
offset: 4637 position: 1204712

index采用稀疏存儲的方式,它不會為每一條message都建立索引,而是每隔一定的字節數建立一條索引,避免索引文件占用過多的空間。缺點是沒有建立索引的offset不能一次定位到message的位置,需要做一次順序掃描,但是掃描的范圍很小。

image

如圖所示,.index文件中存儲了索引以及物理偏移量(position),.log文件存儲了消息的內容

索引包含兩個部分(均為4個字節的數字),分別為相對offset和position。相對offset表示segment文件中的offset,其實就是消息的唯一標識,同一個partition內的消息offset是唯一的,position表示在消息在.log文件中在數據文件中的位置,其實是消息真實的物理偏移地址。

Kafka采用整數值position記錄單個分區的消費狀態,當消費成功broker收到確認,position指向下一個offset。 由于消息一定時間內不清除,那么可以重置offset來重新消費消息。

在partition中如何通過offset查找message

查找的算法是

  1. 根據offset的值,查找segment段中的index索引文件。由于索引文件命名是以上一個文件的最后一個offset+1進行命名的,所以,使用二分查找算法能夠根據offset快速定位到指定的索引文件。
  2. 找到索引文件后,根據offset進行定位,找到索引文件中的符合范圍的索引。(kafka采用稀疏索引的方式來提高查找性能)
  3. 得到position以后,再到對應的log文件中,從position出開始查找offset對應的消息,將每條消息的offset與目標offset進行比較,直到找到消息

比如說,我們要查找offset=2490這條消息,那么先找到00000000000000000000.index, 然后找到[2487,49111]這個索引,再到log文件中,根據49111這個position開始查找,比較每條消息的offset是否大于等于2490。最后查找到對應的消息以后返回

Log文件的消息內容分析

sh kafka-run-class.sh kafka.tools.DumpLogSegments --files /tmp/kafka-logs/test-0/00000000000000000000.log --print-data-log | grep position

前面我們通過kafka提供的命令,可以查看二進制的日志文件信息,一條消息,會包含很多的字段。

offset: 5371 position: 102124 CreateTime: 1531477349286 isvalid: true keysize:
-1 valuesize: 12 magic: 2 compresscodec: NONE producerId: -1 producerEpoch: -1
sequence: -1 isTransactional: false headerKeys: [] payload: message_5371

offset和position這兩個前面已經講過了、 createTime表示創建時間、keysize和valuesize表示key和value的大小、 compresscodec表示壓縮編碼、payload:表示消息的具體內容

kafka提供的命令的參數

sh kafka-run-class.sh kafka.tools.DumpLogSegments
Parse a log file and dump its contents to the console, useful for debugging a seemingly corrupt log segment.
Option                               Description
------                               -----------
--deep-iteration                     if set, uses deep instead of shallow
                                       iteration.
--files <String: file1, file2, ...>  REQUIRED: The comma separated list of data
                                       and index log files to be dumped.
--help                               Print usage information.
--index-sanity-check                 if set, just checks the index sanity
                                       without printing its content. This is
                                       the same check that is executed on
                                       broker startup to determine if an index
                                       needs rebuilding or not.
--key-decoder-class [String]         if set, used to deserialize the keys. This
                                       class should implement kafka.serializer.
                                       Decoder trait. Custom jar should be
                                       available in kafka/libs directory.
                                       (default: kafka.serializer.StringDecoder)
--max-message-size <Integer: size>   Size of largest message. (default: 5242880)
--offsets-decoder                    if set, log data will be parsed as offset
                                       data from the __consumer_offsets topic.
--print-data-log                     if set, printing the messages content when
                                       dumping data logs. Automatically set if
                                       any decoder option is specified.
--transaction-log-decoder            if set, log data will be parsed as
                                       transaction metadata from the
                                       __transaction_state topic.
--value-decoder-class [String]       if set, used to deserialize the messages.
                                       This class should implement kafka.
                                       serializer.Decoder trait. Custom jar
                                       should be available in kafka/libs
                                       directory. (default: kafka.serializer.
                                       StringDecoder)
--verify-index-only                  if set, just verify the index log without
                                       printing its content.

日志的清除策略以及壓縮策略

日志清除策略

前面提到過,日志的分段存儲,一方面能夠減少單個文件內容的大小,另一方面,方便kafka進行日志清理。日志的清理策略有兩個:

  1. 根據消息的保留時間,當消息在kafka中保存的時間超過了指定的時間,就會觸發清理過程
  2. 根據topic存儲的數據大小,當topic所占的日志文件大小大于一定的閥值,則可以開始刪除最舊的消息。kafka會啟動一個后臺線程,定期檢查是否存在可以刪除的消息

通過log.retention.bytes和log.retention.hours這兩個參數來設置,當其中任意一個達到要求,都會執行刪除。
默認的保留時間是:7天

日志壓縮策略

Kafka還提供了“日志壓縮(Log Compaction)”功能,通過這個功能可以有效的減少日志文件的大小,緩解磁盤緊張的情況,在很多實際場景中,消息的key和value的值之間的對應關系是不斷變化的,就像數據庫中的數據會不斷被修改一樣,消費者只關心key對應的最新的value。因此,我們可以開啟kafka的日志壓縮功能,服務端會在后臺啟動啟動Cleaner線程池,定期將相同的key進行合并,只保留最新的value值。日志的壓縮原理是

image

磁盤存儲的性能問題

磁盤存儲的性能優化

我們現在大部分企業仍然用的是機械結構的磁盤,如果把消息以隨機的方式寫入到磁盤,那么磁盤首先要做的就是尋址,也就是定位到數據所在的物理地址,在磁盤上就要找到對應的柱面、磁頭以及對應的扇區;這個過程相對內存來說會消耗大量時間,為了規避隨機讀寫帶來的時間消耗,kafka采用順序寫的方式存儲數據。即使是這樣,但是頻繁的I/O操作仍然會造成磁盤的性能瓶頸

零拷貝

消息從發送到落地保存,broker維護的消息日志本身就是文件目錄,每個文件都是二進制保存,生產者和消費者使用相同的格式來處理。在消費者獲取消息時,服務器先從硬盤讀取數據到內存,然后把內存中的數據原封不動的通過socket發送給消費者。雖然這個操作描述起來很簡單,但實際上經歷了很多步驟。

操作系統將數據從磁盤讀入到內核空間的頁緩存:
? 應用程序將數據從內核空間讀入到用戶空間緩存中
? 應用程序將數據寫回到內核空間到socket緩存中
? 操作系統將數據從socket緩沖區復制到網卡緩沖區,以便將數據經網絡發出

image

通過“零拷貝”技術,可以去掉這些沒必要的數據復制操作,同時也會減少上下文切換次數。現代的unix操作系統提供一個優化的代碼路徑,用于將數據從頁緩存傳輸到socket;在Linux中,是通過sendfile系統調用來完成的。Java提供了訪問這個系統調用的方法:FileChannel.transferTo API
使用sendfile,只需要一次拷貝就行,允許操作系統將數據直接從頁緩存發送到網絡上。所以在這個優化的路徑中,只有最后一步將數據拷貝到網卡緩存中是需要的

image

頁緩存

頁緩存是操作系統實現的一種主要的磁盤緩存,但凡設計到緩存的,基本都是為了提升i/o性能,所以頁緩存是用來減少磁盤I/O操作的。
磁盤高速緩存有兩個重要因素:
第一,訪問磁盤的速度要遠低于訪問內存的速度,若從處理器L1和L2高速緩存訪問則速度更快。
第二,數據一旦被訪問,就很有可能短時間內再次訪問。正是由于基于訪問內存比磁盤快的多,所以磁盤的內存緩存將給系統存儲性能帶來質的飛越。

當 一 個進程準備讀取磁盤上的文件內容時, 操作系統會先查看待讀取的數據所在的頁(page)是否在頁緩存(pagecache)中,如果存在(命中)則直接返回數據, 從而避免了對物理磁盤的I/0操作;如果沒有命中, 則操作系統會向磁盤發起讀取請求并將讀取的數據頁存入頁緩存, 之后再將數據返回給進程。
同樣,如果 一 個進程需要將數據寫入磁盤, 那么操作系統也會檢測數據對應的頁是否在頁緩存中,如果不存在, 則會先在頁緩存中添加相應的頁, 最后將數據寫入對應的頁。 被修改過后的頁也就變成了臟頁, 操作系統會在合適的時間把臟頁中的數據寫入磁盤, 以保持數據的 一 致性
Kafka中大量使用了頁緩存, 這是Kafka實現高吞吐的重要因素之 一 。 雖然消息都是先被寫入頁緩存,然后由操作系統負責具體的刷盤任務的, 但在Kafka中同樣提供了同步刷盤及間斷性強制刷盤(fsync),可以通過 log.flush.interval.messages 和 log.flush.interval.ms 參數來控制。
同步刷盤能夠保證消息的可靠性,避免因為宕機導致頁緩存數據還未完成同步時造成的數據丟失。但是實際使用上,我們沒必要去考慮這樣的因素以及這種問題帶來的損失,消息可靠性可以由多副本來解決,同步刷盤會帶來性能的影響。 刷盤的操作由操作系統去完成即可

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容

  • 什么是Kafka[#---kafka] Kafka的應用場景[#kafka-----] Kafka的架構[#kaf...
    PENG先森_曉宇閱讀 4,910評論 4 91
  • 一、概述 (一)、kafka的定義 1、定義 1)kafka傳統的定義:kafka是一個分布式的基于發布/訂閱模式...
    rainple閱讀 732評論 0 0
  • 七、Kafka ·kafka是一個分布式消息系統。具有高性能、持久化、多副本備份、橫向擴展能力。將消息保存在磁盤中...
    Tu_jc閱讀 787評論 0 0
  • 消息中間件的背景分析 場景分析 前面跟著我看過 zk 的源碼,學過并發編程的同學應該知道,我們可以使用阻塞隊列+線...
    悠娜的奶爸閱讀 309評論 0 2
  • Kafka高級特性解析(三) 物理存儲 日志存儲概述 Kafka 消息是以主題為單位進行歸類,各個主題之間是彼此獨...
    奮斗的蛐蛐閱讀 682評論 0 0