Redis主键失效实现机制

作为我们经常使用的内存数据库,redis在其内存管理以及主键失效的实现是怎么样的呢?我们一起通过粗略的阅读源代码(使用版本:3.2.2)来看看其实现机制:

第一种方式:主动轮询删除

首先其核心文件server.c的main函数里的initServer()函数里的相关代码:

void initServer(void) { 
	....
	/* Create the serverCron() time event, that's our main way to process
     * background operations. */
    if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
        serverPanic("Can't create the serverCron time event.");
        exit(1);
    }
	....
}

从注释可以看到其创建了一个时间事件的回调函数serverCron,我们继续看看serverCron里的相关代码:

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { 
	....
	/* Handle background operations on Redis databases. */
    databasesCron();
	....
}

继续追databasesCron:

void databasesCron(void) {
	....
	/* Expire keys by random sampling. Not required for slaves
     * as master will synthesize DELs for us. */
    if (server.active_expire_enabled && server.masterhost == NULL)
        activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
	....
}

继续activeExpireCycle:

void activeExpireCycle(int type) {
	....
	do {
		long long t = dictGetSignedIntegerVal(de);
	    if (now > t) {
	        //删除前将要删除的key广播出去	
	        propagateExpire(db,keyobj);
	        //删除该key
	        dbDelete(db,keyobj);	        
	    } 
			/* We don't repeat the cycle if there are less than 25% of keys
             * found expired in the current DB. */
        } while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);
	....
}

即当数据库中失效的key大于25%时,在限制的时间内轮询执行删除key操作,并且在删除key之前,将要删除的key广播出去,广播去哪里了呢,我们继续看代码(此代码位于db.c):

void propagateExpire(redisDb *db, robj *key) {
    ....
    if (server.aof_state != AOF_OFF)
        feedAppendOnlyFile(server.delCommand,db->id,argv,2);
    replicationFeedSlaves(server.slaves,db->id,argv,2);
    ....
}

可以看到,广播给了可能开启的AOF持久化功能,还有自己的slave(这里我们回顾一下在执行activeExpireCycle函数时候的一个判断语句有一个 server.masterhost == NULL,即自己是主库的情况下才会主动去删除过期key并广播给从库,也就是说,这种轮询方式下从库是不需要主动清理过期key的)

第二种方式:被动消极删除

第一种方式虽然能保证大部分过期key的清理,但是我们也明显可以看到其是有弊端的,即并不能实时清理过期的key,总有一些漏网之鱼,那么这些key在访问的时候就会造成错误,所以redis还有一种被动的删除,即访问时判断是否过期,以下罗列一些源码:

/* Lookup a key for read operations, or return NULL if the key is not found
 * in the specified DB.  
 */
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
    ....
    if (expireIfNeeded(db,key) == 1) {
    	....
	}
    ....
}

以上代码监听key的读取

/* Lookup a key for write operations, and as a side effect, if needed, expires
 * the key if its TTL is reached.
*/
robj *lookupKeyWrite(redisDb *db, robj *key) {
    expireIfNeeded(db,key);                                                                                                          
    return lookupKey(db,key,LOOKUP_NONE);
}

以上代码监听key的写入

/* EXISTS key1 key2 ... key_N.
 * Return value is the number of keys existing. */
void existsCommand(client *c) {
    ....
    for (j = 1; j < c->argc; j++) {
        expireIfNeeded(c->db,c->argv[j]);                                                                                            
        if (dbExists(c->db,c->argv[j])) count++;
    }
    ....
}

以上代码查看key是否存在

还有其他一些源码就不一一列出了,这几段源码我们可以看到其中都调用了expireIfNeeded()函数,那么它是做什么的呢,追下源码:

int expireIfNeeded(redisDb *db, robj *key) {
    mstime_t when = getExpire(db,key);
    mstime_t now;
	//key不过期,直接返回
    if (when < 0) return 0; 
    //正在进行rdb数据恢复,直接返回
    if (server.loading) return 0;
 	//如果是从库,只返回是否过期即可   
    if (server.masterhost != NULL) return now > when;
	//广播
    propagateExpire(db,key);
	//删除
    return dbDelete(db,key);                                                                                                         
}

可以看到,其实在redis的每一次对key的操作的同时都会去检查一下这个key是否过期了,如果过期了就会做和轮询时一样的事情,广播并且删除该key。

第三种方式:OOM时的无奈删除

我们知道,以上两种方式已经可以完全保证数据的准确性了,但是如果当发生OOM(Out of Memory)时redis又该怎么办呢,是写不进去,还是如何处理?实际上,当OOM发生时,redis的LRU机制就会启动,老的数据将会被删除,我们通过redis的配置文件(config.c)看下redis都支持哪些LRU算法:

configEnum maxmemory_policy_enum[] = {
    {"volatile-lru", MAXMEMORY_VOLATILE_LRU},
    {"volatile-random",MAXMEMORY_VOLATILE_RANDOM},
    {"volatile-ttl",MAXMEMORY_VOLATILE_TTL},
    {"allkeys-lru",MAXMEMORY_ALLKEYS_LRU},
    {"allkeys-random",MAXMEMORY_ALLKEYS_RANDOM},
    {"noeviction",MAXMEMORY_NO_EVICTION},
    {NULL, 0}
};

看下redis的作者对这些配置的解释吧==> Redis as an LRU cache。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值