天天看點

Redis:緩存問題及其解決方案

作者:日拱一卒程式猿

一、緩存穿透

一般的緩存系統,都是按照key去緩存查詢,如果不存在對應的value,就應該去後端系統查找(比如DB)。

緩存穿透是指在高并發下查詢key不存在的資料,會穿過緩存查詢資料庫。導緻資料庫壓力過大而當機。

解決方案:

  • 對查詢結果為空的情況也進行緩存,緩存時間(ttl)設定短一點,或者該key對應的資料insert了之後清理緩存。問題:緩存太多空值占用了更多的空間
  • 使用布隆過濾器。在緩存之前再加一層布隆過濾器,在查詢的時候先去布隆過濾器查詢 key 是否存在,如果不存在就直接傳回,存在再查緩存和DB。

二、緩存雪崩

當緩存伺服器重新開機或者大量緩存集中在某一個時間段失效,這樣在失效的時候,也會給後端系統(比如DB)帶來很大壓力。突然間大量的key失效了或redis重新開機,大量通路資料庫,資料庫崩潰。

解決方案:

1、 key的失效期分散開:不同的key設定不同的有效期

2、設定二級緩存(資料不一定一緻)

3、高可用(髒讀)

三、緩存擊穿

對于一些設定了過期時間的key,如果這些key可能會在某些時間點被超高并發地通路,是一種非常“熱點”的資料。

這個時候,需要考慮一個問題:緩存被“擊穿”的問題,這個和緩存雪崩的差別在于這裡針對某一key緩存,前者則是很多key。緩存在某個時間點過期的時候,恰好在這個時間點對這個Key有大量的并發請求過來,這些請求發現緩存過期一般都會從後端DB加載資料并回設到緩存,這個時候大并發的請求可能會瞬間把後端DB壓垮。

解決方案:

1、用分布式鎖控制通路的線程

使用redis的setnx互斥鎖先進行判斷,這樣其他線程就處于等待狀态,保證不會有大并發操作去操作資料庫。

2、不設逾時時間,volatile-lru 但會造成寫一緻問題

當資料庫資料發生更新時,緩存中的資料不會及時更新,這樣會造成資料庫中的資料與緩存中的資料的不一緻,應用會從緩存中讀取到髒資料。可采用延時雙删政策處理,這個我們後面會詳細講到。

四、資料不一緻

緩存和DB的資料不一緻的根源 : 資料源不一樣

如何解決強一緻性很難,追求最終一緻性(時間)

網際網路業務資料處理的特點

高吞吐量低延遲

資料敏感性低于金融業

時序控制是否可行?

先更新資料庫再更新緩存或者先更新緩存再更新資料庫本質上不是一個原子操作,是以時序控制不可行

高并發情況下會産生不一緻

保證資料的最終一緻性(延時雙删)

1、先更新資料庫同時删除緩存項(key),等讀的時候再填充緩存

2、2秒後再删除一次緩存項(key)

3、設定緩存過期時間 Expired Time 比如 10秒 或1小時

4、将緩存删除失敗記錄到日志中,利用腳本提取失敗記錄再次删除(緩存失效期過長 7*24)

更新方案

通過資料庫的binlog來異步淘汰key,利用工具(canal)将binlog日志采集發送到MQ中,然後通過ACK機制确認處理删除緩存。

五、資料并發競争

這裡的并發指的是多個redis的client同時set 同一個key引起的并發問題。多用戶端(Jedis)同時并發寫一個key,一個key的值是1,本來按順序修改為2,3,4,最後是4,但是順序變成了4,3,2,最後變成了2。

第一種方案:分布式鎖+時間戳

1.整體技術方案

這種情況,主要是準備一個分布式鎖,大家去搶鎖,搶到鎖就做set操作。加鎖的目的實際上就是把并行讀寫改成串行讀寫的方式,進而來避免資源競争

Redis:緩存問題及其解決方案

2.Redis分布式鎖的實作

主要用到的redis函數是setnx()用SETNX實作分布式鎖時間戳由于上面舉的例子,要求key的操作需要順序執行,是以需要儲存一個時間戳判斷set順序。

  • 系統A key 1 {ValueA 7:00}
  • 系統B key 1 { ValueB 7:05 }

假設系統B先搶到鎖,将key1設定為{ValueB 7:05}。接下來系統A搶到鎖,發現自己的key1的時間戳早于緩存中的時間戳(7:00<7:05),那就不做set操作了。

第二種方案:利用消息隊列

在并發量過大的情況下,可以通過消息中間件進行處理,把并行讀寫進行串行化。把Redis的set操作放在隊列中使其串行化,必須的一個一個執行。

六、Hot Key

當有大量的請求(幾十萬)通路某個Redis某個key時,由于流量集中達到網絡上限,進而導緻這個redis的伺服器當機。造成緩存擊穿,接下來對這個key的通路将直接通路資料庫造成資料庫崩潰,或者通路資料庫回填Redis再通路Redis,繼續崩潰。

Redis:緩存問題及其解決方案

如何發現熱key

1、預估熱key,比如秒殺的商品、火爆的新聞等

2、在用戶端進行統計,實作簡單,加一行代碼即可

3、如果是Proxy,比如Codis,可以在Proxy端收集

4、利用Redis自帶的指令,monitor、hotkeys。但是執行緩慢(不要用)5、利用基于大資料領域的流式計算技術來進行實時資料通路次數的統計,比如 Storm、Spark Streaming、Flink,這些技術都是可以的。發現熱點資料後可以寫到zookeeper中

Redis:緩存問題及其解決方案

如何處理熱Key:

1、變分布式緩存為本地緩存發現熱key後,把緩存資料取出後,直接加載到本地緩存中。可以采用Ehcache、Guava Cache都可以,這樣系統在通路熱key資料時就可以直接通路自己的緩存了。(資料不要求時時一緻)

2、在每個Redis主節點上備份熱key資料,這樣在讀取時可以采用随機讀取的方式,将通路壓力負載到每個Redis上。

3、利用對熱點資料通路的限流熔斷保護措施每個系統執行個體每秒最多請求緩存叢集讀操作不超過 400 次,一超過就可以熔斷掉,不讓請求緩存叢集,直接傳回一個空白資訊,然後使用者稍後會自行再次重新重新整理頁面之類的。(首頁不行,系統友好性差)通過系統層自己直接加限流熔斷保護措施,可以很好的保護後面的緩存叢集。

七、Big Key

大key指的是存儲的值(Value)非常大,

常見場景:

熱門話題下的讨論

大V的粉絲清單

序列化後的圖檔

沒有及時處理的垃圾資料.....

大key的影響:

大key會大量占用記憶體,在叢集中無法均衡Redis的性能下降,主從複制異常在主動删除或過期删除時會操作時間過長而引起服務阻塞

如何發現大key:

1、redis-cli --bigkeys指令。可以找到某個執行個體5種資料類型(String、hash、list、set、zset)的最大key。但如果Redis 的key比較多,執行該指令會比較慢

2、擷取生産Redis的rdb檔案,通過rdbtools分析rdb生成csv檔案,再導入MySQL或其他資料庫中進行分析統計,根據size_in_bytes統計bigkey

大key的處理:

優化big key的原則就是string減少字元串長度,list、hash、set、zset等減少成員數。

1、string類型的big key,盡量不要存入Redis中,可以使用文檔型資料庫MongoDB或緩存到CDN上。如果必須用Redis存儲,最好單獨存儲,不要和其他的key一起存儲。采用一主一從或多從。

2、單個簡單的key存儲的value很大,可以嘗試将對象分拆成幾個key-value, 使用mget擷取值,這樣分拆的意義在于分拆單次操作的壓力,将操作壓力平攤到多次操作中,降低對redis的IO影響。

2、hash, set,zset,list 中存儲過多的元素,可以将這些元素分拆。

以hash類型舉例來說,對于field過多的場景,可以根據field進行hash取模,生成一個新的key,

例如原來的hash_key:{filed1:value, filed2:value, filed3:value ...},

可以hash取模後形成如下key:value形式

hash_key:1:{filed1:value}

hash_key:2:{filed2:value}

hash_key:3:{filed3:value}...

取模後,将原先單個key分成多個key,每個key filed個數為原先的1/N

3、删除大key時不要使用del,因為del是阻塞指令,删除時會影響性能。

4、使用 lazy delete (unlink指令)删除指定的key(s),若key不存在則該key被跳過。但是,相比DEL會産生阻塞,該指令會在另一個線程中回收記憶體,是以它是非阻塞的。 這也是該指令名字的由來:僅将keys從key空間中删除,真正的資料删除會在後續異步操作。

redis> SET key1 "Hello""OK"

redis> SET key2 "World""OK"

redis> UNLINK key1 key2 key3

(integer) 2

繼續閱讀