我們先來看看我們常說的緩存有哪些
- 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 官方文檔也都有對應的描述
總結
- 一個系統中的不同層之間的訪問速度不一樣,所以我們才需要緩存
- 緩存會帶來數據一致性問題,可見性問題
- 緩存往往容量是小於被緩存數據的,所以任何緩存系統都要關注緩存命中率和數據淘汰的問題
這里就不展開說如何解決緩存帶來的問題了,每個系統都有自己的解決方案
簡單聊聊,最近的一些收獲,歡迎指正討論