Redis 之内存淘汰源码分析

一、前述

在上一篇文章中,介绍了一下 Redis LRU 内存淘汰、LFU 内存淘汰以及两种算法的区别:Redis 之 LRU 与 LFU
可以知道,LRU我们可以配置六种策略,LFU 同样可以配置,那么今天就来看看源码, Redis 是怎样的一个逻辑来进行内存淘汰的

二、 源码概览以及解析

算是第一篇解析源码的文章,一边看着源码注释一边自己理解写下来的,当中有什么不对或者改进的,还希望留言指教

/*
 * 1、内存淘汰调用的方法是 freeMemoryIfNeededAndSafe(),实际调用如下方法
 * 这个方法会定期的被调用,以便于查看根据当前设置的 maxmemory 来去确定释放多少内存,假如我们的应用程序超过了这
 * 个设置的内存限制,这个方法将会尝试释放内存以达到内存限制以下
 *  
 * 2、当我们的应用程序使用的内存低于当前服务设置的内存限制以下,或者曾经在内存限制以上,但是后来尝试释放内存成功
 * 就会返回 C_OK
 * 否则当占用的内存在内存限制以上,但是又没有足够的内存可以被释放到内存限制以下,方法就会返回 C_ERR
 * 
 * 3、在 redis 源码的 server.h 文件中,定义如下
 * C_OK        0
 * C_ERR      -1
 */
int freeMemoryIfNeeded(void) {																		(1)

    //默认情况下,只有主节点需要来释放内存,
    if (server.masterhost && server.repl_slave_ignore_maxmemory) return C_OK;						(2)
    
    size_t mem_reported, mem_tofree, mem_freed;
    mstime_t latency, eviction_latency;
    long long delta;
    //所有的从节点数
    int slaves = listLength(server.slaves);															(3)
    if (clientsArePaused()) return C_OK;															(4)
    
   /*
    * 1、获取内存用量状态,如果在设置的内存限制以下,返回 C_OK,不需要释放内存
    * 2、getMaxmemoryState 会计算总共使用了多少内存,需要释放多少内存
    */
    if (getMaxmemoryState(&mem_reported,NULL,&mem_tofree,NULL) == C_OK)								(5)
        return C_OK;
	//初始化释放的内存数值,用来统计释放了多少内存
    mem_freed = 0;
    
	// 当前设置的是不进行内存淘汰,goto 跳转处理逻辑
    if (server.maxmemory_policy == MAXMEMORY_NO_EVICTION)											(6)
        goto cant_free; 
        
	//延迟开启监控
    latencyStartMonitor(latency);																	(7)
    //尝试释放足够大的内存
    while (mem_freed < mem_tofree) {																(8)
        int j, k, i, keys_freed = 0;
        static unsigned int next_db = 0;
        sds bestkey = NULL;
        int bestdbid;
        redisDb *db;
        dict *dict;
        dictEntry *de;
	    //
        if (server.maxmemory_policy & (MAXMEMORY_FLAG_LRU|MAXMEMORY_FLAG_LFU) ||
            server.maxmemory_policy == MAXMEMORY_VOLATILE_TTL)
        {
            struct evictionPoolEntry *pool = EvictionPoolLRU;

            while(bestkey == NULL) {
                unsigned long total_keys = 0, keys;
                
                /*
                 *  获取所有需要被淘汰的 key 
                 */
                for (i = 0; i < server.dbnum; i++) {												(9)
                    db = server.db+i;
                    dict = (server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) ?
                            db->dict : db->expires;
                    if ((keys = dictSize(dict)) != 0) {
                    
                    /*
                     * 这是freememoryifrequired()的一个帮助函数,以便于在每次过期一个 key 的时候在 evictionPool 里填充一个键值对空间
                     * 空闲时间小于当前键里面任何一个键,这个 key 就会被添加。
                     * 按照升序的方式插入 key, 闲置时间越短的在左边,长的在右边
                     * /
                        evictionPoolPopulate(i, dict, db->dict, pool);
                        total_keys += keys;
                    }
                }
               /*
                * 没有需要被淘汰的 Key
                */
                if (!total_keys) break; 															(10)
				//遍历数据库 ,从所有的 key 里获取到字典键值对,或者从过期的 key 里面获取
                for (k = EVPOOL_SIZE-1; k >= 0; k--) {												(11)
                    if (pool[k].key == NULL) continue;
                    bestdbid = pool[k].dbid;
                    if (server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) {
                        de = dictFind(server.db[pool[k].dbid].dict,
                            pool[k].key);
                    } else {
                        de = dictFind(server.db[pool[k].dbid].expires,
                            pool[k].key);
                    }
                    
                    //从 pool 里移除键值对
                    if (pool[k].key != pool[k].cached)												(12)
                        sdsfree(pool[k].key);
                    pool[k].key = NULL;
                    pool[k].idle = 0;
                    
                    //如果存在就获取到最需要淘汰的 key,否则需要尝试下一个元素来判断
                    if (de) {																		(13)
                        bestkey = dictGetKey(de);
                        break;
                    } else {
                      																				(14)
                    }
                }
            }
        }
        
        //如果淘汰策略是 allkeys_random 以及 volatile_random
        else if (server.maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM || 							(15)
                 server.maxmemory_policy == MAXMEMORY_VOLATILE_RANDOM)
        {
            for (i = 0; i < server.dbnum; i++) {													(16)
                j = (++next_db) % server.dbnum;
                db = server.db+j;
                dict = (server.maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM) ?
                        db->dict : db->expires;
                if (dictSize(dict) != 0) {
                    de = dictGetRandomKey(dict);
                    bestkey = dictGetKey(de);
                    bestdbid = j;
                    break;
                }
            }
        }
        
        //最后移除被选出来的 key 
        if (bestkey) {																				(17)
            db = server.db+bestdbid;
            robj *keyobj = createStringObject(bestkey,sdslen(bestkey));
            
            //主节点 key 过期时,会发送这个 key 的删除指令到所有的从节点以及 aof 文件,如果启用了主从的话
            propagateExpire(db,keyobj,server.lazyfree_lazy_eviction);
            
            //获取当前以及占用的内存空间大小,因为 AOF 和输出缓冲区最后都会被释放,因此我们仅仅只要关心 key 占用的空间就好了
            delta = (long long) zmalloc_used_memory();												(18)
            latencyStartMonitor(eviction_latency);
            
			//如果开启了惰性释放,就会对这个 key 对象做异步的删除,否则就是同步
            if (server.lazyfree_lazy_eviction)
                dbAsyncDelete(db,keyobj);
            else
                dbSyncDelete(db,keyobj);
			//结束延迟监控
            latencyEndMonitor(eviction_latency);
            //延迟抽样采集
            latencyAddSampleIfNeeded("eviction-del",eviction_latency);
            latencyRemoveNestedEvent(latency,eviction_latency);
            delta -= (long long) zmalloc_used_memory();
            
            //统计释放的内存,就是用之前统计的那次内存数值减去现在的就是刚才释放的
            mem_freed += delta;
            server.stat_evictedkeys++;
            
            //发布一个 key 进行内存淘汰的事件
            notifyKeyspaceEvent(NOTIFY_EVICTED, "evicted",
                keyobj, db->id);
                
            //更新引用计数器
            decrRefCount(keyobj);
            keys_freed++;

			/*
			 * 当要释放的内存足够大的时候,可能会花很多时间在这个地方,导致延迟,以至于不可能足够快的将数据传输到从节点,所以我们强制
			 * 在循环内传输同步数据
			 */
            if (slaves) flushSlavesOutputBuffers();													(19)
            
			/*
			 * 如果是异步删除,需要在循环过程中定期去看后台是否释放了足够的内存,默认每 16 个 Key 循环检查一次
			 * 一般来说停止的条件就是能够固定的释放,能够提前计算内存的总数,然而当另外一个线程正在删除对象 key 的时候,最好检查下,因为
			 * 有时候如果已经到了目标的内存大小值,但是 mem_freed 这个值却是只在 dbAsyncDelete 中计算的,与此同时一直有线程在释放内存
			 * 就会导致计算出错
			 * /
            if (server.lazyfree_lazy_eviction && !(keys_freed % 16)) {								(20)
                if (getMaxmemoryState(NULL,NULL,NULL,NULL) == C_OK) {
                //满足停止的条件
                    mem_freed = mem_tofree;															(21)
                }
            }
        }
		//没有 key 被释放,结束延迟监控,处理对应逻辑														(22)
        if (!keys_freed) {
            latencyEndMonitor(latency);
            latencyAddSampleIfNeeded("eviction-cycle",latency);
            goto cant_free; /* nothing to free... */
        }
    }
    latencyEndMonitor(latency);
    latencyAddSampleIfNeeded("eviction-cycle",latency);
    return C_OK;
 /*
  * 无法释放内存时,阻塞,检查一下后台清理线程是否还有任务正在清理,等他清理出足够内存之后
  * 再退出
  * 
  */
cant_free:																							(23)
    while(bioPendingJobsOfType(BIO_LAZY_FREE)) {
        if (((mem_reported - zmalloc_used_memory()) + mem_freed) >= mem_tofree)
            break;
        usleep(1000);
    }
    return C_ERR;
}

三、总结

沉下心来,细细的查看源代码,可以让我们发现很多平时我们工作中根本察觉不到的底层变化,能够更好的使用
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值