1.文件目錄布局
Kafka消息以日志文件的形式存儲,不同主題下不同分區的消息分開存儲,同一個分區的不同副本分布在不同的broker上存儲
邏輯上看來日志是以副本為單位的,每個副本對應一個log對象,實際在物理上,一個log划分為多個logSegment
創建一個topic為3個分區,會在log.dirs路徑下創建三個文件夾,代表3個分區,命名規則為“topic名稱-分區編號”
logSegment並不代表這個文件夾,logSegment代表邏輯上的一組文件,這組文件就是.log、.index、.timeindex這三個不同文件擴展名,但是同文件名的文件
- .log存儲消息
- .index存儲消息的索引
- .timeIndex,時間索引文件,通過時間戳做索引
同一個logSegment的三個文件,文件名是一樣的。命名規則為.log文件中第一條消息的前一條消息偏移量,也稱為基礎偏移量,左邊補0,補齊20位。比如說第一個LogSegement的日志文件名為00000000000000000000.log,假如存儲了200條消息后,達到了log.segment.bytes配置的閾值(默認1個G),那么將會創建新的logSegment,文件名為00000000000000000200.log。以此類推。另外即使沒有達到log.segment.bytes的閾值,而是達到了log.roll.ms或者log.roll.hours設置的時間觸發閾值,同樣會觸發產生新的logSegment。
2.日志定位
日志定位也就是消息定位,輸入一個消息的offset,kafka如何定位到這條消息呢?
日志定位的過程如下:
1、根據offset定位logSegment。(kafka將基礎偏移量也就是logsegment的名稱作為key存在concurrentSkipListMap中)
2、根據logSegment的index文件查找到距離目標offset最近的被索引的offset的position x。
3、找到logSegment的.log文件中的x位置,向下逐條查找,找到目標offset的消息。
這里先說明一下.index文件的存儲方式。.index文件中存儲了消息的索引,存儲內容是消息的offset及物理位置position。並不是每條消息都有自己的索引,kafka采用的是稀疏索引,說白了就是隔n條消息存一條索引數據。這樣做比每一條消息都建索引,查找起來會慢,但是也極大的節省了存儲空間。此例中我們假設跨度為2,實際kafka中跨度並不是固定條數,而是取決於消息累積字節數大小。
例子中consumer要消費offset=15的消息。我們假設目前可供消費的消息已經存儲了三個logsegment,分別是00000000000000000,0000000000000000010,0000000000000000020。為了講解方便,下面提到名稱時,會把前面零去掉。
下面我們詳細講一下查找過程。
- kafka收到查詢offset=15的消息請求后,通過二分查找,從concurrentSkipListMap中找到對應的logsegment名稱,也就是10。
- 從10.index中找到offset小於等於15的最大值,offset=14,它對應的position=340
- 從10.log文件中物理位置340,順序往下掃描文件,找到offset=15的消息內容。
可以看到通過稀疏索引,kafka既加快了消息查找的速度,也顧及了存儲的開銷
3.日志清理
Kafka提供了兩種日志清理的方式:
1.日志刪除(Log Retention):直接刪除
2.日志壓縮(Log Compaction):對相同key的不同value值,只保留最后一個版本
通過log.cleanup.policy參數來設置清理策略,默認值為"delete",如果要設置為壓縮,需要改為"compact",還可以同時支持兩種策略
1.日志刪除
1.基於時間
根據日志分段中最大的時間戳來查找過期的日志分段文件
2.基於日志大小
檢查當前日志大小是否超過retentionSize,默認值為-1,表示無窮大,該值代表所有日志文件的總大小,不是單個日志分段的大小,單個日志分段大小默認為1GB
3.基於日志起始偏移量
判斷某個日志分段的下一個日志分段的起始偏移量baseOffset是否小於等於logStartOffset
4.磁盤存儲
1.頁緩存
頁緩存是操作系統實現的一種主要的磁盤緩存,用來減少對磁盤I/O的操作,將磁盤中的數據緩存到內存中,將對磁盤的訪問轉變為對內存的訪問
當一個進程准備讀取磁盤上的文件內容時,操作系統會首先查看待讀取的數據所在的頁是否在頁緩存中,如果在直接返回數據,如果沒有,操作系統會向磁盤發起讀取請求並將讀取的數據頁存入頁緩存,再將數據返回,寫入的時候也是類似
優點:對於進程而言,會在進程內部緩存數據,同時有可能這些數據緩存在操作系統的頁緩存中,被緩存了兩次,使用頁緩存可以省去進程內部的緩存消耗,還可以通過結構緊湊的字節碼替代使用對象的方式節省更多空間,假設服務重啟頁緩存還是保持有效
Kafka大量使用了頁緩存,這是Kafka實現高吞吐的重要因素之一。在Kafka中提供了同步刷盤和間斷性強制刷盤的功能,但不建議使用,因為會嚴重影響性能
Linux系統使用磁盤的一部分作為swap分區,把非活躍進程放入swap分區,對於Kafka而言,應該避免這種內存的交換,否則會影響性能,建議把vm.swappiness值設為1,保留了該機制又使得它對Kafka的影響最小
2.磁盤I/O流程
I/O調度策略:
1.NOOP
No Operation,I/O請求大致按照先來后到順序進行操作,做了相鄰I/O請求的合並
2.CFQ
Completely Fair Queuing(默認),按照I/O請求地址進行排序
3.DEADLINE
除了CFQ本身具有的I/O隊列,還為讀I/O和寫I/O提供了FIFO隊列
4.ANTICIPATORY
滿足隨機I/O和順序I/O混合的場景
3.零拷貝
將數據直接從磁盤文件復制到網卡設備中,不需要經過應用程序,減少了內核和用戶態之間的上下文切換,依賴sendFile()方法實現
通過DMA(Direct Memory Access)方式數據只需要復制2次,上下文切換也只需要兩次,零拷貝是針對內核模式的