在面試中遇到美女面試官時,咱們覺得面試會比較容易過,也能好好表現本身技術的時候了。然而卻出現如下這一幕,當美女面試官據說你使用過Redis時,那麼問題來了。面試
👩面試官:Q1,你知道Redis設置key過時時間的命令嗎?redis
👧你:你堅決果斷的巴拉巴拉說了一堆命令,以及用法,好比expire 等等命令算法
(🎈這時候你想問得那麼簡單?但真的那麼簡單嗎?美女面試官停頓了一下,接着問)數據庫
👩面試官:Q2,那你說說Redis是怎麼實現過時時間設置呢?以及怎麼判斷鍵過時的呢?
👧你:(這時候想這還難不倒我),而後又巴拉巴拉的說一通,Redis的數據庫服務器中redisDb數據結構以及過時時間的斷定服務器
(🎈你又在想應該不會問了吧,換個Redis的話題了吧,那你就錯了)數據結構
👩面試官:(擡頭笑着看了看你)Q3,那你說說過時鍵的刪除策略以及Redis過時鍵的刪除策略以及實現?
🤦️你:這時你回答的就不那麼流暢了,有時頭腦還阻塞了。dom
(🎈這是你可能就有點蒙了,或者只知道一些過時鍵的刪除策略,但具體怎麼實現不知道呀,你覺得面試官的提問這樣就完了嗎?)函數
👩面試官:Q4,那你再說說其餘環節中是怎麼處理過時鍵的呢(好比AOF、RDB)?
🤦🤦你:...........學習
(🎈這更加尷尬了,知道的不全,也可能不知道,原本想好好表現,也想着面試比較簡單,沒想到會經歷這些)this
爲了不這尷尬的場景出現,那如今須要你記錄下如下的內容,這樣就能夠在美女面試官面前好好表現了。
redis數據庫在數據庫服務器中使用了redisDb
數據結構,結構以下:
typedef struct redisDb {
dict *dict; /* 鍵空間 key space */
dict *expires; /* 過時字典 */
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 */
struct evictionPoolEntry *eviction_pool; /* Eviction pool of keys */
int id; /* Database ID */
long long avg_ttl; /* Average TTL, just for stats */
} redisDb;
複製代碼
其中,
key space
):dict字典用來保存數據庫中的全部鍵值對expires
):保存數據庫中全部鍵的過時時間,過時時間用UNIX
時間戳表示,且值爲long long
整數EXPIRE \<key> \<ttl>
:命令用於將鍵key的過時時間設置爲ttl秒以後PEXPIRE \<key> \<ttl>
:命令用於將鍵key的過時時間設置爲ttl毫秒以後EXPIREAT \<key> \<timesramp>
:命令用於將key的過時時間設置爲timrestamp所指定的秒數時間戳PEXPIREAT \<key> \<timesramp>
:命令用於將key的過時時間設置爲timrestamp所指定的毫秒數時間戳設置過時時間:
redis> set Ccww 5 2 0
ok
redis> expire Ccww 5
ok
複製代碼
使用redisDb結構存儲數據圖表示:
過時鍵的斷定,其實經過過時字典進行斷定,步驟:
因爲定時刪除會佔用太多cpu時間,影響服務器的響應時間和吞吐量以及惰性刪除浪費太多內存,有內存泄露的危險,因此出現一種整合和折中這兩種策略的按期刪除策略。
定時刪除策略難點就是肯定刪除操做執行的時長和頻率:
刪除操做執行得太頻繁。或者執行時間太長,按期刪除策略就會退化成爲定時刪除策略,以致於將cpu時間過多地消耗在刪除過時鍵上。相反,則惰性刪除策略同樣,出現浪費內存的狀況。 因此使用按期刪除策略,須要根據服務器的狀況合理地設置刪除操做的執行時長和執行頻率。
Redis服務器結合惰性刪除和按期刪除兩種策略一塊兒使用,經過這兩種策略之間的配合使用,使得服務器能夠在合理使用CPU時間和浪費內存空間取得平衡點。
Redis在執行任何讀寫命令時都會先找到這個key,惰性刪除就做爲一個切入點放在查找key以前,若是key過時了就刪除這個key。
robj *lookupKeyRead(redisDb *db, robj *key) {
robj *val;
expireIfNeeded(db,key); // 切入點
val = lookupKey(db,key);
if (val == NULL)
server.stat_keyspace_misses++;
else
server.stat_keyspace_hits++;
return val;
}
複製代碼
經過expireIfNeeded
函數對輸入鍵進行檢查是否刪除
int expireIfNeeded(redisDb *db, robj *key) {
/* 取出鍵的過時時間 */
mstime_t when = getExpire(db,key);
mstime_t now;
/* 沒有過時時間返回0*/
if (when < 0) return 0; /* No expire for this key */
/* 服務器loading時*/
if (server.loading) return 0;
/* 根據必定規則獲取當前時間*/
now = server.lua_caller ? server.lua_time_start : mstime();
/* 若是當前的是從(Slave)服務器
* 0 認爲key爲無效
* 1 if we think the key is expired at this time.
* */
if (server.masterhost != NULL) return now > when;
/* key未過時,返回 0 */
if (now <= when) return 0;
/* 刪除鍵 */
server.stat_expiredkeys++;
propagateExpire(db,key,server.lazyfree_lazy_expire);
notifyKeyspaceEvent(NOTIFY_EXPIRED,
"expired",key,db->id);
return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
dbSyncDelete(db,key);
}
複製代碼
key的按期刪除會在Redis的週期性執行任務(serverCron
,默認每100ms執行一次)中進行,並且是發生Redis的master
節點,由於slave
節點會經過主節點的DEL命令同步過來達到刪除key的目的。
for (j = 0; j < dbs_per_call; j++) {
int expired;
redisDb *db = server.db+(current_db % server.dbnum);
current_db++;
/* 超過25%的key已過時,則繼續. */
do {
unsigned long num, slots;
long long now, ttl_sum;
int ttl_samples;
/* 若是該db沒有設置過時key,則繼續看下個db*/
if ((num = dictSize(db->expires)) == 0) {
db->avg_ttl = 0;
break;
}
slots = dictSlots(db->expires);
now = mstime();
/*但少於1%時,須要調整字典大小*/
if (num && slots > DICT_HT_INITIAL_SIZE &&
(num*100/slots < 1)) break;
expired = 0;
ttl_sum = 0;
ttl_samples = 0;
if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)
num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP;// 20
while (num--) {
dictEntry *de;
long long ttl;
if ((de = dictGetRandomKey(db->expires)) == NULL) break;
ttl = dictGetSignedIntegerVal(de)-now;
if (activeExpireCycleTryExpire(db,de,now)) expired++;
if (ttl > 0) {
/* We want the average TTL of keys yet not expired. */
ttl_sum += ttl;
ttl_samples++;
}
}
/* Update the average TTL stats for this database. */
if (ttl_samples) {
long long avg_ttl = ttl_sum/ttl_samples;
/樣本獲取移動平均值 */
if (db->avg_ttl == 0) db->avg_ttl = avg_ttl;
db->avg_ttl = (db->avg_ttl/50)*49 + (avg_ttl/50);
}
iteration++;
if ((iteration & 0xf) == 0) { /* 每迭代16次檢查一次 */
long long elapsed = ustime()-start;
latencyAddSampleIfNeeded("expire-cycle",elapsed/1000);
if (elapsed > timelimit) timelimit_exit = 1;
}
/* 超過期間限制則退出*/
if (timelimit_exit) return;
/* 在當前db中,若是少於25%的key過時,則中止繼續刪除過時key */
} while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);
}
複製代碼
依次遍歷每一個db(默認配置數是16),針對每一個db,每次循環隨機選擇20個(ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP
)key判斷是否過時,若是一輪所選的key少於25%過時,則終止迭次,此外在迭代過程當中若是超過了必定的時間限制則終止過時刪除這一過程。
生成RDB文件
程序會數據庫中的鍵進行檢查,已過時的鍵不會保存到新建立的RDB文件中
載入RDB文件
當服務器運行在複製模式下時,從服務器的過時鍵刪除動做由主服務器控制的,這樣的好處主要爲了保持主從服務器數據一致性:
最後可關注公衆號【Ccww筆記】,一塊兒學習,天天會分享乾貨,還有學習視頻乾貨領取!