天天看點

redis清理緩存_Redis緩存設計與優化

介紹

緩存帶來了加速讀寫,降低後端負載的好處外,同時也存在一定的成本,比如資料不一緻,緩存層和資料層有時間視窗不一緻,和更新政策有關;代碼維護成本多了一層緩存邏輯;以及運維成本,例如Redis Cluster等。是以在實際的使用中,我們需要區分場景合理使用緩存邏輯。同時緩存對粒度控制分緩存全部資料和部分重要資料:

  • 通用性:全量屬性更好
  • 占用空間:部分屬性更好
  • 代碼維護上:表面上全量屬性更好

一、緩存适用場景

緩存的适用場景示例:

  • 對高消耗的SQL:join結果集/分組統計結果緩存
  • 加速請求響應:利用Redis/Memcache優化IO響應時間
  • 大量寫合并為批量寫:如計數器先Redis累加再批量寫DB

二、緩存更新政策

緩存的更新政策:

  • 控制最大記憶體情況下,LRU/LFU/FIFO算法剔除:例如maxmemory-policy
  • 逾時剔除:例如expire
  • 主動更新:開發控制生命周期

三種緩存更新政策對比:

政策 一緻性 維護成本
LRU/LIRS算法剔除 最差
逾時剔除 較差
主動更新

使用建議:

  • 低一緻性:最大記憶體和淘汰政策
  • 高一緻性:逾時剔除和主動更新結合,最打記憶體和淘汰政策兜底

除了緩存伺服器自帶的緩存失效政策之外,我們還可以根據具體的業務需求進行自定義的緩存淘汰,常見的政策有兩種:

  • 定時去清理過期的緩存
  • 當有使用者請求過來時,再判斷這個請求所用到的緩存是否過期,過期的話就去底層系統得到新資料并更新緩存

兩者各有優劣,第一種的缺點是維護大量緩存的key是比較麻煩的,第二種的缺點就是每次使用者請求過來都要判斷緩存失效,邏輯相對比較複雜。

二、緩存穿透優化緩存穿透最常見的場景就是通路根本就不存在的資料。一般是黑客故意去請求緩存中不存在的資料,導緻所有的請求都落到資料庫上,造成資料庫短時間内承受大量請求而崩掉。

redis清理緩存_Redis緩存設計與優化

原因:

  • 業務代碼自身問題,空變量
  • 惡意攻擊、爬蟲等

解決:

1. 緩存空對象+過期時間

redis清理緩存_Redis緩存設計與優化

存在的問題:

  • 需要更多的鍵
  • 緩存層和存儲層資料短期不一緻

示例代碼:

public String getPassThrough(String key) {  String cacheValue = cache.get(key);  if (StringUtils.isBlank(cacheValue)) {    String storageValue = storage.get(key);    cache.set(key, storageValue);    //如果存儲資料為空,需要設定一個過期時間(300秒)    if (StringUtils.isBlack(storageValue)) {      cache.expire(key, 60 * 5);    }    return storageValue;  } else {    return cacheValue;  }}
           

2. 布隆過濾器攔截

最常見的則是采用布隆過濾器,将所有可能存在的資料哈希到一個足夠大的bitmap中,一個一定不存在的資料會被 這個bitmap攔截掉,進而避免了對底層存儲系統的查詢壓力。

比如10億電話本判斷電話在不在電話本中,使用很少的記憶體解決這個問題。在cache層之前增加了布隆過濾器,如果布隆過濾器過濾掉了則說明這個key是無效的,直接傳回,如果沒被過濾,則從cache層去拿資料。

redis清理緩存_Redis緩存設計與優化

三、緩存無底洞問題優化

有這麼一個場景,已經存在了很多Redis或者Memcache服務節點,發現加機器性能沒提示反而下降:http://highscalability.com/blog/2009/10/26/facebooks-memcached-multiget-hole-more-machines-more-capacit.html

問題關鍵點:

  • 更多的機器!=更高的性能
  • 批量接口需求(mget、mset等)
  • 資料增長與水準擴充需求

是以原因就是批量操作的變化,當隻有一個節點是,一個mget操作是有一次網絡IO,當階段擴大到3個時候,使用順序IO方式的話,一次mget的操作會随着機器節點的個數增加而網絡傳輸次數也越來越多,對用戶端執行效率帶來很大的下降。實際上IO由于擴容從原來的o(1)增加到了o(node)。

優化IO的幾種方法:

  • 指令本身優化:例如慢查詢keys、hgetall bigkey
  • 減少網絡通信次數
  • 降低接入成本:例如用戶端長連結/連接配接池、NIO等
  • 串行mget
  • 串行io
  • 并行io
  • hash_tag

串行mget、串行io、并行io以及hash_tag介紹詳見【Redis Cluster高可用叢集模式】

四種方案優缺點對比:

方案 優點 缺點 網絡IO
串行mget 少量keys滿足需求 大量keys請求延遲嚴重 o(keys)
串行IO 少量節點滿足需求 大量nodes延遲嚴重 o(nodes)
并行IO 延遲取決于最慢的節點 逾時定位問題複雜 o(max_slow(node))
hash_tag 性能最高 讀寫增加tag維護成本,tag分布容易出現資料傾斜 o(1)

四、緩存雪崩問題優化

當流量洪峰到達時,緩存同一時間大面積的失效,是以,後面的請求都會落到資料庫上,造成資料庫短時間内承受大量請求而崩掉,就是緩存雪崩。

redis清理緩存_Redis緩存設計與優化

解決方法:

  • 事前:盡量保證整個 redis 叢集的高可用性,如采用Redis Cluster架構,發現機器當機盡快補上。選擇合适的記憶體淘汰政策
  • 事中:本地cache緩存 + hystrix限流&降級,避免MySQL崩掉
  • 事後:利用 redis 持久化機制儲存的資料盡快恢複緩存
  • 對緩存進行實時監控,當請求通路的慢速度比超過門檻值,及時報警,通過自動故障轉移,服務降級,停止部分非核心接口的通路
  • 提前壓測預估系統處理能力,做好限流與服務降級

五、緩存預熱優化

緩存預熱就是系統上線後,将相關的緩存資料直接加載到緩存系統。這樣就可以避免在使用者請求的時候,先查詢資料庫,然後再将資料緩存的問題,使用者直接查詢事先被預熱的緩存資料。解決思路:

  • 直接寫個緩存重新整理頁面,上線時手工操作下
  • 資料量不大,可以在項目啟動的時候自動進行加載
  • 定時重新整理緩存

六、熱點key重建優化

熱key重建指的是開發人員設定好的緩存過期時間過了,需要重新建構緩存。熱key說明目前可能有大量的請求,同時通路同一個key,而且這個并發量特别大,緩存失效的瞬間可能會有大量的線程來重建緩存,造成後端資料庫壓力暴增。

問題描述:熱點key+較長的重建時間。

redis清理緩存_Redis緩存設計與優化

存在問題:大量的線程都會做緩存重建和查詢資料源。

解決方法:

1. 互斥鎖(mutex key)

通過設定互斥鎖,統一時間隻允許一個請求進行熱key的重建。如基于redis的setnx指令實作

redis清理緩存_Redis緩存設計與優化

存在問題:不需要大量重建工作,但是存在大量線程等待的問題。

示例代碼:

String get(String key) {  String value = redis.get(key);  if (value == null) {    String mutexKey = "mutex:key:" + key;    if (redis.set(mutexKey, "1", "ex 180", "nx")) {       value = db.get(key);       redis.set(key,value);       redis.delete(mutexKey);    } else {       //其他線程休息50毫秒後重試       Thread.sleep(50);       get(key);    }  }  return value;}
           

2. 永不過期

redis清理緩存_Redis緩存設計與優化

為每個value添加邏輯過期時間,發現超過邏輯過期時間後,會使用單獨的線程去建構緩存,但是存在緩存不一緻情況。示例代碼:

String get(final String key) {  V v = redis.get(key);  String value = v.getValue();  long logicTimeout = v.getLogicTimeout();  if (logicTimeout >= System.currentTimeMills()) {    String mutexKey = "mutex:key:" + key;    if (redis.set(mutexKey, "1", "ex 180", "nx")) {       //異步更新背景異步執行       threadPool.execute(() -> {         String dbValue = db.get(key);         redis.set(key,dbValue, newLogicTimeout());         redis.delete(mutexKey);       });    }  }  return value;}
           

3. 方案對比

方案 優點 缺點
互斥鎖 保證一緻性 代碼複雜,存在死鎖風險
永遠不過期 基本杜絕熱點key重建問題 不保證一緻性,邏輯過期時間增加維護成本和記憶體成本

4. 緩存降級

與熱點key相對立的政策就是緩存降級了,服務降級的目的,是為了防止Redis服務故障,導緻資料庫跟着一起發生雪崩問題。是以,對于不重要的緩存資料,可以采取服務降級政策,例如一個比較常見的做法就是,Redis出現問題,不去資料庫查詢,而是直接傳回預設值給使用者。

推薦閱讀

1. Redis雲平台CacheCloud:https://github.com/sohutv/cachecloud

2. Redis資料結構與内部編碼,你知道多少?

3. Redis持久化機制

4. Redis Sentinel哨兵模式

5. Redis Cluster高可用叢集模式

redis清理緩存_Redis緩存設計與優化

看完本文有收獲?請轉發分享給更多人

關注「并發程式設計之美」,一起交流Java學習心得

繼續閱讀