你爲何在Redis裏讀到了本應過時的數據

一個事故的故事

晚上睡的正香忽然被電話吵醒,對面是開發焦急的聲音:咱們的程序在訪問redis的時候讀到了本應過時的key致使整個業務邏輯出了問題,須要立刻解決。面試

看到這裏你可能會想:這是否是又是所謂的「redis的坑」啊?redis

不不不,咱們歷來不會隨便把一些問題歸類到「xxx的坑」裏,那麼這個問題真的存在嗎?算法

是的,這個問題真實存在而且你極可能已經碰到了只是並未發覺。數據庫

那麼形成這一問題的緣由是什麼呢?dom

簡單的說,redis的從庫是沒法主動的刪除已通過期的key的,因此若是你作了讀寫分離,那麼就極可能會在從庫讀到髒數據。函數

複雜的說,這個問題的答案相對複雜或者你根本不想知道這麼詳細,因此若是你只是想簡單的瞭解如何避免這個問題,那麼請繼續看,很簡單,咱們有兩種作法:性能

1.經過一個程序循環遍歷全部key,例如scan

2.升級到redis3.2

OK,你已經遇到/可能遇到/即將遇到的問題咱們已經幫你解決了,那麼若是你仍然對本文有興趣那麼能夠繼續往下看,由於在下文中對這個問題的分析以及更細化的解決方案必定會爲你增長面試、討論、對噴時的資本!spa

「通俗易懂」Redis過時策略解讀

Redis key的三種過時策略code

  • 惰性刪除:當讀/寫一個已通過期的key時,會觸發惰性刪除策略,直接刪除掉這個過時key,很明顯,這是被動的!內存

  • 按期刪除:因爲惰性刪除策略沒法保證冷數據被及時刪掉,因此redis會按期主動淘汰一批已過時的key。(在第二節中會具體說明)

  • 主動刪除:當前已用內存超過maxmemory限定時,觸發主動清理策略,該策略由啓動參數的配置決定,可配置參數及說明以下:

volatile-lru:從已設置過時時間的數據集中根據LRU算法刪除數據(redis3.0以前的默認策略)

volatile-ttl:從已設置過時時間的數據集中挑選過時時間最小的數據刪除

volatile-random:從已設置過時時間的數據集中隨機選擇數據刪除

allkeys-lru:從全部數據集中根據LRU算法刪除數據

allkeys-random:從全部數據集中任意選擇刪除數據

noenviction:禁止從內存中刪除數據(從redis3.0 開始默認策略)

maxmemory-samples:刪除數據的抽樣樣本數,redis3.0以前默認樣本數爲3,redis3.0開始默認樣本數爲5,該參數設置太小會致使主動刪除策略不許確,過大會消耗多餘的cpu

Redis過時key刪除策略之按期刪除

由於redis自己的定位爲輕量、快速的內存數據庫,因此若是爲全部key都加上定時器,過時即刪除的定時策略顯然會消耗大量的性能,這與redis做者的價值觀有着巨大差別;因爲redis中key的過時刪除只會在主庫上進行,對於目前redis使用的組合策略來講,單位時間過時的數據量越多,越可能會帶來key的過時延遲,對於作了讀寫分離的業務,很容易致使從庫讀取到過時的髒數據。

redis源碼activeExpireCycle函數的解讀結果請看下文(若是你懶得看,能夠直接跳過本節看第三節):

相關參數默認值:

hz 10 :每秒執行10次activeExpireCycle 函數

activeExpireCycle函數解析:

每次循環隨機拿出的key的數量

正常過時模式最大cpu耗時率

過時模式:

「正常過時」模式 :執行時間限制:25ms;計算公式爲

「快速過時」模式 :執行時間限制爲1ms,觸發條件爲上次的執行時間超過了timelimit,以後函數會使timelimit_exit=1 爲真,並從上次發生超時的db的下一個db開始繼續處理。

過時策略:redis會遍歷全部db,每次從db中隨機拿出20個帶有過時時間屬性的key作過時判斷。

循環檢測:對隨機拿出的20個key進行檢測,若是在本次檢測中發現有超過25%的key被斷定爲過時則持續執行過時檢測循環,直到這批key中須要過時的key的比例低於25%或某次循環超過timelimit執行時間限制。

上文已經提到,過時刪除行爲只會在主庫中進行。這是由於key的過時刪除依賴於expireIfNeeded函數,這個函數在任何訪問數據的操做中都會被調用並用來檢測客戶端訪問的數據是否過時。

若是當前數據庫實例角色是master,則不進行key過時的刪除操做。反之,它會先調用另外一個函數propagateExpire發送del key命令到aof和當前redis實例的全部slave,最後將該key從數據庫中刪除。此時,從庫中的該key才真正意義上的過時/消失/你訪問不到了!

因此一旦一個redis集羣的內存沒有觸及maxmemory,而它每時每刻都有大量的key須要過時致使按期刪除忙不過來,而且這些過時了的key不會再被訪問到,那麼你就極可能會在從庫莫名其妙的讀到了本應過時的key了。

如何避免從庫讀取到髒數據

  1. 經過scan命令掃庫:
    當redis中的key被scan的時候,至關於訪問了該key,一樣也會作過時檢測,充分發揮redis惰性刪除的策略。這個方法能大大下降了髒數據讀取的機率,但缺點也比較明顯,會形成必定的數據庫壓力,謹慎合理使用,不然有可能影響線上業務的效率。

  2. 升級redis到新的版本:
    在redis 3.2-rc1版本中,redis加入了一個新特性來解決主從不一致致使讀取到過時數據的問題(好吧,雖然這個新特性咱們一直以爲是個bug fix),在源碼db.c文件中,做者對lookupKeyRead作了相應的修改,增長了key是否過時以及對主從庫的判斷(代碼以下),若是key已過時,當前訪問的是master則返回null;當前訪問的是從庫,且執行的是隻讀命令也返回null(老版本從庫真實的返回該操做的結果,若是該key過時後主庫沒有刪除),源碼片斷以下:

那麼,不想經過本身寫程序解決問題的同窗,快快升級redis到新的版本吧。

相關文章
相關標籤/搜索