我們先來看看我們常說的快取有哪些
- CPU 的 L1, L2, L3 快取
- 作業系統記憶體(相對於CPU來說也是快取)
- 作業系統 page cache 高速頁快取(快取磁碟中的資料)
- Redis 用作 MySQL的快取
- MySQL buffer pool 的資料頁快取
- Java HashMap 實現的堆內快取
通過上面的例子我們可以知道快取的實現不一定都是DRAM記憶體,但他們的作用都一樣,解決兩個系統或層次的讀寫能力差距
作業系統的快取
在極客時間《Java併發程式設計實戰》這樣寫道
這些年,我們的 CPU、記憶體、I/O 裝置都在不斷迭代,不斷朝著更快的方向努力。但是,在這個快速發展的過程中,有一個核心矛盾一直存在,就是這三者的速度差異。
CPU 和記憶體的速度差異可以形象地描述為:CPU 是天上一天,記憶體是地上一年(假設 CPU 執行一條普通指令需要一天,那麼 CPU 讀寫記憶體得等待一年的時間)。記憶體和 I/O 裝置的速度差異就更大了,記憶體是天上一天,I/O 裝置是地上十年。
程式裡大部分語句都要訪問記憶體,有些還要訪問 I/O,根據木桶理論(一隻水桶能裝多少水取決於它最短的那塊木板),程式整體的效能取決於最慢的操作——讀寫 I/O 裝置,也就是說單方面提高 CPU 效能是無效的。
為了合理利用 CPU 的高效能,平衡這三者的速度差異,計算機體系結構、作業系統、編譯程式都做出了貢獻,主要體現為:
1. CPU 增加了快取,以均衡與記憶體的速度差異;
2. 作業系統增加了程式、執行緒,以分時複用 CPU,進而均衡 CPU 與 I/O 裝置的速度差異;
3. 編譯程式優化指令執行次序,使得快取能夠得到更加合理地利用。
同時作者也說了快取帶來的問題:可見性,這也是併發程式設計Bug的源頭之一
一個執行緒對共享變數的修改,另外一個執行緒能夠立刻看到,我們稱為可見性。
在單核時代,所有的執行緒都是在一顆 CPU 上執行,CPU 快取與記憶體的資料一致性容易解決。因為所有執行緒都是操作同一個 CPU 的快取,一個執行緒對快取的寫,對另外一個執行緒來說一定是可見的。
多核時代,每顆 CPU 都有自己的快取,這時 CPU 快取與記憶體的資料一致性就沒那麼容易解決了,當多個執行緒在不同的 CPU 上執行時,這些執行緒操作的是不同的 CPU 快取。
Redis 用作快取
下面我們在看看 Redis 用作快取的場景
在極客時間《Redis核心技術與實戰》中作者寫道:
一個系統中的不同層之間的訪問速度不一樣,所以我們才需要快取
所以,計算機系統中,預設有兩種快取:
1. CPU 裡面的末級快取,即 LLC,用來快取記憶體中的資料,避免每次從記憶體中存取資料;
2. 記憶體中的高速頁快取,即 page cache,用來快取磁碟中的資料,避免每次從磁碟中存取資料。
還有一點非常重要,文中也提到了
快取系統的容量大小總是小於後端慢速系統的,我們不可能把所有資料都放在快取系統中。
這其實取決於硬體的限制成本,讀寫越快的儲存越貴,容量越大的儲存越貴,並且兩者不是線性增長的關係。
另外這個限制也說明快取系統是要有資料淘汰機制的,比如redis就有多種資料淘汰策略
同時還要關注快取命中率的問題,有限的資源當然要給訪問更頻繁的資料。
那麼Redis用作磁碟DB的快取又會帶來什麼問題呢?
就是我們常說的快取不一致問題,這裡不展開討論了
MySQL中的快取設計
我們都知道MySQL的資料是儲存在磁碟上的,但是在購買MySQL例項時,往往能看到8核32g,64g這樣的配置,為什麼磁碟資料庫還需要這麼大的記憶體呢,如果你檢視的記憶體使用量,發現也是不低的
這是因為MySQL也有自己的資料頁記憶體,和作業系統類似,作業系統對磁碟有page cache
,mysql
也有 buffer pool
另外,MySQL作為一個複雜的資料庫系統,在磁碟IO上做了大量的快取設計,比如寫 binlog
有 binlog cache
,寫 redo log
有 redo log cache
同時 MySQL 也利用了作業系統的高速頁快取(page cache)來提高讀寫效能,比如組提交機制
MySQL 涉及到的快取很多,這裡不細講了,極客上面的課有對上面的內容作解釋,MySQL 官方文件也都有對應的描述
總結
- 一個系統中的不同層之間的訪問速度不一樣,所以我們才需要快取
- 快取會帶來資料一致性問題,可見性問題
- 快取往往容量是小於被快取資料的,所以任何快取系統都要關注快取命中率和資料淘汰的問題
這裡就不展開說如何解決快取帶來的問題了,每個系統都有自己的解決方案
簡單聊聊,最近的一些收穫,歡迎指正討論