With Redis, keys can be set to EXPIRE with the EXPIRE or EXPIREAT command. The Expires dictionary in the redisDb structure holds the expiration of all keys. The dict key is a pointer to a key object in Redis. The value of an expiration dictionary is an integer that holds the expiration time.
/* 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; /* Expired dictionary */ 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;Copy the code
Setting expiration Time
Whether it is EXPIRE, EXPIREAT, or PEXPIRE, PEXPIREAT, the underlying implementation is the same. Find the key to set the expiration time in the Key space of Redis, and add this entry (pointer to the key, expiration time) to the expiration dictionary.
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); }Copy the code
Expired Deletion Policy
If a key expires, when is it deleted? There are two expiration deletion strategies in Redis :(1) lazy expiration deletion; (2) Regularly delete. Let’s see.
Lazy expired delete
Redis finds this key before any read or write command is executed, and lazy deletion is used as a pointcut before looking for the key, and if the key is out of date, it is removed.
robj *lookupKeyRead(redisDb *db, robj *key) { robj *val; expireIfNeeded(db,key); // pointcut val = lookupKey(db,key); if (val == NULL) server.stat_keyspace_misses++; else server.stat_keyspace_hits++; return val; }Copy the code
Periodically delete
The key is periodically deleted in the Redis periodic execution task (serverCron, every 100ms by default), and is the master node where the Redis occurs, because the slave node is synchronized with the DEL command on the master node to delete the key.
For each DB, 20 (ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP) keys are randomly selected for each db. If less than 25% of the selected keys are expired, the loop terminates repeatedly. In addition, the expiration deletion process is terminated if a certain time limit is exceeded during iteration.
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; /* 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) {/* Check once every 16 iterations */ Long Long Elapsed = ustime()-start; latencyAddSampleIfNeeded("expire-cycle",elapsed/1000); if (elapsed > timelimit) timelimit_exit = 1; } // If (timelimit_exit) return; /* Stop deleting expired keys */} while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4); /* Stop deleting expired keys */} while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4) }Copy the code
conclusion
- Lazy delete: Check whether the key has expired before reading or writing
- Periodically delete: Periodically sample keys to determine whether they are expired