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;
dict數組儲存所有的資料庫,redis初始化的時候,預設會建立16個資料庫
#define redis_default_dbnum 16
預設情況下,redis用戶端的目标資料庫是0 号資料庫,可以通過select指令切換。 注意,由于redis缺少擷取目前操作的資料庫指令,使用select切換需要特别注意
讀寫資料庫中的鍵值對的時候,redis除了對鍵空間執行指定操作外,還有一些額外的操作:
讀取鍵之後(讀和寫操作都會先讀取),記錄鍵空間命中或不命中次數
讀取鍵之後,更新鍵的lru
讀取時發現已經過期,會先删除過期鍵
如果有用戶端使用watch指令監視了key,會在修改後标記為dirty
修改之後,會對dirty鍵計數器加1,用于持久化和複制
如果開啟了資料庫通知,修改之後會發送相應通知
robj *lookupkeyreadorreply(redisclient *c, robj *key, robj *reply) {
robj *o = lookupkeyread(c->db, key);
if (!o) addreply(c,reply);
return o;
}
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;
robj *lookupkey(redisdb *db, robj *key) {
dictentry *de = dictfind(db->dict,key->ptr);
if (de) {
robj *val = dictgetval(de);
/* update the access time for the ageing algorithm.
* don’t do it if we have a saving child, as this will trigger
* a copy on write madness. */
if (server.rdb_child_pid == –1 && server.aof_child_pid == –1)
//設定lru時間
val->lru = server.lruclock;
} else {
return null;
通過exprire或者pexpire指令,可以設定鍵的ttl,如果鍵的ttl為0,會被自動删除。
expires字典儲存了資料庫中所有鍵的過期時間。
過期字典的鍵是指向某個資料中的鍵對象
過期字段的值是long long類型的整數,儲存這個鍵的過期時間
void expirecommand(redisclient *c) {
expiregenericcommand(c,mstime(),unit_seconds);
void expiregenericcommand(redisclient *c, long long basetime, int unit) {
robj *key = c->argv[1], *param = c->argv[2];
long long when; /* unix time in milliseconds when the key will expire. */
if (getlonglongfromobjectorreply(c, param, &when, null) != redis_ok)
return;
if (unit == unit_seconds) when *= 1000;
when += basetime;
/* no key, return zero. */
if (lookupkeyread(c->db,key) == null) {
addreply(c,shared.czero);
/* expire with negative ttl, or expireat with a timestamp into the past
* should never be executed as a del when load the aof or in the context
* of a slave instance.
*
* instead we take the other branch of the if statement setting an expire
* (possibly in the past) and wait for an explicit del from the master. */
if (when <= mstime() && !server.loading && !server.masterhost) {
robj *aux;
redisassertwithinfo(c,key,dbdelete(c->db,key));
server.dirty++;
/* replicate/aof this as an explicit del. */
aux = createstringobject(“del“,3);
rewriteclientcommandvector(c,2,aux,key);
decrrefcount(aux);
signalmodifiedkey(c->db,key);
notifykeyspaceevent(redis_notify_generic,“del“,key,c->db->id);
addreply(c, shared.cone);
//放到expires字典中
setexpire(c->db,key,when);
addreply(c,shared.cone);
notifykeyspaceevent(redis_notify_generic,“expire“,key,c->db->id);
惰性删除:每次執行指令前,都會調用expireifneeded函數檢查是否過期,如果已經過期,改函數會删除過期鍵
定時删除:定時執行activeexpirecycletryexpire函數
int expireifneeded(redisdb *db, robj *key) {
mstime_t when = getexpire(db,key);
mstime_t now;
if (when < 0) return 0; /* no expire for this key */
/* don’t expire anything while loading. it will be done later. */
if (server.loading) return 0;
/* if we are in the context of a lua script, we claim that time is
* blocked to when the lua script started. this way a key can expire
* only the first time it is accessed and not in the middle of the
* script execution, making propagation to slaves / aof consistent.
* see issue #1525 on github for more information. */
now = server.lua_caller ? server.lua_time_start : mstime();
/* if we are running in the context of a slave, return asap:
* the slave key expiration is controlled by the master that will
* send us synthesized del operations for expired keys.
* still we try to return the right information to the caller,
* that is, 0 if we think the key should be still valid, 1 if
* we think the key is expired at this time. */
if (server.masterhost != null) return now > when;
/* return when this key has not expired */
if (now <= when) return 0;
/* delete the key */
server.stat_expiredkeys++;
propagateexpire(db,key);
notifykeyspaceevent(redis_notify_expired,
“expired“,key,db->id);
return dbdelete(db,key);
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) ttl = 0;
ttl_sum += ttl;
ttl_samples++;
生成rdb檔案時,已過期的鍵不會被儲存到新的rdb檔案中
載入rdb檔案:
主伺服器載入時,會忽略過期鍵
從伺服器載入時,都會被載入(但是很快會因為同步被覆寫)
aof寫入,已過期未删除的鍵沒有影響,被删除後,會追加一條del指令
aof重寫,會對鍵進行檢查,過期鍵不會儲存到重寫後的aof檔案
複制:
主伺服器删除一個過期鍵後,會顯式向所有從伺服器發送del指令
從伺服器執行讀指令,及時過期也不會删除,隻有接受到主伺服器del指令才會删除
轉載自:https://coolex.info/blog/459.html