網際網路架構中快取無處不在,某廠牛人曾經說過:」快取就像清涼油,**不舒服,抹一下就好了」。高品質的儲存容量小,**高;低品質儲存容量大,**低,快取的目的就在於」擴充」高品質儲存的容量。本文**快取相關的一些問題。
lru替換演算法
快取的技術點包括記憶體管理和替換演算法。lru是使用最多的替換演算法,每次淘汰最久沒有使用的元素。lru快取實現分為兩個部分:hash表和lru鍊錶,hash表用於查詢快取中的元素,lru鍊錶用於淘汰。記憶體常以slab的方式管理。
上圖是memcache的記憶體管理示意圖,memcache以slab方式管理記憶體塊,從系統申請1mb大小的大塊記憶體並劃分為不同大小的 chunk,不同slab的chunk大小依次為80位元組,80 * 1.25,80 * 1.25^2, …。向memcache中新增item時,memcache會根據item的大小選擇合適的chunk。
oceanbase最初也採用lru演算法,只是記憶體管理有些不同。oceanbase向系統申請2mb大小的大塊記憶體,插入item時直接追加到最 後乙個2mb記憶體塊的尾部,當快取的記憶體量太大需要**時根據一定的策略整塊**2mb的記憶體,比如**最近最少使用的item所在的2mb記憶體塊。這樣 的做法雖然不是特別精確,但是記憶體管理簡單,對於系統初期很有好處。
快取鎖
快取需要操作兩個資料結構:hash表和lru鍊錶。多執行緒操作cache時需要加鎖,比較直接的做法是整體加一把大鎖後再操作hash表和lru鍊錶。有如下的優化思路:
1, hash表和lru鍊錶使用兩把不同的鎖,且hash表鎖的粒度可以降低到每個hash桶一把鎖。這種做法的難點是需要處理兩種資料結構不一致導致的問 題,假設操作順序為read hash -> del hash item -> del lru item -> read lru item,最後一次read lru item時item所在的記憶體塊可能已經被**或者重用,一般需要引入引用計數並考慮複雜的時序問題。
2, 採用多個lru鍊錶以減少lru表鎖粒度。hash表的鎖衝突可以通過增加hash桶的個數來解決,而lru鍊錶是乙個整體,難以分解。可以將快取的資料 分成多個工作集,每個item屬於某個工作集,每個工作集乙個lru鍊錶。這樣做的主要問題是可能不均衡,比如某個工作集很熱,某些從整體上看比較熱的數 據也可能被淘汰。
3, 犧牲lru的精確性以減少鎖。比如mysql中的lru演算法變形,大致如下:將lru鍊錶分成兩部分,前半部分和後半部分,如果訪問的item在前半部 分,什麼也不做,而不是像傳統的lru演算法那樣將item移動到鍊錶頭部;又如linux page cache中的clock演算法。oceanbase目前的快取演算法也是通過犧牲精確性來減少鎖。前面提到,oceanbase快取以2mb的記憶體塊為單位 進行淘汰,最開始採用lru策略,每次淘汰最近最少使用的item所在的2mb記憶體塊,然而,這樣做的問題是需要維護最近最少使用的item,即每次讀寫 快取都需要加鎖。後續我們將淘汰策略修改為:每個2mb的記憶體塊記錄乙個訪問次數和乙個最近訪問時間,每次讀取item時,如果訪問次數大於所有2mb內 存塊訪問次數的平均值,更新最近訪問時間;否則,將訪問次數加1。根據記錄的最近訪問時間淘汰2mb記憶體塊。雖然,這個演算法的快取命中率不容易評估,但是 快取讀取只需要一些原子操作,不需要加鎖,大大減少了鎖粒度。
lirs思想
cache有兩個問題:乙個是前面提到的降低鎖粒度,另乙個是提高精準度,或者稱為提高命中率。lru在大多數情況下表現是不錯的,但是有如下的問題:
1, 順序掃瞄。順序掃瞄的情況下lru沒有命中情況,而且會淘汰其它將要被訪問的item從而汙染cache。
2, 迴圈的資料集大於快取大小。如果迴圈訪問且資料集大於快取大小,那麼沒有命中情況。
之所以會出現上述一些比較極端的問題,是因為lru只考慮訪問時間而沒有考慮訪問頻率,而lirs在這方面做得比較好。lirs將資料分為兩部 分:lir(low inner-reference recency)和hir(high inner-reference recency),其中,lir中的資料是熱點,在較短的時間內被訪問了至少兩次。lirs可以看成是一種分級思想:第一級是hir,第二級是lir,數 據先進入到第一級,當資料在較短的時間內被訪問兩次時成為熱點資料則進入lir,hir和lir內部都採用lru策略。這樣,lir中的資料比較穩定,解 決了lru的上述兩個問題。lirs**中提出了一種實現方式,不過我們可以做一些變化,如可以實現兩級cache,cache元素先進入第一級 cache,當訪問頻率達到一定值(比如2)時公升級到第二級,第一級和第二級均內部採用lru進行替換。oracle buffer cache中的touch count演算法也是採用了類似的思想。
ssd與快取
ssd發展很快,大有取代傳統磁碟之勢。ssd的發展是否會使得單機快取變得毫無必要我們無從得知,目前,memory + ssd + 磁碟的混合儲存方案還是比較靠譜的。ssd使用可以有如下不同的模式:
1, write-back:資料讀寫都走ssd,記憶體中的資料寫入到ssd即可,另外有單獨的執行緒定期將ssd中的資料刷到磁碟。典型的代表如facebook flashcache。
2, write-through:資料寫操作需要先寫到磁碟,記憶體和ssd合在一起看成兩級快取,即cache中相對較冷的資料在ssd,相對較熱的資料在記憶體。
當然,隨著ssd的應用,我想減少快取鎖粒度的重要性會越來越突出。
總結&推薦資料
到目前為止,我們在ssd,快取相關優化的工作還是比較少的。今後的一年左右時間,我們將會投入一定的精力在系統優化上,相信到時候再來總結的時候 認識會更加深刻。我想,快取相關的優化工作首先要做的是根據需求制定乙個大致的評價標準,接著使用實際資料做一些實驗,最終可能會同時保留兩到三種實現方 式或者配置略微有所不同的快取實現。快取相關的推薦資料如下:
[1] touch count algorithm.
[2] lirs.
原文出處:nosqlnotes
快取設計 讀寫鎖場景實現
設計乙個快取系統 讀寫鎖的應用。jdk1.5自帶的讀寫鎖特性,讀與讀不互斥,讀與寫互斥,寫與寫互斥。為什麼要使用讀寫鎖?一句話概括那就是提高系統效能,如何提高呢?試想,對於所有對讀的操作是不需要執行緒互斥的,而如果方法內 使用了synchronized關鍵字同步以達到執行緒安全,對於所有的執行緒不管...
mysql鎖粒度是什麼意思 mysql鎖粒度是什麼
mysql鎖粒度就是我們通常所說的鎖級別。資料庫引擎具有多粒度鎖定,允許乙個事務鎖定不同型別的資源。mysql資料庫有三種鎖的級別,分別是 頁級鎖 表級鎖和行級鎖。資料庫引擎具有多粒度鎖定,允許乙個事務鎖定不同型別的資源。為了儘量減少鎖定的開銷,資料庫引擎自動將資源鎖定在適合任務的級別。鎖定在較小的...
匯流排鎖 快取鎖 MESI
隨著多核時代的到來,併發操作已經成了很正常的現象,作業系統必須要有一些機制和原語,以保證某些基本操作的原子性,比如處理器需要保證讀乙個位元組或寫乙個位元組是原子的,那麼它是如何實現的呢?有兩種機制 匯流排鎖定和快取一致性。我們知道,cpu和物理記憶體之間的通訊速度遠慢於cpu的處理速度,所以cpu有...