引言:前面我們從底往上介紹了磁盤到文件系統再到虛擬內存,而我們經常聽到“高速緩存”是個啥玩意?首先我們擴展下該知識點。而本文主要是站在計算機體系的角度上和站在應用程序如數據庫的角度上對存儲和存儲分層做最后的總結。
一、存儲器層次結構
首先我們站在計算機體系結構的角度看存儲分層,如下圖:
仔細看看此圖,特別是兩邊的注釋,存儲的結構就一目了然。而很多的資料上介紹的是存儲經典的三層金字塔:L0(或L2)、L4和L5。而這個圖是見過最詳細的了,非常好!
上一篇虛擬內存屬於圖中的L4,而我們有一系列的文章大多在講述L5,NAS可以屬於L6,同時希望有一天來講述真正的分布式文件系統。今天我們主要介紹L0-L3。
1、硬件高速緩存
在這個層次結構的最頂層,是CPU內部的一些寄存器,它們的訪問速度是非常快的,當今的CPU主頻都是GHZ級別的,而對於內存DDR(L4)來說,每次存取操作都會耗用很多的時鍾周期,這意味着,CPU需要等待很長時間來完成一次讀或者寫操作。
為了縮小CPU和RAM之間的速度不匹配,引入了硬件高速緩存內存(hardware cache memory)。硬件高速緩存基於著名的局部性原理(locality priciple),該原理既適用於程序結構也適用於數據結構。
80x86體系結構引入了一個叫行(line)的新單位。行由十幾個連續的字節組成,它們以脈沖突發模式(burst mode)在慢速DRAM和快速的用來實現高速緩存的片上靜態RAM(SRAM)之間傳送,用來實現高速緩存。
高速緩存再被細分為行的子集。在一種極端的情況下,高速緩存可以是直接映射的(direct mapped),這是主存個中的一個行總是存放在高速緩存中完全相同的位置。在另一種極端情況下,高速緩存是充分關聯的(fully associative),這意味着主存中的任意一個行可以存放在高速緩存中的任意位置。但是大多數高速緩存在某種程度上是N-路組關聯的(N-way set associative),意味着主存中的任意一個行可以存放在高速緩存N行中的任意一行中。
高速緩存單元插在分頁單元和主內存之間。它包含一個硬件高速緩存內存(hardware cache memory)和一個高速緩存控制器(cache controller)。高速緩存內存存放內存中真正的行。高速緩存控制器存放一個表項數組,每個表項對應高速緩存內存中的一個行。每個表項有一個標簽(tag)和表示高速緩存行狀態的幾個表示(flag)。這個標簽由一些位組成,這些位讓高速緩存控制器能夠辨別由這個行當前所映射的內存單元。這種內存物理地址通常分為3組:最高幾位對應標簽,中間幾位對應高速緩存控制器的子集索引,最低幾位對應行內的偏移量。
當訪問一個RAM存儲單元是,CPU從物理地址中提取出子集的索引號並把子集中所有行的標簽與物理地址的高幾位相比較。如果發現某一個行的標簽與這個物理地址的高位相同,則稱CPU(的高速)緩存命中(cache hit);否則,(高速)緩存不命中(cache miss)。緩存不命中一般需要進行置換和調整工作集或者訪問內存,太頻繁說明程序的局部性較差。
如上圖為多處理器系統緩存結構。在L1中分數據cache(d-cache)和指令cache(i-cache),它們分別用來存放數據和執行這些數據的指令。L2 cache只存儲數據,因此不分數據cache和指令cache。L3 cache為多核共享。
多處理器系統的每一個處理器都有一個單獨的硬件高速緩存,因此它們需要額外的硬件電路用於保持高速緩存內容的同步。更新變得更耗時:自己一個CPU修改了它的硬件高速緩存,就必須檢查其他CPU中是否有同樣的數據;如果是,那么它必須通知其他CPU用適當的值更新數據。這種活動叫做高速緩存偵聽(cache snooping)。這些都在硬件級處理,內核無需關心。
多級緩存的一致性是由硬件實現的,Linux忽略這些硬件細節並假定只有一個單獨的高速緩存。
2、局部性原理
兩種不同形式:時間局部性和空間局部性。
在一個具有良好時間局部性的程序中,被引用過一次的內存位置很可能在不遠的將來在此多次引用;
在一個具有良好空間局部性的程序中,如果一個內存位置被引用了一次,那么程序在不遠的將來引用附近的一個內存位置。
有良好局部性的程序比局部性差的程序運行的更快。因此當我們引入小而快的緩存來存放最近最常使用的代碼和數據,將極大提高系統的運行效率。
小結:現代系統中到處使用了緩存,如上圖,CPU芯片、操作系統、文件系統、RAID陣列和萬維網上都使用了緩存。各種各樣硬件和軟件的組合構成和管理着緩存。
二、緩存讀寫模式
這一節我們從應用程序的角度,看存儲系統的分層:
我們知道,緩存無處不在。上面三層都有自己的緩存,一般都在主機內存,例如文件系統和應用程序數據庫,都有自己的緩存。根據虛擬內存的定義,它們都是進程,都有自己的虛擬地址。
但是最底層的磁盤控制器或者RAID卡則有自己的硬件緩存,前面我們提到過,RAID卡自帶CPU、ROM和RAM,所以它就是一個小型的計算機。如果有RAID卡的服務器就是計算機中嵌套小型計算機。RAID卡的緩存是RAM芯片,不是SRAM,但是也具備緩存的功能。
這些緩存原理是相似的,但同時也可能是矛盾的。
1、緩存的寫模式
(1)WriteBack模式:回寫模式。上層發送過來的數據,將其保存到緩存后,立即通知主機IO已經完成。從而主機可以不加等待地執行下一個IO,此時數據還在緩存中,並沒有真正寫入磁盤。所以這是有一定風險的,如果此時意外斷電等異常,存在丟失數據的可能。
(2)WriteThrough模式:寫透模式,直寫模式。上層發送過來的數據,只有數據寫入磁盤之后,才通知主機IO已經完成。這種保證了數據的完整性。但是雖然緩存還是有一定的緩沖作用,但是提速的作用就沒有了。
2、緩存的讀模式
除了寫緩存外,讀緩存也非常重要,緩存算法是門復雜的學問,有一套復雜的機制,這里介紹兩種常見的模式。
(1)預取模式(PreFetch):對磁盤接下來“有可能”被主機訪問到的數據,在主機還沒有發出讀IO請求的時候,就“擅自”先讀入到緩存。這是RAID卡常見的模式。
(2)臟讀模式(dirty):數據被修改后,繼續保留在緩存中,認為下次很有可能被讀取。我們前面提到虛擬內存頁面置換非常經典的LRU算法就是這種方式。也大量應用在數據庫中。其實這也是訪問的局部性原理。
以上是大部分緩存包括cache、RAM和RAID卡常見的讀寫模式。
但是文件系統的IO定義上有所不同:主要有O_DIRECT和O_SYNC兩個選項。
I/O緩沖的過程是這樣的:用戶數據 –> stdio緩沖區 –> 內核緩沖區高速緩存 –> 磁盤。
O_DIRECT:用於讓IO從用戶態直接跨過“stdio緩沖區的高速緩存”和“內核緩沖區的高速緩存”,直接寫到存儲上。
O_SYNC:用於控制“內核緩沖區的高速緩存”直接寫到存儲上,即強制刷新內核緩沖區到輸出文件的存儲。
文件系統最后寫到磁盤,而磁盤可能再有緩存模式,這樣整個IO流程將變得更加復雜。建議就是,回寫模式或者O_SYNC方式只要有一處足矣;如果需要為了確保數據安全,那么則選擇所有的緩存設置為WriteThrough或者O_DIRECT。
最后,數據庫系統也是存儲系統的一部分,也屬於存儲系統的范疇,它和文件系統在很多方面是類似的。例如MySQL有非常豐富的日志文件,其中重寫日志和文件系統的日志類似都是為了保證數據的一致性;同時MySQL還有非常完善的緩存機制和檢查機制check point。
數據庫的主要作用是管理數據,(主要戰場)不是在磁盤上就在內存中(如Redis)。而文件系統也是將數據最終寫在磁盤上。它們的管理的數據形式不一樣,對象不一樣,所以機制會有不同。但是最終都是利用磁盤和內存作為緩存對數據進行管理。最終都利用了存儲結構中上層對下層的緩存,充分利用局部性原理提高性能。
參考資料:
《深入理解計算機系統》第三版。
《大話存儲II》。