Redis数据库定义:



typedef struct redisDb {
 
 
/* The keyspace for this DB */
 
 
/* Timeout of keys with a timeout set */
 
 
/* Keys with clients waiting for data (BLPOP) */
 
 
/* Blocked keys that received a PUSH */
 
 
/* WATCHED keys for MULTI/EXEC CAS */
 
 
int
 
 
long long avg_ttl; /* Average TTL, just for stats */
 
 
}



dict

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) {
 
 
 lookupKeyRead(c->db, key);
 
 
if(!o) addReply(c,reply);
 
 
return
 
 
}
 
 
robj *lookupKeyRead(redisDb *db, robj *key) {
 
 
 robj *val;
 
 
 
 
 
 //查询是否已经过期 
 
 
 expireIfNeeded(db,key);
 
 
 lookupKey(db,key);
 
 
if(val == NULL)
 
 
.stat_keyspace_misses++;
 
 
else
 
 
.stat_keyspace_hits++;
 
 
return
 
 
}
 
 
robj *lookupKey(redisDb *db, robj *key) {
 
 
 dictFind(db->dict,key->ptr);
 
 
if(de) {
 
 
 dictGetVal(de);
 
 
 
 
 
/*
 
 
 * Don’t do it if we have a saving child, as this will trigger
 
 
*/
 
 
if(server.rdb_child_pid == –1 && server.aof_child_pid == –1)
 
 
 //设置lru时间 
 
 
.lruclock;
 
 
return
 
 
} else {
 
 
return NULL;
 
 
}
 
 
}



expires

通过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) { 
   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); 
   return; 
   } 
     
   /* 
    * 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 
   */ 
   if(when <= mstime() && !server.loading && !server.masterhost) { 
    robj *aux; 
     
    redisAssertWithInfo(c,key,dbDelete(c->db,key)); 
   .dirty++; 
     
   /* Replicate/AOF this as an explicit DEL. */ 
    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); 
   return; 
   } else { 
    //放到expires字典中  
    setExpire(c->db,key,when); 
    addReply(c,shared.cone); 
    signalModifiedKey(c->db,key); 
    notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,“expire“,key,c->db->id); 
   .dirty++; 
   return; 
   } 
   }

过期键删除策略

  • 惰性删除:每次执行命令前,都会调用expireIfNeeded函数检查是否过期,如果已经过期,改函数会删除过期键
  • 定时删除:定时执行activeExpireCycleTryExpire函数

expireIfNeeded

int expireIfNeeded(redisDb *db, robj *key) {
mstime_t when = getExpire(db,key);
mstime_t
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;
/*
 * 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.
*/
.lua_caller ? server.lua_time_start : mstime();
/*
 * 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
*/
if(server.masterhost != NULL) return
/* Return when this key has not expired */
if(now <= when) return 0;
/* Delete the key */
.stat_expiredkeys++;
 propagateExpire(db,key);
 notifyKeyspaceEvent(REDIS_NOTIFY_EXPIRED,
“expired“,key,db->id);
return dbDelete(db,key);
}

activeExpireCycleTryExpire

while (num–) {
 dictEntry *de;
long long
if((de = dictGetRandomKey(db->expires)) == NULL) break;
 dictGetSignedIntegerVal(de)-now;
if(activeExpireCycleTryExpire(db,de,now)) expired++;
if(ttl < 0) ttl = 0;
 ttl_sum += ttl;
 ttl_samples++;
}

AOF、RDB和复制功能对过期键的处理

  • 生成RDB文件时,已过期的键不会被保存到新的RDB文件中
  • 载入RDB文件:
  • 主服务器载入时,会忽略过期键
  • 从服务器载入时,都会被载入(但是很快会因为同步被覆盖)
  • AOF写入,已过期未删除的键没有影响,被删除后,会追加一条del命令
  • AOF重写,会对键进行检查,过期键不会保存到重写后的AOF文件
  • 复制:
  • 主服务器删除一个过期键后,会显式向所有从服务器发送DEL命令
  • 从服务器执行读命令,及时过期也不会删除,只有接受到主服务器DEL命令才会删除