Redis源码(八)——Redis的数据库

在前面的博客中,对Redis的底层数据结构和Redis中的几种对象做了相对详尽的分析,但是对于Redis——作为一种高速分布式数据库,我们并没有对Redis的一些典型功能例如存取键、设置过期时间、主从复制等作出介绍。接下来我们将对Redis的一些功能作出详细的分析。

作为一种数据库,Redis的数据库功能是最基本也是最核心的功能,接下来就对此进行介绍。


一、数据库的结构定义

在redis.h文件中的redisDb定义了Redis数据库结构

/* Redis database representation. There aremultiple databases identified
 * byintegers 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 keyspacefor this DB */
 
    // 键的过期时间,字典的键为键,字典的值为过期事件 UNIX 时间戳
    dict*expires;              /* Timeout of keyswith a timeout set */
 
    // 正处于阻塞状态的键
    dict*blocking_keys;        /* Keys withclients waiting for data (BLPOP) */
 
    // 可以解除阻塞的键
    dict*ready_keys;           /* Blocked keysthat received a PUSH */
 
    // 正在被 WATCH 命令监视的键
    dict*watched_keys;         /* WATCHED keysfor MULTI/EXEC CAS */
 
    structevictionPoolEntry *eviction_pool;    /*Eviction pool of keys */
 
    // 数据库号码
    intid;                     /* Database ID */
 
    // 数据库的键的平均 TTL ,统计信息
    longlong avg_ttl;          /* Average TTL,just for stats */
 
} redisDb;

其中用一个dict字典结构保存着数据库中所有的键值对,这个字典被称为键空间。除此之外,redisDb结构中还保存着键的过期时间的字典、数据库的ID等属性。

 

二、数据库相关操作

 db.c文件中实现了数据库的相关操作:

1). 增加键值对:

/* Add the key to the DB. It's up to the callerto increment the reference
 * counterof the value if needed.
 *
 * 尝试将键值对 key 和 val 添加到数据库中。
 *
 * 调用者负责对 key 和 val 的引用计数进行增加。
 *
 * Theprogram is aborted if the key already exists.
 *
 * 程序在键已经存在时会停止。
 */
void dbAdd(redisDb *db, robj *key, robj *val) {
 
    // 复制键名
    sdscopy = sdsdup(key->ptr);
 
    // 尝试添加键值对
    intretval = dictAdd(db->dict, copy, val);
 
    // 如果键已经存在,那么停止
   redisAssertWithInfo(NULL,key,retval == REDIS_OK);
 
    // 如果开启了集群模式,那么将键保存到槽里面
    if(server.cluster_enabled) slotToKeyAdd(key);
 }

2). 删除键值对:

/* Delete a key, value, and associated expirationentry if any, from the DB
 *
 * 从数据库中删除给定的键,键的值,以及键的过期时间。
 *
 * 删除成功返回 1 ,因为键不存在而导致删除失败时,返回 0 。
 */
int dbDelete(redisDb *db, robj *key) {
 
    /*Deleting an entry from the expires dict will not free the sds of
     * thekey, because it is shared with the main dictionary. */
    // 删除键的过期时间
    if(dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
 
    // 删除键值对
    if(dictDelete(db->dict,key->ptr) == DICT_OK) {
        // 如果开启了集群模式,那么从槽中删除给定的键
        if(server.cluster_enabled) slotToKeyDel(key);
       return 1;
    } else{
        // 键不存在
       return 0;
    }
}

3). 查找键所对应的值:

/*
 * 从数据库 db 中取出键 key 的值(对象)
 *
 * 如果 key 的值存在,那么返回该值;否则,返回 NULL 。
 */
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
         * acopy on write madness. */
        // 更新时间信息(只在不存在子进程时执行,防止破坏 copy-on-write 机制)
        if(server.rdb_child_pid == -1 && server.aof_child_pid == -1)
           val->lru = LRU_CLOCK();
 
        // 返回值
       return val;
    } else{
 
        // 节点不存在
 
       return NULL;
    }
}

4). 更新键所对应的值:

/* Overwrite an existing key with a new value.Incrementing the reference
 * count ofthe new value is up to the caller.
 *
 * 为已存在的键关联一个新值。
 *
 * 调用者负责对新值 val 的引用计数进行增加。
 *
 * Thisfunction does not modify the expire time of the existing key.
 *
 * 这个函数不会修改键的过期时间。
 *
 * Theprogram is aborted if the key was not already present.
 *
 * 如果键不存在,那么函数停止。
 */
void dbOverwrite(redisDb *db, robj *key, robj*val) {
   dictEntry *de = dictFind(db->dict,key->ptr);
   
    // 节点必须存在,否则中止
   redisAssertWithInfo(NULL,key,de != NULL);
 
    // 覆写旧值
   dictReplace(db->dict, key->ptr, val);
}

5). 设置键的过期时间,Redis中有四个不同的命令可以设置键的过期时间:EXPIRE(生存时间以秒为单位)、EXPIREAT(生存时间以毫秒为单位)、PEXPIRE(生存时间为秒的时间戳)、PEXPIREAT(生存时间为毫秒的时间戳),然而这四个指令都是使用PEXPIREAT实现的:

void expireCommand(redisClient *c) {
   expireGenericCommand(c,mstime(),UNIT_SECONDS);
}
 
void expireatCommand(redisClient *c) {
   expireGenericCommand(c,0,UNIT_SECONDS);
}
 
void pexpireCommand(redisClient *c) {
   expireGenericCommand(c,mstime(),UNIT_MILLISECONDS);
}
 
void pexpireatCommand(redisClient *c) {
   expireGenericCommand(c,0,UNIT_MILLISECONDS);
}

expireGenericCommand函数如下:

/* This is the generic command implementation forEXPIRE, PEXPIRE, EXPIREAT
 * andPEXPIREAT. Because the commad second argument may be relative or absolute
 * the"basetime" argument is used to signal what the base time is (either 0
 * for *ATvariants of the command, or the current time for relative expires).
 *
 * 这个函数是 EXPIRE 、PEXPIRE 、 EXPIREAT 和PEXPIREAT 命令的底层实现函数。
 *
 * 命令的第二个参数可能是绝对值,也可能是相对值。
 * 当执行 *AT 命令时, basetime 为 0 ,在其他情况下,它保存的就是当前的绝对时间。
 *
 * unit iseither UNIT_SECONDS or UNIT_MILLISECONDS, and is only used for
 * theargv[2] parameter. The basetime is always specified in milliseconds.
 *
 * unit 用于指定 argv[2] (传入过期时间)的格式,
 * 它可以是 UNIT_SECONDS 或UNIT_MILLISECONDS ,
 * basetime参数则总是毫秒格式的。
 */
void expireGenericCommand(redisClient *c, longlong basetime, int unit) {
    robj*key = c->argv[1], *param = c->argv[2];
    longlong when; /* unix time in milliseconds when the key will expire. */
 
    // 取出 when 参数
    if(getLongLongFromObjectOrReply(c, param, &when, NULL) != REDIS_OK)
       return;
 
    // 如果传入的过期时间是以秒为单位的,那么将它转换为毫秒
    if(unit == UNIT_SECONDS) when *= 1000;
    when +=basetime;
 
    /* Nokey, return zero. */
    // 取出键
    if(lookupKeyRead(c->db,key) == NULL) {
       addReply(c,shared.czero);
       return;
    }
 
   /* EXPIREwith 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.
     *
     * 在载入数据时,或者服务器为附属节点时,
     * 即使 EXPIRE 的 TTL 为负数,或者 EXPIREAT 提供的时间戳已经过期,
     * 服务器也不会主动删除这个键,而是等待主节点发来显式的 DEL 命令。
     *
     *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.
     *
     * 程序会继续将(一个可能已经过期的 TTL)设置为键的过期时间,
     * 并且等待主节点发来 DEL 命令。
     */
    if(when <= mstime() && !server.loading && !server.masterhost){
 
        //when 提供的时间已经过期,服务器为主节点,并且没在载入数据
 
       robj *aux;
 
       redisAssertWithInfo(c,key,dbDelete(c->db,key));
       server.dirty++;
 
        /*Replicate/AOF this as an explicit DEL. */
        // 传播 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);
 
       return;
    } else{
 
        // 设置键的过期时间
        // 如果服务器为附属节点,或者服务器正在载入,
        // 那么这个 when 有可能已经过期的
       setExpire(c->db,key,when);
 
       addReply(c,shared.cone);
 
       signalModifiedKey(c->db,key);
       notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"expire",key,c->db->id);
 
       server.dirty++;
 
       return;
    }
}

 上面简单地列出了最简单的键值对操作,详细地请看db.c文件。Redis在操作数据库时,除了对键空间执行指令指定的读写操作,还会做一些额外的操作:

1). 在读取一个键时,服务器会根据键是否存在来对服务器中键命中(hit)和不命中(miss)属性进行更新:

robj *lookupKeyRead(redisDb *db, robj *key) {
    robj*val;
 
    // 检查 key 释放已经过期
   expireIfNeeded(db,key);
 
    // 从数据库中取出键的值
    val =lookupKey(db,key);
 
    // 更新命中/不命中信息
    if (val== NULL)
       server.stat_keyspace_misses++;
    else
       server.stat_keyspace_hits++;
 
    // 返回值
    returnval;
}

2). 读取键值以后,服务器会更新键的最后使用时间(LRU),这个时间用来计算键的空闲时间,这在服务器因内存不足而对缓存清除时有可能用到(比如使用最近最少使用算法淘汰缓存):

// 更新时间信息(只在不存在子进程时执行,防止破坏 copy-on-write 机制)
        if(server.rdb_child_pid == -1 && server.aof_child_pid == -1)
           val->lru = LRU_CLOCK();

3). 服务器对键进行操作时,会更新dirty计数器的值,这个值会在服务器持久化时被用到:

// 随便列举一个对键进行的操作
void persistCommand(redisClient *c) {
   dictEntry *de;
 
    // 取出键
    de =dictFind(c->db->dict,c->argv[1]->ptr);
 
    if (de== NULL) {
        // 键没有过期时间
       addReply(c,shared.czero);
 
    } else{
 
        // 键带有过期时间,那么将它移除
        if(removeExpire(c->db,c->argv[1])) {
           addReply(c,shared.cone);
           server.dirty++;
 
        // 键已经是持久的了
        }else {
           addReply(c,shared.czero);
        }
    }
}

4). 服务器开启通知时,对键进行修改后,会发出通知,如上面代码中的addReply函数

 

三、过期的键值删除策略

    在上面我们介绍了Redis中的过期键的一些操作,那么过期键会在什么时候删除?按照正常人的逻辑一般可以想到如下两到三种删除策略:

1. 第一种最简单——定时删除:因为我们可以获得一个键的存活时间,那么我们为这个键定义一个定时器,时间到了就触发一个事件,这个时候再去删除这个键。如果有成千上万个过期键呢?难道需要开成千上万个定时器去处理么?那无疑将大部分的CPU都耗在了监控这些过期键了。这种方案性能很明显是不好的

2. 定期删除:这种方案是对第一种方案的改进——既然不能喂每一个键都创建一个定时器,那么就让服务器每隔一段时间就扫描一次数据库,从而清除过期的键。但是如何平衡定时删除的频率是一个难点。

3. 延时删除:也叫惰性删除,这是一种用内存换时间的方案——键过期了服务器并不去主动删除他,直到客户端对此过期键进行操作时,服务器告诉客户端“没有这个键”(因为过期了),然后把这个无效的键删除。那么考虑一种情况:成千上万个无效键一直没有被请求,那么这些无效的键就会堆积在内存中,这无疑对内存是很不利的。

那么作为一种高性能的缓存数据库,Redis是怎么做的呢?——Redis将定期删除和延时删除两种方案结合起来,从而平衡了时间和空间的取舍。

延时删除是通过expireIfNeeded函数来实现的,在对键进行操作之前都会调用expireIfNeeded来确认此键是否是无效的(如上面的lookupKeyRead函数):

/*
 * 检查 key 是否已经过期,如果是的话,将它从数据库中删除。
 *
 * 返回 0 表示键没有过期时间,或者键未过期。
 *
 * 返回 1 表示键已经因为过期而被删除了。
 */
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;
 
    /* Ifwe 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
     * onlythe first time it is accessed and not in the middle of the
     *script execution, making propagation to slaves / AOF consistent.
     * Seeissue #1525 on Github for more information. */
    now =server.lua_caller ? server.lua_time_start : mstime();
 
    /* Ifwe are running in the context of a slave, return ASAP:
     * theslave key expiration is controlled by the master that will
     * sendus synthesized DEL operations for expired keys.
     *
     *Still we try to return the right information to the caller,
     * thatis, 0 if we think the key should be still valid, 1 if
     * wethink the key is expired at this time. */
    // 当服务器运行在 replication 模式时
    // 附属节点并不主动删除 key
    // 它只返回一个逻辑上正确的返回值
    // 真正的删除操作要等待主节点发来删除命令时才执行
    // 从而保证数据的同步
    if(server.masterhost != NULL) return now > when;
 
    // 运行到这里,表示键带有过期时间,并且服务器为主节点
 
    /*Return when this key has not expired */
    // 如果未过期,返回 0
    if (now<= when) return 0;
 
    /*Delete the key */
   server.stat_expiredkeys++;
 
    // 向 AOF 文件和附属节点传播过期信息
   propagateExpire(db,key);
 
    // 发送事件通知
   notifyKeyspaceEvent(REDIS_NOTIFY_EXPIRED,
       "expired",key,db->id);
 
    // 将过期键从数据库中删除
    returndbDelete(db,key);
}

 定期删除策略是通过redis.c文件中的activeExpireCycle函数实现的, Redis会定期执行(默认100ms一次)redis.c/serverCron函数,其中就会执行databasesCron函数,而databasesCron函数包含了activeExpireCycle函数的执行:

/* Try to expire a few timed out keys. Thealgorithm used is adaptive and
 * will usefew CPU cycles if there are few expiring keys, otherwise
 * it willget more aggressive to avoid that too much memory is used by
 * keysthat can be removed from the keyspace.
 *
 * 函数尝试删除数据库中已经过期的键。
 * 当带有过期时间的键比较少时,函数运行得比较保守,
 * 如果带有过期时间的键比较多,那么函数会以更积极的方式来删除过期键,
 * 从而可能地释放被过期键占用的内存。
 *
 * No morethan REDIS_DBCRON_DBS_PER_CALL databases are tested at every
 *iteration.
 *
 * 每次循环中被测试的数据库数目不会超过 REDIS_DBCRON_DBS_PER_CALL 。
 *
 * Thiskind of call is used when Redis detects that timelimit_exit is
 * true, sothere is more work to do, and we do it more incrementally from
 * thebeforeSleep() function of the event loop.
 *
 * 如果 timelimit_exit 为真,那么说明还有更多删除工作要做,
 * 那么在 beforeSleep() 函数调用时,程序会再次执行这个函数。
 *
 * Expirecycle type:
 *
 * 过期循环的类型:
 *
 * If typeis ACTIVE_EXPIRE_CYCLE_FAST the function will try to run a
 *"fast" expire cycle that takes no longer thanEXPIRE_FAST_CYCLE_DURATION
 *microseconds, and is not repeated again before the same amount of time.
 *
 * 如果循环的类型为 ACTIVE_EXPIRE_CYCLE_FAST ,
 * 那么函数会以“快速过期”模式执行,
 * 执行的时间不会长过 EXPIRE_FAST_CYCLE_DURATION 毫秒,
 * 并且在 EXPIRE_FAST_CYCLE_DURATION 毫秒之内不会再重新执行。
 *
 * If typeis ACTIVE_EXPIRE_CYCLE_SLOW, that normal expire cycle is
 *executed, where the time limit is a percentage of the REDIS_HZ period
 * asspecified by the REDIS_EXPIRELOOKUPS_TIME_PERC define.
 *
 * 如果循环的类型为 ACTIVE_EXPIRE_CYCLE_SLOW ,
 * 那么函数会以“正常过期”模式执行,
 * 函数的执行时限为 REDIS_HS 常量的一个百分比,
 * 这个百分比由 REDIS_EXPIRELOOKUPS_TIME_PERC 定义。
 */
 
void activeExpireCycle(int type) {
    /* Thisfunction has some global state in order to continue the work
     *incrementally across calls. */
    // 静态变量,用来累积函数连续执行时的数据
    staticunsigned int current_db = 0; /* Last DB tested. */
    staticint timelimit_exit = 0;      /* Timelimit hit in previous call? */
    staticlong long last_fast_cycle = 0; /* When last fast cycle ran. */
 
   unsigned int j, iteration = 0;
    // 默认每次处理的数据库数量
   unsigned int dbs_per_call = REDIS_DBCRON_DBS_PER_CALL;
    // 函数开始的时间
    longlong start = ustime(), timelimit;
 
    // 快速模式
    if(type == ACTIVE_EXPIRE_CYCLE_FAST) {
        /*Don't start a fast cycle if the previous cycle did not exited
         *for time limt. Also don't repeat a fast cycle for the same period
         *as the fast cycle total duration itself. */
        // 如果上次函数没有触发 timelimit_exit ,那么不执行处理
        if(!timelimit_exit) return;
        // 如果距离上次执行未够一定时间,那么不执行处理
        if(start < last_fast_cycle + ACTIVE_EXPIRE_CYCLE_FAST_DURATION*2) return;
        // 运行到这里,说明执行快速处理,记录当前时间
       last_fast_cycle = start;
    }
 
    /* Weusually should test REDIS_DBCRON_DBS_PER_CALL per iteration, with
     * twoexceptions:
     *
     * 一般情况下,函数只处理 REDIS_DBCRON_DBS_PER_CALL 个数据库,
     * 除非:
     *
     * 1)Don't test more DBs than we have.
     *    当前数据库的数量小于 REDIS_DBCRON_DBS_PER_CALL
     * 2)If last time we hit the time limit, we want to scan all DBs
     * inthis iteration, as there is work to do in some DB and we don't want
     *expired keys to use memory for too much time.
     *     如果上次处理遇到了时间上限,那么这次需要对所有数据库进行扫描,
     *     这可以避免过多的过期键占用空间
     */
    if(dbs_per_call > server.dbnum || timelimit_exit)
       dbs_per_call = server.dbnum;
 
    /* Wecan use at max ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC percentage of CPU time
     * periteration. Since this function gets called with a frequency of
     *server.hz times per second, the following is the max amount of
     *microseconds we can spend in this function. */
    // 函数处理的微秒时间上限
    //ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC 默认为 25 ,也即是 25 % 的 CPU 时间
   timelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100;
   timelimit_exit = 0;
    if(timelimit <= 0) timelimit = 1;
 
    // 如果是运行在快速模式之下
    // 那么最多只能运行 FAST_DURATION 微秒
    // 默认值为 1000 (微秒)
    if(type == ACTIVE_EXPIRE_CYCLE_FAST)
       timelimit = ACTIVE_EXPIRE_CYCLE_FAST_DURATION; /* in microseconds. */
 
    // 遍历数据库
    for (j= 0; j < dbs_per_call; j++) {
        intexpired;
        // 指向要处理的数据库
       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. */
        // 为 DB 计数器加一,如果进入 do 循环之后因为超时而跳出
        // 那么下次会直接从下个 DB 开始处理
       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 there is nothing to expire try next DB ASAP. */
           // 获取数据库中带过期时间的键的数量
           // 如果该数量为 0 ,直接跳过这个数据库
           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. */
           // 这个数据库的使用率低于 1% ,扫描起来太费力了(大部分都会 MISS)
            // 跳过,等待字典收缩程序运行
           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 计数器
           ttl_sum = 0;
           // 总共处理的键计数器
           ttl_samples = 0;
 
           // 每次最多只能检查 LOOKUPS_PER_LOOP 个键
           if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)
               num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP;
 
           // 开始遍历数据库
           while (num--) {
               dictEntry *de;
               long long ttl;
 
               // 从 expires 中随机取出一个带过期时间的键
               if ((de = dictGetRandomKey(db->expires)) == NULL) break;
               // 计算 TTL
               ttl = dictGetSignedIntegerVal(de)-now;
               // 如果键已经过期,那么删除它,并将 expired 计数器增一
                if(activeExpireCycleTryExpire(db,de,now)) expired++;
               if (ttl < 0) ttl = 0;
               // 累积键的 TTL
               ttl_sum += ttl;
               // 累积处理键的个数
               ttl_samples++;
           }
 
           /* Update the average TTL stats for this database. */
           // 为这个数据库更新平均 TTL 统计数据
           if (ttl_samples) {
               // 计算当前平均值
               long long avg_ttl = ttl_sum/ttl_samples;
               
               // 如果这是第一次设置数据库平均 TTL ,那么进行初始化
               if (db->avg_ttl == 0) db->avg_ttl = avg_ttl;
               /* Smooth the value averaging with the previous one. */
               // 取数据库的上次平均 TTL 和今次平均 TTL 的平均值
               db->avg_ttl = (db->avg_ttl+avg_ttl)/2;
           }
 
            /* We can't block forever here even ifthere are many keys to
            * expire. So after a given amount of milliseconds return to the
            * caller waiting for the other active expire cycle. */
           // 我们不能用太长时间处理过期键,
           // 所以这个函数执行一定时间之后就要返回
 
           // 更新遍历次数
           iteration++;
 
           // 每遍历 16 次执行一次
           if ((iteration & 0xf) == 0 && /* check once every 16iterations. */
               (ustime()-start) > timelimit)
           {
                // 如果遍历次数正好是 16 的倍数
               // 并且遍历的时间超过了 timelimit
               // 那么断开 timelimit_exit
               timelimit_exit = 1;
           }
 
           // 已经超时了,返回
           if (timelimit_exit) return;
 
           /* We don't repeat the cycle if there are less than 25% of keys
            * found expired in the current DB. */
           // 如果已删除的过期键占当前总数据库带过期时间的键数量的 25 %
           // 那么不再遍历
        }while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值