關於OS Page Cache的簡單介紹


在現代計算機系統中,CPU,RAM,DISK的速度不相同。CPU與RAM之間,RAM與DISK之間的速度差異常常是指數級。為了在速度和容量上折中,在CPU與RAM之間使用CPU cache以提高訪存速度,在RAM與磁盤之間,操作系統使用page cache提高系統對文件的訪問速度。

操作系統在處理文件時,需要考慮兩個問題:

1.相對於內存的高速讀寫,緩慢的硬盤驅動器,特別是磁盤尋道較為耗時。

2.文件加載到物理內存一次,並在多個程序間共享。

幸運的是,操作系統使用page cache機制解決了上面的兩個問題。page cache(頁面緩存),內核在其中存儲頁面大小倍數的文件塊。現假設一名為render的程序需要讀取512字節scene.dat文件的內容,流程分析如下:

1.render請求獲取512字節scene.dat文件的內容,使用系統調用 read(scene.dat, to_heap_buf, 512, offset=0)

2.內核從頁面緩存中搜索滿足請求的scene.dat文件的4KB的塊,如果數據尚未緩存,則進入下一步

3.內核申請頁幀空間,進行I/O操作,從偏移位置0開始請求4KB的數據,並復制到頁幀中

4.內核從page cache中復制512字節的數據到render的緩存中,read()系統調用結束

對於系統的所有文件I/O請求,操作系統都是通過page cache機制實現的,對於操作系統而言,磁盤文件都是由一系列的數據塊順序組成,數據塊的大小隨系統的不同而不同,x86 linux系統下是4KB(一個標准的頁面大小)。內核在處理文件I/O請求時,首先到page cache中查找(page cache中的每一個數據塊都設置了文件以及偏移信息),如果未命中,則啟動磁盤I/O,將磁盤文件中的數據塊加載到page cache中的一個空閑塊,之后copy到用戶緩沖區中。

很明顯,同一塊文件數據,在內存中保存了兩份,這既占用了不必要的內存空間,冗余的拷貝,也導致了CPU cache利用率不高。針對此問題,操作系統提供了內存映射機制(Linux中的mmap,windows中的Filemapping)。

在使用mmap調用時,系統並不馬上為其分配內存空間,而僅僅是添加一個VMA(Virtual Memory Area)到該進程中,當程序訪問到目標空間時,產生缺頁中斷。在缺頁中斷中,從page cache中查找要訪問的文件塊,若未命中,則啟動磁盤I/O從磁盤中加載到page cache,然后將文件塊在page cache中的物理頁映射到進程mmap地址空間。

當程序退出或關閉文件時,系統是否會馬上清除page cache中的相應頁面呢?答案是否定的。由於該文件可能被其它進程訪問,或該進程一段時間后會重新訪問,因此,在物理內存足夠的情況下,系統總是將其保存在page cache中,這樣可以提高系統的整體性能。只有當系統物理內存不足時,內核才會主動清理page cache。

當進程調用write修改文件時,由於page cache的存在,修改並不會馬上更新到磁盤,而只是暫時更新到page cache中,同時mark目標page為dirty,當內核主動釋放page cache時,才將更新寫入到磁盤(主動調用sync時,也會更新到磁盤)。

下面介紹一下Kafka中對於page cache的利用。

Kafka是一種高吞吐量的分布式發布訂閱消息系統,有如下特性:

1.通過O(1)的磁盤數據結構提供消息的持久化,這種結構對於即使數以TB的消息存儲也能夠保持長時間的穩定性能

2.高吞吐量,即使是非常普通的硬件,Kafka也可以支持每秒數百萬的消息

3.支持Kafka服務器和消費機集群來分區消息

4.支持Hadoop並行數據加載

生產者寫入速度快,不同於Redis和MemcacheQ等內存消息隊列,Kafka的設計是把所有的Message都要寫入速度低,容量大的硬盤,以此來換取更強的存儲能力。實際上,Kafka使用硬盤並沒有帶來過多的性能損失,而是“規規矩矩”的抄了一條近道。

首先說“規規矩矩”是因為Kafka在磁盤上只做Sequence I/O(順序寫),由於消息系統讀寫的特殊性,這並不存在什么問題。關於磁盤I/O的性能,Kafka官方給出的測試數據(RAID-5,7200rpm):

Sequence I/O: 600MB/s (實驗室)

Sequence I/O: 400MB-500MB/s (工作場景)

Random I/O: 100KB/s

所以通過只做Sequence I/O的限制,規避了磁盤訪問速度低下對性能可能造成的影響。

接下來談談Kafka如何“抄近道”的。

首先,Kafka重度依賴底層OS提供的page cache功能。當上層有寫操作時,OS只是將數據寫入到page cache,同時標記page屬性為dirty。當讀操作發生時,先從page cache中查找,如果發生缺頁才進行磁盤調度,最終返回需要的數據。

實際上page cache是把盡可能多的空閑內存都當做磁盤緩存來使用,同時如果有其它進程申請內存,回收page cache的代價又很小,所以現代的OS都支持page cache。使用page cache功能同時可以避免在JVM內部緩存數據,JVM為我們提供了強大的GC功能,同時也引入了一些問題不適用與Kafka的設計。如果在heap內管理緩存,JVM的GC線程會頻繁掃描heap空間,帶來不必要的開銷。如果heap過大,執行一次Full GC對系統的可用性來說將會是極大的挑戰。此外所有在JVM內的對象都不免帶有一個Object Overhead(對象數量足夠多時,不可小覷此部分內存占用),內存的有效空間利用率會因此降低。所有In-Process Cache在OS中都有一份同樣的page cache。所以通過將緩存只放在page cache,可以至少讓可用緩存空間翻倍。如果Kafka重啟,所有的In-Process Cache都會失效,而OS管理的page cache依然可以繼續使用。

消費者獲取數據快,page cache還只是第一步,Kafka為了進一步的優化性能還采用了Sendfile技術。在解釋Sendfile之前,先介紹一下傳統的網絡I/O操作流程,大體上分為以下四步:

1.OS從硬盤把數據讀到內核的page cache

2.用戶進程把數據從內核拷貝到用戶空間

3.然后用戶進程再把數據寫入到Socket,數據流入內核空間的socket buffer上

4.OS再把數據從buffer中拷貝到網卡到buffer上,這樣完成一次發送

整個過程共經歷兩次context switch,四次system call。同一份數據在內核buffer與用戶buffer之間重復拷貝,效率低下。其中2,3兩步沒有必要,完全可以直接在內核空間完成數據拷貝。這也是sendfile所解決的問題,經過sendfile優化后,整個I/O過程變成了下面的樣子:

 

參考博文:

http://www.manongjc.com/article/24518.html

https://zhuanlan.zhihu.com/p/54762255

https://blog.csdn.net/pizi0475/article/details/49493841


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM