Kafka深入解析Log存儲

引言


  Kafka中的Message是以topic爲基本單位組織的,不同的topic之間是相互獨立的。每個topic又可以分成幾個不同的partition(每個topic有幾個partition是在創建topic時指定的),每個partition存儲一部分Message。借用官方的一張圖,可以直觀地看到topic和partition的關係。

這裏寫圖片描述

  partition是以文件的形式存儲在文件系統中,比如,創建了一個名爲page_visits的topic,其有5個partition,那麼在Kafka的數據目錄中(由配置文件中的log.dirs指定的)中就有這樣5個目錄: page_visits-0, page_visits-1,page_visits-2,page_visits-3,page_visits-4,其命名規則爲topic_name-partition_id,裏面存儲的分別就是這5個partition的數據。

Partition的數據文件


  Partition中的每條Message由offset來表示它在這個partition中的偏移量,這個offset不是該Message在partition數據文件中的實際存儲位置,而是邏輯上一個值,它唯一確定了partition中的一條Message。因此,可以認爲offset是partition中Message的id。partition中的每條Message包含了以下三個屬性:

  • offset
  • MessageSize
  • data

  其中offset爲long型,MessageSize爲int32,表示data有多大,data爲message的具體內容。它的格式和Kafka通訊協議中介紹的MessageSet格式是一致。

  Partition的數據文件則包含了若干條上述格式的Message,按offset由小到大排列在一起。它的實現類爲FileMessageSet,類圖如下:

這裏寫圖片描述

它的主要方法如下:

  • append: 把給定的ByteBufferMessageSet中的Message寫入到這個數據文件中。
  • searchFor: 從指定的startingPosition開始搜索找到第一個Message其offset是大於或者等於指定的offset,並返回其在文件中的位置Position。它的實現方式是從startingPosition開始讀取12個字節,分別是當前MessageSet的offset和size。如果當前offset小於指定的offset,那麼將position向後移動LogOverHead+MessageSize(其中LogOverHead爲offset+messagesize,爲12個字節)。
  • read:準確名字應該是slice,它截取其中一部分返回一個新的FileMessageSet。它不保證截取的位置數據的完整性。
  • sizeInBytes: 表示這個FileMessageSet佔有了多少字節的空間。
  • truncateTo: 把這個文件截斷,這個方法不保證截斷位置的Message的完整性。
  • readInto: 從指定的相對位置開始把文件的內容讀取到對應的ByteBuffer中。

我們來思考一下,如果一個partition只有一個數據文件會怎麼樣?

1、新數據是添加在文件末尾(調用FileMessageSet的append方法),不論文件數據文件有多大,這個操作永遠都是O(1)的。

2、查找某個offset的Message(調用FileMessageSet的searchFor方法)是順序查找的。因此,如果數據文件很大的話,查找的效率就低。

那Kafka是如何解決查找效率的的問題呢?有兩大法寶:

1、分段

2、索引

數據文件的分段


  Kafka解決查詢效率的手段之一是將數據文件分段,比如有100條Message,它們的offset是從0到99。假設將數據文件分成5段,第一段爲0-19,第二段爲20-39,以此類推,每段放在一個單獨的數據文件裏面,數據文件以該段中最小的offset命名。這樣在查找指定offset的Message的時候,用二分查找就可以定位到該Message在哪個段中。

爲數據文件建索引


  數據文件分段使得可以在一個較小的數據文件中查找對應offset的Message了,但是這依然需要順序掃描才能找到對應offset的Message。爲了進一步提高查找的效率,Kafka爲每個分段後的數據文件建立了索引文件,文件名與數據文件的名字是一樣的,只是文件擴展名爲.index。
索引文件中包含若干個索引條目,每個條目表示數據文件中一條Message的索引。索引包含兩個部分(均爲4個字節的數字),分別爲相對offset和position。

  • 相對offset:因爲數據文件分段以後,每個數據文件的起始offset不爲0,相對offset表示這條Message相對於其所屬數據文件中最小的offset的大小。舉例,分段後的一個數據文件的offset是從20開始,那麼offset爲25的Message在index文件中的相對offset就是25-20 = 5。存儲相對offset可以減小索引文件佔用的空間。
  • position:表示該條Message在數據文件中的絕對位置。只要打開文件並移動文件指針到這個position就可以讀取對應的Message了。

  index文件中並沒有爲數據文件中的每條Message建立索引,而是採用了稀疏存儲的方式,每隔一定字節的數據建立一條索引。這樣避免了索引文件佔用過多的空間,從而可以將索引文件保留在內存中。但缺點是沒有建立索引的Message也不能一次定位到其在數據文件的位置,從而需要做一次順序掃描,但是這次順序掃描的範圍就很小了。

在Kafka中,索引文件的實現類爲OffsetIndex,它的類圖如下:

這裏寫圖片描述

主要的方法有:

  • append方法,添加一對offset和position到index文件中,這裏的offset將會被轉成相對的offset;
  • lookup, 用二分查找的方式去查找小於或等於給定offset的最大的那個offset。

小結


  我們以幾張圖來總結一下Message是如何在Kafka中存儲的,以及如何查找指定offset的Message的。

  Message是按照topic來組織,每個topic可以分成多個的partition,比如:有5個partition的名爲爲page_visits的topic的目錄結構爲:

這裏寫圖片描述

  partition是分段的,每個段叫LogSegment,包括了一個數據文件和一個索引文件,下圖是某個partition目錄下的文件:

這裏寫圖片描述

可以看到,這個partition有4個LogSegment。

借用博主@lizhitao博客上的一張圖來展示是如何查找Message的。

這裏寫圖片描述

比如:要查找絕對offset爲7的Message:

1、首先是用二分查找確定它是在哪個LogSegment中,自然是在第一個Segment中。

2、打開這個Segment的index文件,也是用二分查找找到offset小於或者等於指定offset的索引條目中最大的那個offset。自然offset爲6的那個索引是我們要找的,通過索引文件我們知道offset爲6的Message在數據文件中的位置爲9807。
打開數據文件,從位置爲9807的那個地方開始順序掃描直到找到offset爲7的那條Message。

3、這套機制是建立在offset是有序的。索引文件被映射到內存中,所以查找的速度還是很快的。

一句話,Kafka的Message存儲採用了分區(partition),分段(LogSegment)和稀疏索引這幾個手段來達到了高效性。

轉載自:http://blog.csdn.net/jewes/article/details/42970799

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