cache

什么是緩存

緩存就是數(shù)據(jù)交換的緩沖區(qū)(稱作Cache)拳芙,是存貯數(shù)據(jù)(使用頻繁的數(shù)據(jù))的臨時(shí)地方薛夜。有cpu緩存袱蚓、文件系統(tǒng)緩存枚碗、應(yīng)用層緩存等逾一;今天講的是應(yīng)用層緩存:應(yīng)用層緩存指的是從代碼層面上,通過代碼邏輯和緩存策略肮雨,實(shí)現(xiàn)對數(shù)據(jù)遵堵、頁面、圖片等資源的緩存怨规,可以根據(jù)實(shí)際情況選擇將數(shù)據(jù)存在文件系統(tǒng)或者內(nèi)存中陌宿,減少數(shù)據(jù)庫查詢或者讀寫瓶頸,提高響應(yīng)效率波丰。
緩存的本質(zhì)就是用空間換時(shí)間壳坪,犧牲數(shù)據(jù)的實(shí)時(shí)性,以服務(wù)器內(nèi)存中的數(shù)據(jù)暫時(shí)代替從數(shù)據(jù)庫讀取最新的數(shù)據(jù)掰烟,減少數(shù)據(jù)庫IO爽蝴,減輕服務(wù)器壓力沐批,減少網(wǎng)絡(luò)延遲,加快頁面打開速度蝎亚。

以下介紹一些常用緩存的設(shè)計(jì)模式(結(jié)構(gòu)模式)及數(shù)據(jù)模式(數(shù)據(jù)結(jié)構(gòu))

緩存設(shè)計(jì)模式

Cache-Aside

image.png

最常用模式九孩。應(yīng)用程序 先查 緩存,緩存存在 則直接返回颖对;不存在時(shí)查數(shù)據(jù)庫捻撑,緩存并返回。
缺點(diǎn):緩存與數(shù)據(jù)庫可能不一致缤底,一般需要設(shè)置緩存過期時(shí)間
這種一般用于數(shù)據(jù)變動(dòng)不太頻繁、或者實(shí)時(shí)性要求不高的場景番捂。

Read-Through Cache

image.png

類似cache-aside个唧,不同的是 緩存是獨(dú)立的,應(yīng)用程序不和數(shù)據(jù)庫直接打交道设预。一般結(jié)合下面的write-through一起使用

Write-Through Cache

image.png

數(shù)據(jù)修改時(shí)先將數(shù)據(jù)寫入緩存徙歼,緩存再更新到數(shù)據(jù)庫。與read-through結(jié)合可解決數(shù)據(jù)不一致問題鳖枕;麻煩的是數(shù)據(jù)變動(dòng)都要通知緩存變動(dòng)(如有多個(gè)途徑修改數(shù)據(jù)則比較麻煩)魄梯,還有就是數(shù)據(jù)變動(dòng)較多時(shí)數(shù)據(jù)庫壓力還是不小的,可用于數(shù)據(jù)表懂較少宾符,但一致性要求高的場景酿秸。

image.png

Write-Around

image.png

相對于write-through,它是由應(yīng)用程序?qū)⒕彺鎸懭霐?shù)據(jù)庫魏烫;配合cache-aside使用辣苏,更新也是由應(yīng)用程序發(fā)起的,但是先寫入數(shù)據(jù)庫哄褒,再更新緩存(不能如write-through一樣先更新緩存稀蟋,后更新數(shù)據(jù)庫;因?yàn)樵诟虏僮鱽砗竽派模绻彺嫖磳懭肭捌渌M(jìn)程/線程未命中緩存退客,就會(huì)查數(shù)據(jù)庫的舊數(shù)據(jù)并可能覆蓋數(shù)據(jù))

image.png

Write-Back

write-through改良,就是多次更新都是會(huì)只更新到緩存链嘀,特定時(shí)間/特定次數(shù)時(shí)才會(huì)更新到數(shù)據(jù)庫萌狂。能夠在多修改的場景下降低數(shù)據(jù)庫負(fù)擔(dān)。缺點(diǎn)是緩存崩潰時(shí)為持久化數(shù)據(jù)會(huì)丟失


image.png

緩存選擇

緩存的數(shù)據(jù)結(jié)構(gòu)

哈希表(散列表)

根據(jù)key 獲取/設(shè)置 value管闷,時(shí)間復(fù)雜度為O(1)
redis里h開頭的命令基本就是對哈希表的操作


image.png

集合類(數(shù)組/隊(duì)列)

根據(jù)index獲取/設(shè)置vale粥脚,根據(jù)index查找時(shí)間復(fù)雜度為O(1),根據(jù)value查找時(shí)間復(fù)雜度為O(n)
ruby數(shù)組是數(shù)組與隊(duì)列的結(jié)合包个,既可以使用index操作刷允,也可以進(jìn)行push,pop等操作
redis的list為雙向鏈表冤留,查找時(shí)間復(fù)雜度為o(n)


image.png

對可消費(fèi)資源進(jìn)行緩存,如消息隊(duì)列等树灶。特點(diǎn)是一次取一個(gè)值(非特定值)

有序集合類(搜索二叉樹/跳躍表)

常用的搜索二叉樹是紅黑樹纤怒,平衡了搜索二叉樹的退化和平衡二叉樹的維護(hù)開銷。
跳躍表就是在鏈表的基礎(chǔ)上增加了多級(jí)索引天通,從而實(shí)現(xiàn)查找時(shí)間復(fù)雜度為O(㏒n)
跳表對比紅黑樹

image.png

https://github.com/factoidforrest/dynamic-skiplist
redis的有序集合zset采用的就是跳表泊窘,zadd時(shí)附加score,查找時(shí)可按照score進(jìn)行查找(zrangebyscore)

他肯定比hash要慢像寒,但提供排序烘豹,以及按范圍查找數(shù)據(jù)。業(yè)務(wù)場景 如 定時(shí)任務(wù)诺祸、排行榜等携悯。
我們的業(yè)務(wù)我感覺 在 分配類業(yè)務(wù)時(shí),可根據(jù)不同的維度 使用不同的跳表筷笨;如分配公司時(shí)根據(jù) 員工 現(xiàn)有公司數(shù)憔鬼、重點(diǎn)公司數(shù)等進(jìn)行排序后分配,這部分?jǐn)?shù)據(jù)可以緩存并在數(shù)據(jù)變化時(shí)更新score胃夏;分配時(shí)按條件取到符合的 數(shù)據(jù)的交集轴或,這樣就減少了每次分配都要計(jì)算的時(shí)間消耗。

緩存淘汰策略

先進(jìn)先出 /FIFO(First Input First Output)

ruby直接hash就可實(shí)現(xiàn)仰禀,先設(shè)置的 shift即移除照雁。
缺點(diǎn):太過簡單粗暴,先緩存的 哪怕后來比其他使用更多悼瘾,時(shí)間更近依然會(huì)被淘汰囊榜。

最近最少使用/ LRU(Least Recently Used):

LRU算法又叫淘汰算法,根據(jù)數(shù)據(jù)歷史訪問記錄進(jìn)行淘汰數(shù)據(jù),其核心思想是“如果數(shù)據(jù)最近被訪問過,那么將來被訪問的幾率也更高”。
每次get時(shí)把最后訪問的放到隊(duì)尾亥宿,每次緩存滿時(shí)移除隊(duì)首緩存
缺點(diǎn):未考慮命中率的問題
https://github.com/SamSaffron/lru_redux

最不經(jīng)常使用/LFU(Least Frequently Used)

按照訪問次數(shù)卸勺,最近最少使用的緩存數(shù)據(jù),先淘汰烫扼。有多個(gè)最少使用的緩存數(shù)據(jù)曙求,再按照LRU淘汰。
缺點(diǎn):命中次數(shù)需要記錄映企,計(jì)算悟狱,多占用內(nèi)存和cpu。

# Observation

# LFU least frequently used
# key's frequency needs to be tracked

# get(key) -> exist -> freq ++
#          -> x -> -1
# put(k, v) -> exist -> modify the node val, freq ++
#           -> x -> store and evict (if size is full)
          
# maintain the HashMap of key the frequency, the value DDL of LRU within that frequency           
# 1. frequency map
# 2. node map
# get -> get node -> take that out of frequency map -> put that into the head of now appropriate frequency map list
# put -> (found) get node -> same as get
#     -> (new)   init node with frequency 1, put that into the map
#                -> (if full)
#                   freq_map[1], remove_from_tail
# {
#   1 => h -> 3 -> 2 -> t
#   2 => h -> 
# }

# ["LFUCache","put","put","get","put","get","get","put","get","get","get"]
# [[2],       [1,1],[2,2],[1],[3,3],[2],[3],[4,4],[1],[3],[4]]

Node = Struct.new(:key, :val, :usage, :next, :prev)
  
class DLinkedList
  def initialize
    @head = Node.new
    @tail = Node.new
    @head.next = @tail
    @tail.prev = @head
  end
  
  def empty?
    @head.next == @tail
  end
  
  def remove(node)
    node.prev.next = node.next
    node.next.prev = node.prev
    node.next = nil
    node.prev = nil
    node
  end
  
  def add_to_top(node)
    tmp = @head.next
    @head.next = node
    node.next = tmp
    tmp.prev = node
    node.prev = @head
  end
  
  def remove_from_tail
    raise if empty?
    remove(@tail.prev)
  end
end

class LFUCache
  def initialize(size)
    @size = size
    @memo = {} # key value map
    @freq_map = Hash.new { |h,k| h[k] = DLinkedList.new }
    @min_freq = nil
  end
  
  def get(key)
    return -1 if @size == 0 || @memo[key].nil?
    
    node = @memo[key]
    @freq_map[node.usage].remove(node)
    
    # if this has been recorded as min, needs to increment the min_freq lookup counter
    @min_freq += 1 if @freq_map[node.usage].empty? && @min_freq == node.usage
    
    node.usage += 1
    @freq_map[node.usage].add_to_top(node)
    node.val
  end
  
  def put(key, value)
    return if @size == 0
    
    if node = @memo[key]
      node.val = value
      get(key)
    else
      evict if @memo.keys.length == @size
      
      new_node = Node.new(key, value, 1)
      @freq_map[1].add_to_top(new_node)
      @min_freq = 1
      @memo[key] = new_node
    end
  end
  
  private
  
  def evict
    deleted = @freq_map[@min_freq].remove_from_tail
    @memo.delete(deleted.key)
  end
end

參考:https://cloud.tencent.com/developer/article/2077083

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末堰氓,一起剝皮案震驚了整個(gè)濱河市挤渐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌双絮,老刑警劉巖浴麻,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件得问,死亡現(xiàn)場離奇詭異,居然都是意外死亡软免,警方通過查閱死者的電腦和手機(jī)宫纬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來膏萧,“玉大人漓骚,你說我怎么就攤上這事¢环海” “怎么了蝌蹂?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長曹锨。 經(jīng)常有香客問我叉信,道長,這世上最難降的妖魔是什么艘希? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮硅急,結(jié)果婚禮上覆享,老公的妹妹穿的比我還像新娘。我一直安慰自己营袜,他們只是感情好撒顿,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著荚板,像睡著了一般凤壁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上跪另,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天拧抖,我揣著相機(jī)與錄音,去河邊找鬼免绿。 笑死唧席,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的嘲驾。 我是一名探鬼主播淌哟,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼辽故!你這毒婦竟也來了徒仓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對情侶失蹤誊垢,失蹤者是張志新(化名)和其女友劉穎掉弛,沒想到半個(gè)月后症见,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡狰晚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年筒饰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片壁晒。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瓷们,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出秒咐,到底是詐尸還是另有隱情谬晕,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布携取,位于F島的核電站攒钳,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏雷滋。R本人自食惡果不足惜不撑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望晤斩。 院中可真熱鬧焕檬,春花似錦、人聲如沸澳泵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽兔辅。三九已至腊敲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間维苔,已是汗流浹背碰辅。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蕉鸳,地道東北人乎赴。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像潮尝,于是被迫代替她去往敵國和親榕吼。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • CPU Cache 今天的CPU比25年前更復(fù)雜勉失。那時(shí)候羹蚣,CPU內(nèi)核的頻率與內(nèi)存總線的頻率相當(dāng)。內(nèi)存訪問只比寄存器...
    blueshadow閱讀 2,983評(píng)論 0 5
  • title: Cache 常見問題 tags: cache categories: Tech comments: ...
    zhaif閱讀 2,522評(píng)論 0 1
  • 實(shí)現(xiàn)方法:1乱凿、LinkedHashMap顽素。2咽弦、雙向鏈表+Mapcache寫機(jī)制:Write-through、wri...
    柚子過來閱讀 254評(píng)論 0 1
  • 原網(wǎng)址:https://github.com/facebook/rocksdb/wiki/Block-Cache[...
    i_need_job閱讀 396評(píng)論 0 0
  • 按照業(yè)務(wù)需求, 在業(yè)務(wù)層緩存一定的數(shù)據(jù)以提升性能. 同時(shí)還要保持緩存數(shù)據(jù)和底層數(shù)據(jù)的一致性. 解決的問題 業(yè)務(wù)端通...
    GongMeng閱讀 5,423評(píng)論 0 1