熱點數據多級緩存方案實現(進行中)


熱點數據多級緩存方案實現

集成CountMinSketch過濾器+本地緩存caffeine+redis緩存+數據庫的多級緩存方案

涉及技術點:

  1. caffeine本地緩存
  2. redis:lua腳本、redis事務的原子性
  3. CountMinSketch算法,原來已有相似技術 counting Bloom filter
  4. 設計思想:計算向數據端遷移

1,背景概述

我們系統在使用過程中,並非所有的數據都是時刻活躍的狀態,一般情況下只有少部分的活躍數據占據大量的請求。所以,我們在將其他非活躍數據也存放於redis緩存或者本地緩存時,是非常浪費內存資源的一種方式(錢多、機器多的可以無視這個)。

所以為了解決上述浪費內存資源的情況,我們一般將熱點數據(當前時間段訪問頻繁的數據)放入緩存中從而用於加快數據的響應速度,舉一個生活中的實例:我們平時看視頻網站中的視頻,如果是當前熱播的視頻,我們點開后加載速度相對於那些冷門的視頻速度快很多。這個就是因為這些視頻網站,將熱門視頻數據也是放置在緩存中,冷門數據還是存儲在磁盤中(節省費用)。

當前熱點數據緩存主要有如下幾個解決方案:

  1. 使用本地緩存,例如caffeine、guava、或者自定義緩存。
    • 它們直接在單節點服務中使用堆內存進行熱點數據的緩存;
    • 優點:響應及時、讀並發響應及時;
    • 缺點:
      • ①消耗堆內存;
      • ②如果緩存中不存在需要查詢數據庫(緩存穿透),如果是多節點服務,每個節點都需要重復查詢數據庫,造成數據庫查詢並發量高
      • 已刪除數據不容易進行多節點同步
  2. 使用redis緩存
    • 直接使用redis中間件的內存操作功能實現熱點數據緩存
    • 優點:可以響應多節點請求與數據一致性同步
    • 缺點:
      • 相較於本地緩存來說,此方案需要額外的網絡通信耗時
      • ②由於redis的單線程數據操作,當有大key等耗時指令時,其他請求需要進行排序等待;
      • ③需要依賴中間件;
      • 並發相對於本地緩存來說要低一些
      • ⑤如果緩存中不存在需要查詢數據庫(緩存穿透);
      • 熱點數據的緩存策略算法沒有caffeine等緩存框架優秀(redis只有單純的LRU、LFU、TTL等簡單驅逐策略),熱點數據命中率低;

另外,在進行數據緩存時,我們還經常遇到緩存穿透的問題(查詢不存在或者冷門的數據,這個操作會查詢至數據庫,並發量如果很高容易導致服務崩潰),一般情況下我們可以使用布隆過濾器來解決該問題,但是布隆過濾器也有自身的問題(只能新增數據,不能刪除歷史數據)。

因此總結上述常用緩存方案存在的問題:

  1. 兩種方案都存在的緩存穿透問題,同時使用布隆過濾器解決緩存穿透又無法刪除歷史數據;
  2. 本地緩存與redis緩存各有優缺點;

這里為了解決上述的兩個問題提出了如下的方案【集成CountMinSketch過濾器+本地緩存caffeine+redis緩存+數據庫的多級緩存方案】:

  1. 利用Count-Min Sketch算法解決緩存穿透的問題:
    • ①與布隆過濾器一樣,解決查詢客觀不存在數據時,直接穿透緩存-查詢數據庫的問題;
    • ②優化布隆過濾器中,不能刪除歷史數據的缺陷;
  2. 利用多級緩存解決之前2種緩存方案存在的缺陷:
    • ①多節點查詢客觀存在的數據,如果本地緩存不存在該數據,現在是直接查詢redis緩存,由redis緩存只查詢一次數據庫;
    • ②由於存在redis用於存儲CountMinSketch過濾器的key是否存在的數據信息,並且該過濾器中的歷史數據可以被刪除,所以保證了每個節點的本地緩存數據一致性;
    • ③現在使用本地緩存消除了之前redis緩存需要額外網絡通信的問題,並且繼承了本地緩存的緩存算法優勢,保證了緩存命中率;

image-20220417173119511

同時,也需要額外說明該實現的方案的缺點:

  1. 實現相對復雜,邏輯依賴redis和數據庫
    • 解決方案:后續將Caffeine本地緩存和redis緩存的操作提取出來,然后利用AOP切面技術來代理,程序員使用該框架時只需要在dao層方法,加一個注解即可,無需大量代碼邏輯改動
  2. 當前不支持數據的修改,只支持刪除和新增
    • 解決方案:后續可以通過RPC調用或者http調用的方式,實現單節點修改數據庫中的value值后,先修改redis中的value值,然后同步刪除其他節點本地緩存數據。從而當其他節點查詢相同數據時,可以從redis中獲取更新數據,同步更新至本地緩存

maven依賴:

<dependency>
    <groupId>site.activeclub</groupId>
    <artifactId>cache-multipe</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

核心注解:

(將如下注解分別添加至dao層的對應的方法上,無需關注內部實現)

@ActiveclubCacheSelect // 查詢

@ActiveclubCacheUpdate // 修改

@ActiveclubCacheDelete // 刪除

核心配置:

activeclub.cache.local.enable=true # 默認值為true,false表示不使用本地緩存

activeclub.cache.redis.enable=true # 默認值為true,false表示不使用redis緩存

activeclub.cache.sync.enable=true # 修改操作是否進行數據同步

activeclub.cache.sync.type=http # 默認使用http請求方式,如果選擇rpc需要額外依賴dubbo

2,技術原理

本處的多級緩存方案的結構圖如下:

image-20220417151719520

邏輯流程圖:

image-20220417153347873

流程描述:

  1. 外部查詢請求進入,先查詢CountMinSketch過濾器

    • 如果過濾器中,不存在該數據,直接返回null,然后結束流程
    • 如果過濾器中,存在該數據,直接進入第2步
  2. 查詢caffeine本地緩存

    • 如果本地緩存中,存在該數據,直接返回數據,然后結束流程
    • 如果本地緩存中,不存在該數據,則進行第3步進行查詢,
      • 查詢結果為null,則直接返回給調用方
      • 查詢結果不為null,則更新至本地緩存后,返回結果數據,然后結束流程
  3. 查詢redis緩存

    • 如果redis緩存中,存在該數據,重置過期時間,返回該數據
    • 如果redis緩存中,不存在該數據,則進入第4步進行查詢,
      • 查詢結果為null,直接返回給調用方
      • 查詢結果不為null,則更新至redis緩存后,返回給數據調用方
  4. 查詢db數據庫

    • 如果db數據庫中,存在該數據,則直接返回給調用方
    • 如果db數據庫中,不存在該數據,則將該key值對應於CountMinSketch過濾器中數據自減1,然后將null值返回給調用方

3,代碼實現

4,注意事項

5,疑問解答

  1. 之前面試的時候,有面試官認為查詢CountMinSketch過濾器的時候,都已經查詢過redis,那么為什么不在這次查詢的時候直接把數據返回?反而搞這么復雜的邏輯呢?
    • ①首先,我們已經簡述過本地緩存caffeine相對於redis緩存的優勢,這個是我們為什么盡量使用caffeine本地緩存,而將redis緩存作為輔助的原因。
      • caffeine的緩存算法更優,命中率更高,並發量也更高(算法優勢
      • 本地緩存查詢數據直接使用的堆內存數據,無需額外網絡通信消耗,響應更快(類比於計算機CPU的三級緩存機制)
    • ②其次,我們查詢redis中的CountMinSketch過濾器時是將運算向數據遷移,最終只需要獲取redis返回的該key值是否存在的布爾值即可,網絡消耗很少(后續還可以進一步在redis中自定義查詢函數,更加精簡查詢命令傳輸)

參考鏈接


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM