使用Redis時我們可以使用EXPIRE或EXPIREAT命令給key設置過期刪除時間,結構體redisDb中的expires字典保存了所有key的過期時間,這個字典(dict)的key是一個指針,指向redis中的某個key對象,過期字典的value是一個保存過期時間的整數。

/* Redis database representation. There are multiple databases identified * by integers from 0 (the default database) up to the max configured * database. The database number is the 'id' field in the structure. */typedef struct redisDb { dict *dict; /* The keyspace for this DB */ 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;

設置過期時間

不論是EXPIRE,EXPIREAT,還是PEXPIRE,PEXPIREAT,底層的具體實現是一樣的。在Redis的key空間中找到要設置過期時間的這個key,然後將這個entry(key的指針,過期時間)加入到過期字典中。

void setExpire(redisDb *db, robj *key, long long when) { dictEntry *kde, *de; /* Reuse the sds from the main dict in the expire dict */ kde = dictFind(db->dict,key->ptr); redisAssertWithInfo(NULL,key,kde != NULL); de = dictReplaceRaw(db->expires,dictGetKey(kde)); dictSetSignedIntegerVal(de,when);}

過期刪除策略

如果一個key過期了,何時會被刪除呢?在Redis中有兩種過期刪除策略:(1)惰性過期刪除;(2)定期刪除。接下來具體看看。

惰性過期刪除

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;}

定期刪除

key的定期刪除會在Redis的週期性執行任務(serverCron)中進行,而且是發生Redis的master節點,因爲slave節點會通過主節點的DEL命令同步過來達到刪除key的目的。

依次遍歷每個db(默認配置數是16),針對每個db,每次循環隨機選擇20個(ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)key判斷是否過期,如果一輪所選的key少於25%過期,則終止迭次,此外在迭代過程中如果超過了一定的時間限制則終止過期刪除這一過程。

for (j = 0; j < dbs_per_call; j++) { int expired; redisDb *db = server.db+(current_db % server.dbnum); /* Increment the DB now so we are sure if we run out of time * in the current DB we'll restart from the next. This allows to * distribute the time evenly across DBs. */ current_db++; /* Continue to expire if at the end of the cycle more than 25% * of the keys were expired. */ 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(); /* When there are less than 1% filled slots getting random * keys is expensive, so stop here waiting for better times... * The dictionary will be resized asap. */ if (num && slots > DICT_HT_INITIAL_SIZE && (num*100/slots < 1)) break; /* The main collection cycle. Sample random keys among keys * with an expire set, checking for expired ones. */ 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; /* Do a simple running average with a few samples. * We just use the current estimate with a weight of 2% * and the previous estimate with a weight of 98%. */ if (db->avg_ttl == 0) db->avg_ttl = avg_ttl; db->avg_ttl = (db->avg_ttl/50)*49 + (avg_ttl/50); } /* We can't block forever here even if there are many keys to * expire. So after a given amount of milliseconds return to the * caller waiting for the other active expire cycle. */ 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);}

總結

惰性刪除:讀寫之前判斷key是否過期定期刪除:定期抽樣key,判斷是否過期

查看原文 >>
相關文章