緩存詳解

1.緩存

緩存,數據交換的緩沖區(qū),針對緩沖對象的不同(不同的硬件)都可以構建緩存。

目的是,把讀寫速度慢的介質數據保存在讀寫速度快的介質中,從而提高讀寫的速度,減少時間消耗。例如:

  • CPU高速緩存:高速緩存的讀寫速度遠高于內存。
    1. CPU讀數據時,如在高速緩存中找到所需數據,就不需要讀內存
    2. CPU寫數據是,先寫入到高速緩存中,在回寫到內存
  • 磁盤緩存:磁盤緩存其實就把常用的磁盤數據保存在內存中,內存的讀寫數據也遠高于磁盤的
    1. 讀數據時從內存中去取
    2. 寫數據時,可以先寫到內存中,定時或者定量回寫到磁盤,或者同步回寫。

目的:使用緩存的目的就是為了提升讀寫性能。在實際的業(yè)務場景中就是為了提升讀性能,帶來更好的性能,更高的并發(fā)量

2.緩存算法

  1. LRU(least recentlty used,最近最少使用)算法
  2. LFU(least Frequently used,最不經常使用)
  3. FIFO(first in first out,先進先出)

3.緩存穿透

存儲穿透:是指查詢一個一定不存在的數據,由于緩存是不命中時被動寫,并且處于容錯考慮,如果從DB查不到數據就不寫入緩存,這將導致這個不存在的數據一直去請求DB,是去緩存的意義。

被動寫:當從緩存中查詢不到數據的時候,從DB查到數據,然后將數據寫入到緩存。

image

如何解決?

  1. 方案一,緩存空對象。

    當從DB查詢數據為空的時候,我們仍然將這個結果進行緩存,具體的值需要使用特殊的標識,能和真正的緩存數據區(qū)分開。另外需要設置較短的過期時間,不建議超過5分鐘。緩存時間太久沒意義,浪費緩存內存。

  2. 方案二,BloomFilter布隆過濾器

    在緩存服務的基礎上,構建BloomFilter數據結構。在BloomFilter中存儲對應的key是否存在,如果存在,再說明該key的值不為空,邏輯如下:

    1. 根據key查詢BloomFilter,如果不存在對應的值,直接返回。如果存在,繼續(xù)向下執(zhí)行。
    2. 根據key的值,查詢緩存的值,如果存在,直接返回,不存在,向下執(zhí)行。
    3. 查詢db的值,如果存在,更新到緩存,直接返回。

    為什么BloomFilter不存儲Key是不存在的情況?

    1. BloomFilter存在誤判。簡單來說,存在的不一定存在,不存在的不一定不存在。一個誤判的key就會被誤判為不存在。
    2. BloomFilter不允許刪除。如果一個key開始不存在,后面又有數據了,這時候會被判斷為一致不存在。
    緩存空對象 BloomFilter
    使用場景 1.數據命中率不高;2.保證一致性 1.數據命中不高。2數據相對固定,實時性低
    維護成本 1.代碼維護簡單;2.需要過多的緩存空間;3.數據不一致 1.代碼維護復雜;2.緩存空間占用少

4.緩存雪崩

概念

緩存雪崩,是指緩存由于某些原因無法提供服務(比如緩存掛了),所有的請求到達DB,導致DB負荷增大,最終掛掉的情況。

如何解決

  1. 緩存高可用

    通過搭建緩存的高可用,避免緩存掛掉導致無法提供服務的情況,從而降低出現緩存雪崩的情況。Redis可以通過搭建Redis Sentinel或者Redis Cluster來做緩存高可用

  2. 本地緩存

    使用本地緩存,即使分布式緩存掛了,也可以將DB查詢到的數據緩存到本地,避免后續(xù)請求全部到達DB。

  3. 請求DB限流

    通過限制DB的每秒請求數,避免DB掛掉,這樣做的好處:

    • 有一部分用戶任然可以使用該系統(tǒng)。
    • 緩存服務恢復后,立即可以使用。不用再去管理DB服務

    被限流的請求,我們可以通過服務降級,提供一些默認的值,比如空白頁面、友情提示等到。我們可以通過Sentinel、Hystrix等來實現。

引入本地緩存的問題

  • 本地緩存實時性如何保證
    • 引入消息隊列。在數據更新時,發(fā)布數據更新的消息;而進程中有相應的消費者消費該消息,從而更新本地緩存;
    • 設置較短的過期時長,請求從DB拉取數據;
    • 通過手動過期
  • 每個進程可能會在本地緩存相同的數據,導致資源浪費?
    • 需要配置本地的緩存過期策略和緩存數量上限。

5.緩存擊穿

概念

緩存擊穿,是指在某個熱點數據在某個時間點過期的時候,恰好這個時間對這個key有大量的的并發(fā)請求過來,這些請求發(fā)現緩存過期,都會去請求到DB加載數據并回寫到緩存,這個時候過大的并發(fā)就有可能瞬間把DB壓垮。

如何解決

  1. 方案一,使用互斥鎖

    請求發(fā)現緩存不存在后,去查詢DB前,使用分布式鎖,保證只有一個線程拿到鎖,然后取請求DB,然后更新寫入緩存

    • 1獲取分布式鎖,直到成功或者超時。如果超時則拋出異常,成功,繼續(xù)向下執(zhí)行
    • 2、獲取緩存,如果存在值,直接返回,如果不存在,繼續(xù)。
    • 3、查詢DB,更新到緩存,返回值。
  2. 方案二,手動過期

    緩存上不設置過期時間,功能上將過期時間設置在Value中。流程如下

    • 1、獲取緩存,通過value中的時間來比較是否過期。如果未過期,直接返回,如果過期,繼續(xù)向下執(zhí)行。
    • 2、通過一個后臺的異步線程進行緩存的構建,也就是“手動過期”。通過后臺的異步線程,保證只有一個線程查詢DB。
    • 3、同時,雖然Value還是過期,但還是直接返回。通過這樣的方式保證了服務的可用性,但是損失了一定的實效性。

6.緩存與DB一致性保持

產生不一致的原因

  1. 并發(fā)場景下,導致老的DB數據寫入到緩存中。

    這里指的是,更新DB之前,先刪除Cache中的數據。在低并發(fā)的情況下,不會出問題,但是在高并發(fā)的情況下就會出問題。在刪除Cache和更新DB之間,這里恰好有請求進來,這時候使用被動讀,因為在DB中的數據還是老數據,這時候又會將老數據寫入到緩存中。

  2. 緩存和DB的操作不在同一個事物中,可能DB操作成功,二cache操作失敗,這樣會導致不一致

解決方案

  1. 將緩存可能出現的并行寫,實現串行寫

    這里指的緩存并行寫。在被動讀中,如果緩存不存在,也存在寫。

    1. 在寫請求前,先淘汰緩存之前,先獲取分布式鎖。

      寫-->獲取鎖-->delete cache-->update db ---> write cache

    2. 在讀請求時,如果緩存不存在,先去獲取分布式鎖

      讀-->讀cache,如果null-->獲取鎖-->查詢cache,如果null-->查db-->write cache

  2. 實現緩存的最終一致性。

    1. 先淘汰緩存,在寫數據庫。

      因為先淘汰緩存,所以數據的最終一致性是可以保證的。為什么尼?先淘汰緩存,即使寫數據庫發(fā)生異常,在下次緩存讀取時候,多讀取一次數據庫。

    2. 先寫數據庫,在更新緩存

      需要保證寫數據庫和更新緩存的操作,能夠在一個”事務“中,從而實現最終一致性。

      基于定時任務來實現

      • 首先,寫入數據庫
      • 然后在寫入數據庫所在的事物中,插入一個記錄的任務表,該記錄表存儲需要存入cache中的key和value
      • 【異步】遍歷任務表,寫入cache

      基于消息隊列來實現

      • 首先,寫入數據庫
      • 然后發(fā)送帶我緩存key和value的事物消息。
      • 【異步】消費者消費該消息,寫入緩存

      基于數據庫的binlog日志

      image
      • 應用直接將數據寫入數據庫
      • 數據庫會更新binlog日志
      • 利用Cannal中間件讀取binlog日志
      • Cannal借助于限流主鍵按頻率將數據發(fā)送到MQ中
      • 應用監(jiān)控MQ渠道,將MQ的數據緩存到cache中

7.緩存預熱

啟動時,先將熱點數據緩存到緩存中

如何實現

  1. 數據量不大的時候,項目啟動,自動進行初始化
  2. 通過修復腳本,執(zhí)行腳本
  3. 寫管理頁面,手動操作

7.緩存淘汰策略

  1. 定時清理
  2. 當用戶請求過來的時候,去判斷是否過期,過期的話就去底層系統(tǒng)獲取數據,進行更新

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容