Redis 緩存失效機制


Redis緩存失效的故事要從EXPIRE這個命令說起,EXPIRE允許用戶為某個key指定超時時間,當超過這個時間之后key對應的值會被清除,這篇文章主要在分析Redis源碼的基礎上站在Redis設計者的角度去思考Redis緩存失效的相關問題。

 

Redis緩存失效機制

 

Redis緩存失效機制是為應對緩存應用的一種很常見的場景而設計的,講個場景:

 

我們為了減輕后端數據庫的壓力,很開心的借助Redis服務把變化頻率不是很高的數據從DB load出來放入了緩存,因此之后的一段時間內我們都可以直接從緩存上拿數據,然而我們又希望一段時間之后,我們再重新的從DB load出當前的數據放入緩存,這個事情怎么做呢?

 

問題提出來了,這個問題怎么解決呢?好吧,我們對於手頭的語言工具很熟悉,堅信可以很快的寫出這么一段邏輯:我們記錄上次從db load數據的時間,然后每次響應服務的時候都去判斷時間是不是過期了,要不要從db重新load了……。當然這種方法也是可以的,然而當我們查閱Redis command document的時候,發現我們做了本來不需要做的事情,Redis本身提供這種機制,我們只要借助EXPIRE命令就可以輕松的搞定這件事情:

 

EXPIRE key 30

 

上面的命令即為key設置30秒的過期時間,超過這個時間,我們應該就訪問不到這個值了,到此為止我們大概明白了什么是緩存失效機制以及緩存失效機制的一些應用場景,接下來我們繼續深入探究這個問題,Redis緩存失效機制是如何實現的呢?

 

延遲失效機制

 

延遲失效機制即當客戶端請求操作某個key的時候,Redis會對客戶端請求操作的key進行有效期檢查,如果key過期才進行相應的處理,延遲失效機制也叫消極失效機制。我們看看t_string組件下面對get請求處理的服務端端執行堆棧:

 

getCommand

     -> getGenericCommand

            -> lookupKeyReadOrReply

                   -> lookupKeyRead

                         -> expireIfNeeded

 

關鍵的地方是expireIfNeed,Redis對key的get操作之前會判斷key關聯的值是否失效,這里先插入一個小插曲,我們看看Redis中實際存儲值的地方是什么樣子的:

 

typedef struct redisDb {

    dict *dict;                 /* The keyspace for this DB */

    dict *expires;              /* Timeout of keys with a timeout set */

    dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP) */

    dict *ready_keys;           /* Blocked keys that received a PUSH */

    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */

    int id;

    long long avg_ttl;          /* Average TTL, just for stats */

} redisDb;

 

上面是Redis中定義的一個結構體,dict是一個Redis實現的一個字典,也就是每個DB會包括上面的五個字段,我們這里只關心兩個字典,一個是dict,一個是expires:

 

  1. dict是用來存儲正常數據的,比如我們執行了set key “hahaha”,這個數據就存儲在dict中。

     

  2. expires使用來存儲關聯了過期時間的key的,比如我們在上面的基礎之上有執行的expire key 1,這個時候就會在expires中添加一條記錄。

 

回過頭來看看expireIfNeeded的流程,大致如下:

 

  1. 從expires中查找key的過期時間,如果不存在說明對應key沒有設置過期時間,直接返回。

     

  2. 如果是slave機器,則直接返回,因為Redis為了保證數據一致性且實現簡單,將緩存失效的主動權交給Master機器,slave機器沒有權限將key失效。

     

  3. 如果當前是Master機器,且key過期,則master會做兩件重要的事情:1)將刪除命令寫入AOF文件。2)通知Slave當前key失效,可以刪除了。

     

  4. master從本地的字典中將key對於的值刪除。

 

主動失效機制

 

主動失效機制也叫積極失效機制,即服務端定時的去檢查失效的緩存,如果失效則進行相應的操作。

 

我們都知道Redis是單線程的,基於事件驅動的,Redis中有個EventLoop,EventLoop負責對兩類事件進行處理:

 

  1. 一類是IO事件,這類事件是從底層的多路復用器分離出來的。

     

  2. 一類是定時事件,這類事件主要用來事件對某個任務的定時執行。

 

看起來Redis的EventLoop和Netty以及JavaScript的EventLoop功能設計的大概類似,一方面對網絡I/O事件處理,一方面還可以做一些小任務。

 

為什么講到Redis的單線程模型,因為Redis的主動失效機制邏輯是被當做一個定時任務來由主線程執行的,相關代碼如下:

 

if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {

        redisPanic("Can't create the serverCron time event.");

        exit(1);

    }

 

serverCron就是這個定時任務的函數指針,adCreateTimeEvent將serverCron任務注冊到EventLoop上面,並設置初始的執行時間是1毫秒之后。接下來,我們想知道的東西都在serverCron里面了。serverCron做的事情有點多,我們只關心和本篇內容相關的部分,也就是緩存失效是怎么實現的,我認為看代碼做什么事情,調用堆棧還是比較直觀的:

 

aeProcessEvents

    ->processTimeEvents

        ->serverCron

             -> databasesCron

                   -> activeExpireCycle

                           -> activeExpireCycleTryExpire

 

EventLoop通過對定時任務的處理,觸發對serverCron邏輯的執行,最終之執行key過期處理的邏輯,值得一提的是,activeExpireCycle邏輯只能由master來做。

 

遺留問題

 

Redis對緩存失效的處理機制大概分為兩種,一種是客戶端訪問key的時候消極的處理,一種是主線程定期的積極地去執行緩存失效清理邏輯,上面文章對於一些細節還沒有展開介紹,但是對於Redis緩存失效實現機制這個話題,本文留下幾個問題:

 

  1. Redis緩存失效邏輯為什么只有master才能操作?

     

  2. 上面提到如果客戶端訪問的是slave,slave並不會清理失效緩存,那么這次客戶端豈不是獲取了失效的緩存?

     

  3. 上面介紹的兩種緩存失效機制各有什么優缺點?Redis設計者為什么這么設計?

     

  4. 服務端對客戶端的請求處理是單線程的,單線程又要去處理失效的緩存,是不是會影響Redis本身的服務能力?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM