因为我们的redis是一个内存型数据库
,我们的数据都是放在内存里面的
!但是内存是有大小的
!
比如,redis有个很重要的配置文件,redis.conf,里面有个配置
# maxmemory //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(字典)
大概的结构如图:
redis的hash默认使用的是ht[0],ht[1]不会初始化和分配空间。
哈希表dictht是用链地址法来解决碰撞问题的。如果节点数量比哈希表的大小要大很多的话,那么哈希表就会退化成多个链表,哈希表本身的性能优势就不再存在,在这种情况下需要扩容。
redis里面的这种操作叫做rehash。 那么它怎么做rehash的,也是看上面字典这篇文章
我们来看定期过期到底是怎么实现的:
先想一下,如果让我们实现一个定期删除,应该怎么做?
我想到的是定期去循环找过期的key,然后去删掉
!巧的是redis也是这样做的
那么问题又来了:
- 我们去循环谁?是不是所有的key
- 我们多久循环一次?
第一个问题,我们并不是去循环所有的key,因为redis里经常会存放巨多的数据,对我们需要经常清理,全部遍历一遍显然不现实,而redis采取的是取样
这个操作
具体实现方式为:
- 不是一次性把所有设置了过期时间的数据拿出来,而是按hash桶维度取 里面取值,取到20个值为止,如果第一个有30个,那么也会取30个! 如果一直取不到20,那么最多400个桶
- 删除取出值的过期key
- 如果400个桶都取不到值,或者取出的key 删除的比例大于10%,继续上 面的操作
- 每循环16次会去检测时间,超过指定时间就跳出
ps:按hash桶维度取key的逻辑是:最后一个桶会取完桶内所有的key,不论里面有多少个,每取完一个桶判断一下是否取到了20个,最多取400个桶
。
现在我们第一个问题解决了!那么第二个问题,定期定期,那么多久去做上面那件时间!那么redis里面有个很重要的概念叫做时间事件,那么这个时间事件是什么意思了,就是定时去做一些事情,那么redis里面有个方法叫servercron(),在文件server.c中;就是它的时间事件去调用的清理
它里面干了很多事情,比如:
- 更新服务器的各类统计信息,比如时间、内存占用、数据库占用情况等
- 清理数据库中的过期键值对。
- 关闭和清理连接失效的客户端
- 尝试进行持久化操作
那么这个时间事件多久去执行一次呢,其实是由你们自己决定的!
redis.conf 中通过 hz 配置,hz代表的意思是每秒执行多少次!默认10次,也就是100ms我们就会去执行定期过期!!
定期过期的逻辑,简单画图