Redis異常測試盤點分析

Redis測試中的異常

在測試工作中,涉及到與 redis 交互的場景變的越來越多瞭。關於redis本身就不作贅述瞭,網上隨便搜,本人也做過一些整理。

今天隻來復盤一下,在測試過程中與 redis 的二三事兒。其中提到的案例是經過抽象化的,用作輔助說明作用,僅供參考。

一、更新 Key 異常

註意點:先刪除原 key 再存,還是直接覆蓋原 key?

比如:之前 A 服務每8小時去查詢一次數據庫,更新到緩存裡去。後來需求調整,變成當數據庫裡有變動的時候就會發送MQ消息給服務 A,然後A就去全量拉取庫裡數據,再更新到緩存。

開發小哥實現的是先刪除key再更新,那麼可能會導致這個時間如果有大量的請求進來,就不能命中緩存。於是乎建議,當從數據庫拉來數據之後,可以先和redis中原來的key值進行對比,刪除多餘的緩存,其他的覆蓋更新。

二、Key的刪除和丟失

註意點:考慮key被刪除,或者key丟失後對上遊的影響。

比如:服務A 會同步一類數據到 redis,然後發消息告訴 服務B。B 收到消息後,拿到 redis 數據去找自己那邊 MongoDB裡的對應 key,做更新操作,若查不到key,就會刪除數據。

此時如果 redis 裡產生瞭數據丟失,key就不存在瞭,那麼同步過後,會導致 MongoDB 裡的數據被勿刪。

於是乎這裡建議方案是:redis 那邊涉及要刪除key的話,就更新key的值為空[],這時候 MongoDB 查詢到值為空的key,就去刪除對應數據。
另外,如果redis那邊key 丟失瞭,MongoDB這邊也別就刪數據瞭,去調用一個實時接口去查詢數據然後更新。

三、KEY 過期策略不當造成內存泄漏

首先回顧一下 redis 中 ttl key指令:

  • 當 key 不存在時,返回 -2
  • 當 key 存在但沒有設置剩餘生存時間時,返回 -1
  • 否則,返回 key 的剩餘生存時間,單位是 s

通常,大多數業務用到redis 都會設置過期時間。接下來,瞭解一下 key 過期是如何清理的。

定期清理

Redis會定期主動淘汰一批已過期的key(隨機抽取一批key檢查)。

缺點:可能存在很多KEY已過期,仍未清理。

惰性清理

在獲取某個 key 的時候,redis 會檢查一下這個 key 如果設置瞭過期時間並且已經過期,就會刪除這個 key,不會返回任何東西。

缺點:如果存在很多未去查詢的過期key,就沒法走到惰性刪除,於是可能會有大量過期的key堆積在內存裡,導致內存耗盡。

一般來說,業務會惰性和定期清理配合使用。

內存淘汰機制

但是,如果定期清理漏掉瞭很多過期的key,然後你也沒及時去查,也就沒走惰性刪除。此時依舊有可能大量過期的key堆積在內存裡,導致內存耗盡。

這時候需要內存淘汰機制,有如下幾個:

  • noeviction:當內存不足以容納新寫入數據時,新寫入操作會報錯。這個一般很少用。
  • allkeys-lru:當內存不足以容納新寫入數據時,在鍵空間中,移除最近最少使用的key,這個是最常用的。
  • allkeys-random:當內存不足以容納新寫入數據時,在鍵空間中,隨機移除某個key。
  • volatile-lru:當內存不足以容納新寫入數據時,在設置瞭過期時間的鍵空間中,移除最近最少使用的key。
  • volatile-random:當內存不足以容納新寫入數據時,在設置瞭過期時間的鍵空間中,隨機移除某個key。
  • volatile-ttl:當內存不足以容納新寫入數據時,在設置瞭過期時間的鍵空間中,有更早過期時間的key優先移除。

以上可以作個瞭解。

四、查詢Redis異常時處理

很多時候,redis 隻是做一個緩存機制,如果redis異常或者未取到數據,是否有實時獲取數據的兜底方案(查接口 or 查庫?),需要考慮。

五、redis 穿透、擊穿、雪崩

穿透

用戶想要查詢一個數據,發現redis內存數據庫中沒有,也就是說沒有命中緩存,也是會向持久層數據庫查詢,發現也沒有,那麼本次查詢失敗。
如果此時,用戶很多,高並發場景下都去查這個數據,由於緩存都沒有命中,於是壓力直接打到持久層數據庫那裡,這就是緩存穿透。

解決方案可以用佈隆過濾器、返回空對象(設置過期時間)。

擊穿

緩存擊穿,是指一個key非常熱點,在不停的扛著高並發,如果這個key失效瞭,在失效的瞬間,持續的並發量就會穿破緩存,直接打到持久層數據庫,就像一個防禦墻被鑿開一個洞。

解決方案可以設置熱點數據永不過期、加互斥鎖等。

雪崩

是指在某一個時間段,緩存集中過期失效,或者redis宕機瞭。

解決方案:

  • 事前:redis 高可用,主從+哨兵,redis cluster,避免全盤崩潰。
  • 事中:本地 ehcache 緩存 + hystrix 限流&降級,避免 MySQL 被打死。
  • 事後:redis 持久化,一旦重啟,自動從磁盤上加載數據,快速恢復緩存數據。

關於這3個,之前有過一篇整理:https://www.jb51.net/article/230997.htm

六、Redis死鎖

Redis鎖,小心使用不當造成鎖不能釋放,陷入死鎖。

目前常用的2種鎖:

SET Key UniqId Seconds

僅在單實例的場景下是安全的。如果不使用setnx+expire+del中間環節斷瞭仍可能造成死鎖;

如果不用SET Key UnixTimestamp Seconds NX,高並發下可能存在相同時間戳。

分佈式Redis鎖:Redlock

此種方式比原先的單節點的方法更安全。

  • 安全性:在同一時間不允許多個Client同時持有鎖。
  • 活性死鎖:鎖最終應該能夠被釋放,即使Client端crash或者出現網絡分區(通常基於超時機制)。
  • 容錯性:隻要超過半數Redis節點可用,鎖都能被正確獲取和釋放。

七、Redis持久化

當Redis數據需要長久有效時,需要考慮是否做RDB和AOF持久化,一般RDB和AOF配合使用,但做持久化,會影響性能。

目前接觸到的業務做持久化的很少見。比如有個推薦系統Redis數據是長久有效的,但卻為瞭響應快不影響性能,未做持久化,而采用瞭其他的降級方案Hbase,以及業務的兜底等。

八、緩存與數據庫雙寫時的數據一致性

一般來說,就是如果你的系統不是嚴格要求緩存+數據庫必須一致性的話,允許緩存跟數據庫偶爾不一致的情況,那麼最後好不要做這個一致性方案。

如果實現這個方案,讀請求和寫請求串行化,串到一個內存隊列裡去,這樣就可以保證一定不會出現不一致的情況。

但是串行化之後,就會導致系統的吞吐量會大幅度的降低,用比正常情況下多幾倍的機器去支撐線上的一個請求。

還有一種適中的方式就是,就是先更新數據庫,然後再刪除緩存。可能會暫時產生不一致的情況,但是發生的幾率特別小。這時候通常並行寫數據庫和緩存,可以加個事務,都寫成功才成功,有一個環節失敗瞭就回滾事務,全失敗。

關於雙寫一致性的問題,其實可以另起一個篇幅來說瞭,有興趣的可以網上搜索一下,後續可能會再進行整理。

以上就是Redis異常測試盤點分析的詳細內容,更多關於Redis異常測試的資料請關註WalkonNet其它相關文章!

推薦閱讀: