При использовании Redis мы можем использовать команду EXPIRE или EXPIREAT, чтобы установить время истечения срока действия для ключа.Словарь expires в структуре redisDb сохраняет время истечения срока действия всех ключей.Ключ этого словаря (dict) является указателем на key в объекте redis, значение словаря срока действия — целое число, которое содержит время истечения срока действия.
/* 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, а затем добавьте запись (указатель ключа, время истечения) в словарь срока действия.
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);
}
Политика удаления с истекшим сроком действия
Если срок действия ключа истек, когда он будет удален? В Redis есть две стратегии удаления с истекшим сроком действия: (1) ленивое удаление с истекшим сроком действия; (2) периодическое удаление. Давайте рассмотрим это подробно.
Ленивое удаление с истекшим сроком действия
Redis сначала найдет этот ключ при выполнении любых команд чтения и записи. Ленивое удаление используется в качестве точки входа перед поиском ключа. Если срок действия ключа истечет, ключ будет удален.
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;
}
регулярно удалять
Периодическое удаление ключа будет выполняться в задаче периодического выполнения Redis (serverCron, выполняется каждые 100 мс по умолчанию), и это мастер-узел, где происходит Redis, потому что подчиненный узел будет синхронизироваться через команду DEL мастера. узел для достижения цели удаления ключа.
Пройдите каждый db по очереди (номер конфигурации по умолчанию — 16) и для каждого db случайным образом выберите 20 (ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP) ключей в каждом цикле, чтобы определить, истек ли срок их действия. Кроме того, если в процессе итерации превышен определенный предел времени , процесс удаления с истекшим сроком действия завершается.
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);
}
Суммировать
- Ленивое удаление: определите, истекает ли срок действия ключа перед чтением и записью
- Периодическое удаление: периодически проверяйте ключи, чтобы определить, не истек ли срок их действия.