Redis过期策略详解

Redis/缓存系统
353
0
0
2022-12-22
标签   Redis

为什么要有过期策略?

因为我们的redis是一个内存型数据库,我们的数据都是放在内存里面的!但是内存是有大小的! 比如,redis有个很重要的配置文件,redis.conf,里面有个配置

# maxmemory <bytes> //redis占用的最大内存

如果我们不淘汰,那么它的数据就会满,满了肯定就不能再放数据,发挥不了redis的作用! 比如冰箱,你如果放满了,那么你的菜就不能放冰箱了! 过期策略:拿出redis中已经过期了的数据,就像你从冰箱把坏的菜拿出来!!但是有一种情况,就是冰箱里面的菜都没坏,redis里面的数据都没过期,它也是会放满的,那怎么办?

那么当redis里面的数据都没过期。但是内存满了的时候,我们就得从未过期的数据里面去拿出一些扔掉,那么这个就是我们的淘汰策略,详见另一篇文章:Redis的淘汰策略详解

Redis自带的有两种过期策略,我们也可以自己实现一些过期的策略,不过今天主要研究自带的

惰性过期(被动过期)

这个怎么实现的呢?所谓惰性,是不是就很懒的意思,就是只有访问我的时候,我才会去判断过不过期,不然我懒得去判断,我不会主动去判断过没过期访问一个key时判断该 key 是否已过期,过期则清除该策略就可以最大化地节省CPU资源,因为它平时都懒得去判断,所以也没有啥cpu损耗,因为只有访问的时候我才去判断一下! 但是却对内存非常不友好。因为你不实时过期了,该过期删除的就可能一直堆积在内存里面!极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。 源码(expireIfNeeded db.c文件下1302行):

int expireIfNeeded(redisDb *db, robj *key) {
	if (!keyIsExpired(db,key)) return 0;
	/* If we are running in the context of a slave,instead of
	 * evicting the expired key from the database, we return ASAP:
	 * 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
	 * we think the key is expired at this time. */
	 if (server.masterhost != NULL) return 1;
	 /* Delete the key */
	 server.stat_expiredkeys++;
	 propagateExpire(db,key,server.lazyfree_lazy_expire);
	 notifyKeyspaceEvent(NOTIFY_EXPIRED,"expired",key,db->id);
	 int retval = server.lazyfree_lazy_expire ?
	 dbAsyncDelete(db,key):
	 dbSyncDelete(db,key);
	 if (retval) signalModifiedKey(NULL,db,key);
	 return retval;
}

我们刚才说了主动过期因为太耗CPU它不用 但是惰性这种又会可能导致大量无效数据堆积在内存里面,我们总得有个办法来解决吧!不能让他一直堆在内存里面啊! 所以我们就有了一个定期过期策略,虽然实时性比不上定时的,但是也足够解决垃圾数据大量堆积在内存的这种情况!

定期过期

所谓定期过期,就是每过一段时间去执行一次删除过期key。 这里需要先学习下redis的一个数据结构:字典 必须学哦 Redis数据结构——dict(字典) 大概的结构如图:

img

redis的hash默认使用的是ht[0],ht[1]不会初始化和分配空间。 哈希表dictht是用链地址法来解决碰撞问题的。如果节点数量比哈希表的大小要大很多的话,那么哈希表就会退化成多个链表,哈希表本身的性能优势就不再存在,在这种情况下需要扩容。 Redis里面的这种操作叫做rehash。 那么它怎么做rehash的,也是看上面字典这篇文章

我们来看定期过期到底是怎么实现的: 先想一下,如果让我们实现一个定期删除,应该怎么做? 我想到的是定期去循环找过期的key,然后去删掉!巧的是Redis也是这样做的 那么问题又来了:

  1. 我们去循环谁?是不是所有的key
  2. 我们多久循环一次?

第一个问题,我们并不是去循环所有的key,因为Redis里经常会存放巨多的数据,对我们需要经常清理,全部遍历一遍显然不现实,而Redis采取的是取样这个操作 具体实现方式为:


  1. 不是一次性把所有设置了过期时间的数据拿出来,而是按hash桶维度取 里面取值,取到20个值为止,如果第一个有30个,那么也会取30个! 如果一直取不到20,那么最多400个桶
  2. 删除取出值的过期key
  3. 如果400个桶都取不到值,或者取出的key 删除的比例大于10%,继续上 面的操作
  4. 每循环16次会去检测时间,超过指定时间就跳出

ps:按hash桶维度取key的逻辑是:最后一个桶会取完桶内所有的key,不论里面有多少个,每取完一个桶判断一下是否取到了20个,最多取400个桶

现在我们第一个问题解决了!那么第二个问题,定期定期,那么多久去做上面那件时间!那么redis里面有个很重要的概念叫做时间事件,那么这个时间事件是什么意思了,就是定时去做一些事情,那么redis里面有个方法叫serverCron(),在文件server.c中;就是它的时间事件去调用的清理 它里面干了很多事情,比如:

  1. 更新服务器的各类统计信息,比如时间、内存占用、数据库占用情况等
  2. 清理数据库中的过期键值对。
  3. 关闭和清理连接失效的客户端
  4. 尝试进行持久化操作

那么这个时间事件多久去执行一次呢,其实是由你们自己决定的! redis.conf 中通过 hz 配置,hz代表的意思是每秒执行多少次!默认10次,也就是100ms我们就会去执行定期过期!!

定期过期的逻辑,简单画图

img

怎么样,学会了Redis的过期策略了吧,还不一键三连