【轉】緩存設計的一些思考

***{轉自:緩存設計的一些思考}***算法

  互聯網架構中緩存無處不在,某廠牛人曾經說過:」緩存就像清涼油,哪裏不舒服,抹一下就行了」。高品質的存儲容量小,價格高;低品質存儲容量大,價格低,緩存的目的就在於」擴充」高品質存儲的容量。本文探討緩存相關的一些問題。sql

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內存塊。這樣的作法雖然不是特別精確,可是內存管理簡單,對於系統初期頗有好處。架構

緩存鎖nosql

  緩存須要操做兩個數據結構: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內存塊。雖然,這個算法的緩存命中率不容易評估,可是緩存讀取只須要一些原子操做,不須要加鎖,大大減小了鎖粒度。
  4. 批量操做。緩存命中時不須要當即更新LRU鏈表,而是能夠將命中的item保存在線程Buffer中,積累了必定數量後一次性更新LRU鏈表。

LIRS思想spa

  Cache有兩個問題:一個是前面提到的下降鎖粒度,另外一個是提升精準度,或者稱爲提升命中率。LRU在大多數狀況下表現是不錯的,可是有以下的問題:.net

  • 順序掃描。順序掃描的狀況下LRU沒有命中狀況,並且會淘汰其它將要被訪問的item從而污染cache。
  • 循環的數據集大於緩存大小。若是循環訪問且數據集大於緩存大小,那麼沒有命中狀況。

  之因此會出現上述一些比較極端的問題,是由於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. http://youyus.com/wp-content/uploads/resource/Shallahamer%20TC4a.pdf

[2] LIRS. http://portal.acm.org/citation.cfm?id=511340

相關文章
相關標籤/搜索