Page Cache 与 Kafka 那些事儿
Kafka 整體架構
Kafka是大數據領域無處不在的消息中間件,目前廣泛使用在企業內部的實時數據管道,并幫助企業構建自己的流計算應用程序。
Kafka雖然是基于磁盤做的數據存儲,但卻具有高性能、高吞吐、低延時的特點,其吞吐量動輒幾萬、幾十上百萬。
Kafka為什么速度快、吞吐量大?
Kafka是將消息記錄持久化到本地磁盤中的,一般人會認為磁盤讀寫性能差,可能會對Kafka性能如何保證提出質疑。實際上不管是內存還是磁盤,快或慢關鍵在于尋址的方式,磁盤分為順序讀寫與隨機讀寫,內存也一樣分為順序讀寫與隨機讀寫。基于磁盤的隨機讀寫確實很慢,但磁盤的順序讀寫性能卻很高,一般而言要高出磁盤隨機讀寫三個數量級,一些情況下磁盤順序讀寫性能甚至要高于內存隨機讀寫。
這里給出著名學術期刊 ACM Queue 上的性能對比圖: queue.acm.org/detail.cf
磁盤的順序讀寫是磁盤使用模式中最有規律的,并且操作系統也對這種模式做了大量優化,Kafka就是使用了磁盤順序讀寫來提升的性能。Kafka的message是不斷追加到本地磁盤文件末尾的,而不是隨機的寫入,這使得Kafka寫入吞吐量得到了顯著提升 。
上圖就展示了Kafka是如何寫入數據的, 每一個Partition其實都是一個文件 ,收到消息后Kafka會把數據插入到文件末尾(虛框部分)。
這種方法有一個缺陷—— 沒有辦法刪除數據 ,所以Kafka是不會刪除數據的,它會把所有的數據都保留下來,每個消費者(Consumer)對每個Topic都有一個offset用來表示 讀取到了第幾條數據 。
兩個消費者,Consumer1有兩個offset分別對應Partition0、Partition1(假設每一個Topic一個Partition);Consumer2有一個offset對應Partition2。這個offset是由客戶端SDK負責保存的,Kafka的Broker完全無視這個東西的存在;一般情況下SDK會把它保存到zookeeper里面。(所以需要給Consumer提供zookeeper的地址)。
如果不刪除硬盤肯定會被撐滿,所以Kakfa提供了兩種策略來刪除數據。一是基于時間,二是基于partition文件大小。具體配置可以參看它的配置文檔:http://kafka.apache.org/08/documentation/#configuration。
在kafka/config/目錄下面有3個配置文件:
producer.properties
consumer.properties
server.properties
詳細配置文件參考:https://www.jianshu.com/p/c5618df93b43。
Page Cache 是什么?
PageCache是OS對文件的緩存,用于加速對文件的讀寫。一般來說,程序對文件進行順序讀寫的速度幾乎接近于內存的讀寫訪問,這里的主要原因就是在于OS使用PageCache機制對讀寫訪問操作進行了性能優化,將一部分的內存用作PageCache。
對于數據文件的讀取,如果一次讀取文件時出現未命中(cache miss)PageCache的情況,OS從物理磁盤上訪問讀取文件的同時,會順序對其他相鄰塊的數據文件進行預讀取(順序讀入緊隨其后的少數幾個頁面)。這樣,只要下次訪問的文件已經被加載至PageCache時,讀取操作的速度基本等于訪問內存。
Page Cache是針對文件系統的緩存,通過將磁盤中的文件數據緩存到內存中,從而減少磁盤I/O操作提高性能。
對磁盤的數據進行緩存從而提高性能主要是基于兩個因素:
文件 IO 讀寫流程
讀流程
1、應用程序發起讀請求,觸發系統調用read()函數,用戶態切換為內核態;
2、文件系統通過目錄項→inode→address_space→頁緩存樹,查詢Page Cache是否存在;
3、Page Cache不存在產生缺頁中斷,CPU向DMA發出控制指令;
DMA 控制器將數據從主存或硬盤拷貝到內核空間(kernel space)的緩沖區(read buffer);
4、DMA 磁盤控制器向 CPU 發出數據讀完的信號,由 CPU 負責將數據從內核緩沖區拷貝到用戶緩沖區;
5、用戶進程由內核態切換回用戶態,獲得文件數據;
寫流程
1、應用程序發起寫請求,觸發系統調用write()函數,用戶態切換為內核態;
2、文件系統通過目錄項→inode→address_space→頁緩存樹,查詢 Page Cache是否存在,如果不存在則需要創建;
3、Page Cache 存在后,CPU將數據從用戶緩沖區拷貝到內核緩沖區,Page Cache 變為臟頁(Dirty Page,內存數據頁跟磁盤數據頁內容不一致),寫流程返回;
4、用戶主動觸發刷盤或者達到特定條件內核觸發刷盤,喚醒 pdflush 線程,pdflush 將內核緩沖區的數據刷入磁盤;
臟頁: 當內存數據頁跟磁盤數據頁內容不一致的時候,我們稱這個內存頁為“臟頁”。內存數據寫入到磁盤后,內存和磁盤上的數據頁的內容就一致了,稱為“干凈頁”。
pdflush回寫時機
定時方式執行;
內存不足時;
用戶主動觸發;
DMA傳輸
DMA 的全稱叫直接內存存取(Direct Memory Access),是一種允許外圍設備(硬件子系統)直接訪問系統主內存的機制。基于 DMA 訪問方式,硬件與內核緩沖區的數據傳輸由DMA控制器控制,CPU只需在數據傳輸開始和結束時做一點處理外(開始和結束時候要做中斷處理),釋放了CPU。目前大多數的硬件設備,包括磁盤控制器、網卡、顯卡以及聲卡等都支持 DMA 技術。
https://blog.csdn.net/yangguosb/article/details/77886826
2、讀Cache
當內核發起一個讀請求時(例如進程發起read()請求),首先會檢查請求的數據是否緩存到了Page Cache中。
如果有,那么直接從內存中讀取,不需要訪問磁盤,這被稱為cache命中(cache hit);
如果cache中沒有請求的數據,即cache未命中(cache miss),就必須從磁盤中讀取數據。然后內核將讀取的數據緩存到cache中,這樣后續的讀請求就可以命中cache了。
page可以只緩存一個文件部分的內容,不需要把整個文件都緩存進來。
3、寫Cache
當內核發起一個寫請求時(例如進程發起write()請求),同樣是直接往cache中寫入,后備存儲中的內容不會直接更新(當服務器出現斷電關機時,存在數據丟失風險)。
內核會將被寫入的page標記為dirty,并將其加入dirty list中。內核會周期性地將dirty list中的page寫回到磁盤上,從而使磁盤上的數據和內存中緩存的數據一致。
當滿足以下兩個條件之一將觸發臟數據刷新到磁盤操作:
Kafka 對 page cache 的利用
Kafka為什么不自己管理緩存,而非要用page cache?原因有如下三點:
1.JVM中一切皆對象,數據的對象存儲會帶來所謂object overhead,浪費空間;
2.如果由JVM來管理緩存,會受到GC的影響,并且過大的堆也會拖累GC的效率,降低吞吐量;
3.一旦程序崩潰,自己管理的緩存數據會全部丟失。
Kafka三大件(broker、producer、consumer)與page cache的關系可以用下面的簡圖來表示。
producer生產消息時,會使用pwrite()系統調用【對應到Java NIO中是FileChannel.write() API】按偏移量寫入數據,并且都會先寫入page cache里。consumer消費消息時,會使用sendfile()系統調用【對應FileChannel.transferTo() API】,零拷貝地將數據從page cache傳輸到broker的Socket buffer,再通過網絡傳輸。
圖中沒有畫出來的還有leader與follower之間的同步,這與consumer是同理的:只要follower處在ISR中,就也能夠通過零拷貝機制將數據從leader所在的broker page cache傳輸到follower所在的broker。
同時,page cache中的數據會隨著內核中flusher線程的調度以及對sync()/fsync()的調用寫回到磁盤,就算進程崩潰,也不用擔心數據丟失。另外,如果consumer要消費的消息不在page cache里,才會去磁盤讀取,并且會順便預讀出一些相鄰的塊放入page cache,以方便下一次讀取。
由此我們可以得出重要的結論:如果Kafka producer的生產速率與consumer的消費速率相差不大,那么就能幾乎只靠對broker page cache的讀寫完成整個生產-消費過程,磁盤訪問非常少。這個結論俗稱為“讀寫空中接力”。并且Kafka持久化消息到各個topic的partition文件時,是只追加的順序寫,充分利用了磁盤順序訪問快的特性,效率高。
關于 Linux 文件 IO 讀寫,參考:https://www.jianshu.com/p/d81f51e58b83
Java 的 IO 讀寫
Java的 IO讀寫大致分為三種:
1、普通IO(java.io)
例如FileWriter、FileReader等,普通IO是傳統字節傳輸方式,讀寫慢阻塞,單向一個Read對應一個Write 。
2、文件通道 FileChannel(java.nio)
FileChannel fileChannel = new RandomAccessFile(new File("data.txt"), "rw").getChannel()全雙工通道,采用內存緩沖區ByteBuffer且是線程安全的
使用FileChannel為什么會比普通IO快?
一般情況FileChannel在一次寫入4kb的整數倍數時,才能發揮出實際的性能,益于FileChannel采用了ByteBuffer這樣的內存緩沖區。這樣可以精準控制寫入磁盤的大小,這是普通IO無法實現FileChannel是直接把ByteBuffer的數據直接寫入磁盤?
ByteBuffer 中的數據和磁盤中的數據還隔了一層,這一層便是 PageCache,是用戶內存和磁盤之間的一層緩存。我們都知道磁盤 IO 和內存 IO 的速度可是相差了好幾個數量級。我們可以認為 filechannel.write 寫入 PageCache 便是完成了落盤操作,但實際上,操作系統最終幫我們完成了 PageCache 到磁盤的最終寫入,理解了這個概念,你就應該能夠理解 FileChannel 為什么提供了一個 force() 方法,用于通知操作系統進行及時的刷盤,同理使用 FileChannel 時同樣經歷:
磁盤->PageCache->用戶內存
這三個階段。
3、內存映射MMAP(java.nio)
mmap的工作原理:當你發起這個 mmap 文件讀寫調用的時候,它只是在你的虛擬空間中分配了一段空間,連真實的物理地址都不會分配的。在你訪問這段空間的時刻,CPU觸發了 OS內核缺頁異常[note],執行 PageFault 異常處理,然后異常處理會在這個時間分配物理內存,并用文件的內容填充這片內存,然后才返回你進程的上下文,這時你的程序才會感知到這片內存里有數據。Linux 是直到,實在實在是不行的時候,才會分配物理頁。https://www.zhihu.com/question/48161206/answer/110418693
note:關于缺頁異常,參考:https://www.jianshu.com/p/5ff9b1b3c95e
Java 中的 mmap 實現類: MappedByteBuffer
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, position, fileSize)mmap 把文件映射到用戶空間里的虛擬內存,省去了從內核緩沖區復制到用戶空間的過程,文件中的位置在虛擬內存中有了對應的地址,可以像操作內存一樣操作這個文件,相當于已經把整個文件放入內存,但在真正使用到這些數據前卻不會消耗物理內存,也不會有讀寫磁盤的操作,只有真正使用這些數據時,也就是圖像準備渲染在屏幕上時,虛擬內存管理系統 VMS
MMAP 并非是文件 IO 的銀彈,它只有在一次寫入很小量數據的場景下才能表現出比 FileChannel 稍微優異的性能。緊接著我還要告訴你一些令你沮喪的事,至少在 JAVA 中使用 MappedByteBuffer 是一件非常麻煩并且痛苦的事,主要表現為三點:
MMAP 使用時必須實現指定好內存映射的大小,并且一次 map 的大小限制在 1.5G 左右,重復 map 又會帶來虛擬內存的回收、重新分配的問題,對于文件不確定大小的情形實在是太不友好了。
MMAP 使用的是虛擬內存,和 PageCache 一樣是由操作系統來控制刷盤的,雖然可以通過 force() 來手動控制,但這個時間把握不好,在小內存場景下會很令人頭疼。
MMAP 的回收問題,當 MappedByteBuffer 不再需要時,可以手動釋放占用的虛擬內存。
參考資料
https://www.jianshu.com/p/958f82922e4bhttps://blog.csdn.net/yancychas/article/details/88561291https://blog.csdn.net/gx11251143/article/details/107620259https://zhuanlan.zhihu.com/p/22604682https://www.sohu.com/a/406019130_115128https://blog.csdn.net/yangguosb/article/details/77886826
總結
以上是生活随笔為你收集整理的Page Cache 与 Kafka 那些事儿的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 求滑动窗口中的最大值和最小值
- 下一篇: (五十二)剑网三大风车伤害计算器