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:
dict是用來存儲正常數據的,比如我們執行了set key "hahaha",這個資料就儲存在dict中。
expires使用來儲存關聯了過期時間的key的,例如我們在上面的基礎之上有執行的expire key 1,這個時候就會在expires中加入一筆記錄。
回頭看看expireIfNeeded的流程,大致如下:
從expires中找出key的過期時間,如果不存在說明對應key沒有設定過期時間,直接回傳。
如果是slave機器,則直接傳回,因為Redis為了確保資料一致性且實作簡單,將快取失效的主動權交給Master機器,slave機器沒有權限將key失效。
如果目前是Master機器,且key過期,則master會做兩件重要的事情:1)將刪除指令寫入AOF檔。 2)通知Slave當前key失效,可以刪除了。
master從本機的字典中將key對於的值刪除。
主動失效機制
主動失效機制也叫積極失效機制,也就是服務端定時的去檢查失效的緩存,如果失效則進行對應的操作。
我們都知道Redis是單線程的,基於事件驅動的,Redis中有個EventLoop,EventLoop負責對兩類事件進行處理:
一類是IO事件,這類事件是從底層的多工器分離出來的。
一類是定時事件,這類事件主要用來事件對某個任務的定時執行。
看起來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知識請關注redis入門教學欄位。
以上是Redis快取失效機制介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!