Redis(開發與運維):53---緩存設計之(緩存的收益和成本、緩存更新策略(剔除算法、超時剔除、主動更新)、緩存粒度控制)

一、緩存的收益和成本

  • 下圖左側爲客戶端直接調用存儲層的架構,右側爲比較典型的緩存層+存儲層架構,下面分析一下緩存加入後帶來的收益和成本

  • 收益如下:
    • 加速讀寫:因爲緩存通常都是全內存的(例如Redis、Memcache),而 存儲層通常讀寫性能不夠強悍(例如MySQL),通過緩存的使用可以有效 地加速讀寫,優化用戶體驗。
    • 降低後端負載:幫助後端減少訪問量和複雜計算(例如很複雜的SQL 語句),在很大程度降低了後端的負載
  • 成本如下:
    • 數據不一致性:緩存層和存儲層的數據存在着一定時間窗口的不一致性,時間窗口跟更新策略有關
    • 代碼維護成本:加入緩存後,需要同時處理緩存層和存儲層的邏輯, 增大了開發者維護代碼的成本
    • 運維成本:以Redis Cluster爲例,加入後無形中增加了運維成本
  • 緩存的使用場景基本包含如下兩種:
    • 開銷大的複雜計算:以MySQL爲例子,一些複雜的操作或者計算(例 如大量聯表操作、一些分組計算),如果不加緩存,不但無法滿足高併發量,同時也會給MySQL帶來巨大的負擔
    • 加速請求響應:即使查詢單條後端數據足夠快(例如select*from table where id=),那麼依然可以使用緩存,以Redis爲例子,每秒可以完成數萬次讀寫,並且提供的批量操作可以優化整個IO鏈的響應時間

二、緩存更新策略

  • 緩存中的數據通常都是有生命週期的,需要在指定時間後被刪除或更新,這樣可以保證緩存空間在一個可控的範圍
  • 但是緩存中的數據會和數據源中的真實數據有一段時間窗口的不一致,需要利用某些策略進行更新
  • 下面將分別從使用場景、一致性、開發人員開發/維護成本三個方面介紹三種緩存的更新策略

①LRU/LFU/FIFO算法剔除

  • 使用場景:剔除算法通常用於緩存使用量超過了預設的最大值時候,如何對現有的數據進行剔除。例如Redis使用maxmemory-policy這個配置作爲內存最大值後對於數據的剔除策略
  • 一致性:要清理哪些數據是由具體算法決定,開發人員只能決定使用哪種算法,所以數據的一致性是最差的
  • 維護成本:算法不需要開發人員自己來實現,通常只需要配置最大maxmemory和對應的策略即可。開發人員只需要知道每種算法的含義,選擇適合自己的算法即可
  • 關於maxmemory-policy和maxmemory可以參閱:https://blog.csdn.net/qq_41453285/article/details/106199033

②超時剔除

  • 使用場景:超時剔除通過給緩存數據設置過期時間,讓其在過期時間後自動刪除,例如Redis提供的expire命令。如果業務可以容忍一段時間內,緩存層數據和存儲層數據不一致,那麼可以爲其設置過期時間。在數據過期後,再從真實數據源獲取數據,重新放到緩存並設置過期時間。例如一個視頻的描述信息,可以容忍幾分鐘內數據不一致,但是涉及交易方面的業務, 後果可想而知
  • 一致性:一段時間窗口內(取決於過期時間長短)存在一致性問題,即緩存數據和真實數據源的數據不一致
  • 維護成本:維護成本不是很高,只需設置expire過期時間即可,當然前 提是應用方允許這段時間可能發生的數據不一致

③主動更新

  • 使用場景:應用方對於數據的一致性要求高,需要在真實數據更新後, 立即更新緩存數據。例如可以利用消息系統或者其他方式通知緩存更新
  • 一致性:一致性最高,但如果主動更新發生了問題,那麼這條數據很可能很長時間不會更新,所以建議結合超時剔除一起使用效果會更好
  • 維護成本:維護成本會比較高,開發者需要自己來完成更新,並保證更新操作的正確性
  • 下圖給出了緩存的三種常見更新策略的對比:

  • 有兩個建議:
    • 低一致性業務建議配置最大內存和淘汰策略的方式使用
    • 高一致性業務可以結合使用超時剔除和主動更新,這樣即使主動更新出了問題,也能保證數據過期時間後刪除髒數據

三、緩存粒度控制

  • 下圖是很多項目關於緩存比較常用的選型,緩存層選用Redis,存儲層選用MySQL

“緩存粒度”概念說明

  • 例如現在需要將MySQL的用戶信息使用Redis緩存,可以執行如下操作:
  • 從MySQL獲取用戶信息:
select * from user where id={id};
  • 將用戶信息緩存到Redis中:
set user:{id} 'select * from user where id={id}'
  • 假設用戶表有100個列,需要緩存到什麼維度呢?
  • 緩存全部列:
set user:{id} 'select * from user where id={id}'
  • 緩存部分重要列:
set user:{id} 'select {important Column1}, {important Column2} ,...{important ColumnN} from user where id={id}'
  • 上述這個問題就是緩存粒度問題
  • 究竟是緩存全部屬性還是隻緩存部分重要屬性呢?下面將從通用性、空間佔用、代碼維護三個角度進行說明:
    • 通用性。緩存全部數據比部分數據更加通用,但從實際經驗看,很長時 間內應用只需要幾個重要的屬性
    • 空間佔用。緩存全部數據要比部分數據佔用更多的空間,可能存在以下 問題:
      • 全部數據會造成內存的浪費
      • 全部數據可能每次傳輸產生的網絡流量會比較大,耗時相對較大,在極端情況下會阻塞網絡
      • 全部數據的序列化和反序列化的CPU開銷更大。
    • 代碼維護。全部數據的優勢更加明顯,而部分數據一旦要加新字段需要修改業務代碼,而且修改後通常還需要刷新緩存數據
  • 下圖給出緩存全部數據和部分數據在通用性、空間佔用、代碼維護上的對比:

  • 緩存粒度問題是一個容易被忽視的問題,如果使用不當,可能會造成很多無用空間的浪費,網絡帶寬的浪費,代碼通用性較差等情況,需要綜合數 據通用性、空間佔用比、代碼維護性三點進行取捨
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章