探索redis数据过期策略

为什么要处理数据过期

1.过期设置为程序逻辑的一部分,所以为了保证逻辑正确(不读取到过期数据),不得不对缓存做数据过期处理
2.过期数据,对业务来说已是无用数据,但是却仍然占有服务资源(主要是内存和磁盘),故处理过期数据,将其删除可以使服务资源得到释放

处理过期数据的常用策略

策略 说明 优点 缺点
定时删除 根据键的过期时间设置定时器,触发超时及删除对应键 删除及时,内存友好 在内存不紧张,cpu紧张的情况下,cpu不友好
惰性删除 程序在取键时才对键进行过期检查 cpu友好,只有在取键时才可能会触发删除当前键 内存不友好
定期删除 每隔一段时间执行一次限时限量的批量删除 相对中性,不像定时删除那样可能短时间占用大量cpu,也没有惰性删除浪费内存空间多 -

redis同时采用定期删除和惰性删除策略: 使用定期策略可以更平滑的利用cpu和内存资源,但是会存在过期数据失效不及时的问题。用惰性删除加以辅助便可达到定期删除下访问实时失效的效果

redis惰性删除

redis在客户端获取数据时,首先判断数据是否设置过期时间,如果设置了过期时间,且当前时间大于过期时间则删除对应的key

核心代码: db.c:expireIfNeeded函数中

//返回0未过期、大于0过期
int expireIfNeeded(redisDb *db, robj *key) {
    //key没设置超时时间返回-1,设置超时时间则返回超时时间的ms值
	mstime_t when = getExpire(db,key);
    mstime_t now;

	//未设置过期时间
    if (when < 0) return 0; 

    if (server.loading) return 0;

	//如果该缓存是slave,则只返回结果,不执行后续删除键操作
    if (server.masterhost != NULL) return now > when;
	//master过期则直接返回0
    if (now <= when) return 0;

    server.stat_expiredkeys++;
	//往aof文件里添加删除命令,同时也给slave发送删除key的命令
    propagateExpire(db,key,server.lazyfree_lazy_expire);
    notifyKeyspaceEvent(NOTIFY_EXPIRED,
        "expired",key,db->id);
	//删除过期键
    return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
                                         dbSyncDelete(db,key);
}

redis定期删除

除了惰性删除外,redis还会定期批量删除过期键

核心代码: expire:activeExpireCycle函数

void activeExpireCycle(int type) {
 
	...
	int j, iteration = 0;
    for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {
        int expired;
		//递增选取一个数据库进行接下来的删除工作(redis支持多数据库)
        redisDb *db = server.db+(current_db % server.dbnum);

        current_db++;

        do {
            unsigned long num, slots;
            long long now, ttl_sum;
            int ttl_samples;
            iteration++;
			//超时字典里没有任何数据,则直接退出
            if ((num = dictSize(db->expires)) == 0) {
                db->avg_ttl = 0;
                break;
            }
            slots = dictSlots(db->expires);
			//获取当前时间
            now = mstime();
			//过期字典中元素数少于其slot位数量的1%则不处理过期(说明过期的健很少,对系统内存影响不大,先不处理)
            if (num && slots > DICT_HT_INITIAL_SIZE &&
                (num*100/slots < 1)) break;

            expired = 0;
            ttl_sum = 0;
            ttl_samples = 0;
			//控制循环次数为<=ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP(20次以内)
            if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)
                num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP;

            while (num--) {
                dictEntry *de;
                long long ttl;
				//随机获取过期字典中的某个键
                if ((de = dictGetRandomKey(db->expires)) == NULL) break;
				//获取该键的超时时间
                ttl = dictGetSignedIntegerVal(de)-now;
				//如果该键超时则删除之
                if (activeExpireCycleTryExpire(db,de,now)) expired++; 

			//每循环16次则进行如下检查
            if ((iteration & 0xf) == 0) { /* check once every 16 iterations. */
                elapsed = ustime()-start;
				//删除过期键执行时间超过最大执行时间(1s),则退出删除操作
                if (elapsed > timelimit) {
                    timelimit_exit = 1;
                    server.stat_expired_time_cap_reached_count++;
                    break;
                }
            }
		//当随机抽取的键超过1/4过期(说明过期键的比例相对比较高),则继续循环删除
        } while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);
    }
}
...

定期删除由redis中databasesCron接口调用activeExpireCycle触发(每秒执行一次)
定期删除流程如下:

关键点分析:
1.为什么存储设置了过期键的槽位占过期字典总槽位1%以下就不处理过期?
答: 因为此种情况下过期键的数量很少,对内存利用率的影响并不显著,暂时无需花费大量cpu处理过期删除任务
2.为什么随机查找某个库过期字典中20个元素,过期键超过1/4就继续执行此操作?
答: 随机挑选20个元素,过期比例超过1/4(这里属于抽样调查),则过期键相对挺多,可能对内存的使用率造成影响。故可循环执行此删除操作
3.为什么删除操作执行超过1s就会停止删除
答: redis执行删除操作会在主线程执行, 如果过期键太多,执行时间太长会使当前redis处于阻塞状态无法执行其他命令。所以加此最大时间限制可尽可能的减少此种情况下对服务的影响

redis持久化如何应对过期数据

rdb持久化模式下:
生成rdb文件时: redis会检查保存的数据是否过期,如果过期则不会写入rdb文件
载入rdb文件时: redis会对rdb文件中的数据进行过期检查,已过期的数据会被忽略,不写入内存

aof持久化模式下:
追加aof文件时: 当有数据被删除,redis会追加该删除命令倒aof文件中去
重写aof文件时: 已过期的数据不会写入新aof文件(重写文件体积压缩的原因之一)

###redis复制如何应对过期数据
这里的复制指的是redis主备下的复制,前文介绍的其实都是redis主机处理过期键的方式,那么redis备机处理过期方式有何不同呢?

redis备机处理过期:
redis备机不主动对过期键进行删除,主机在处理过期键时会发送del命令给备机,此时备机才会删除对应的过期键

可以看出redis备机处理过期键相当简单,只需等待主机发来del命令执行即可,但是关于备机过期键这里还是需要弄清楚如下几个问题:
1.reids备机为啥不自己去执行类似主机的删除策略去处理过期键?
答: 因为redis需要保证主备机数据的一致性,如果各自处理过期数据,会造成数据不一致。可能有些人会疑惑,既然数据都过期了,不管删不删用户都不会访问到此数据了,所以站到用户的角度其实数据还是一致的呀?这里其实作者起初也认为如此,但是不知道您知不知道redis提供了一个移除键过期时间的命令:persist,在极端情况下,例如主备发生分区,然后主机对某个设置了过期时间的key执行了persist将其变为永久key,此时备机收不到主机的persist命令,当key过期时,备机将其移除。这种情况下就明显发生了主备不一致的现象
2.既然备机不主动执行删除操作,那么用户访问备机上的某个备机键,能获得结果么?
答: 当有客户端访问备机过期键时,备机不执行删除操作,但是会给客户端返回空

总结

redis源码相对比较简洁,代码注释详细,且对应的分析书籍较多。多学习学习总有收获

你可能感兴趣的:(数据库,linux,redis,缓存)