Redis淘汰机制+热点数据问题

为什么需要淘汰

Redis是内存数据库,我们能时时刻刻能感受到Redis作者为更好地使用内存而费尽各种心思,例如最明显的是对于同一种数据结构在不同应用场景下提供了基于不同底层编码的实现(如压缩列表、跳跃表等)。

Redis最常见的两种应用场景为缓存和持久存储,当Redis做缓存时,有一个Redis服务器,服务器物理内存大小为1G的,我们需要存在Redis中的数据量很小,这看起来似乎足够用很长时间了,随着业务量的增长,我们放在Redis里面的数据越来越多了,数据量大小似乎超过了1G,但是应用还可以正常运行,这是因为操作系统的可见内存并不受物理内存限制,而是虚拟内存,物理内存不够用没关系,操作系统会从硬盘上划出一片空间用于构建虚拟内存,比如32位的操作系统的可见内存大小为2^32,而用户空间的可见内存要小于2^32很多,大概是3G左右。好了,我们庆幸操作系统为我们做了这些,但是我们需要知道这背后的代价是不菲的,不合理的使用内存有可能发生频繁的swap,频繁swap的代价是惨痛的。所以回过头来看,作为有追求的程序员,我们还是要小心翼翼地使用好每块内存,把用户代码能解决的问题尽量不要抛给操作系统解决。

所以,为了更好的利用内存,使Redis存储的都是缓存的热点数据,Redis设计了相应的内存淘汰机制(也可以叫做缓存淘汰机制)。

配置文件常见修改

开启淘汰机制

我们可以通过配置redis.conf中的maxmemory这个值来开启内存淘汰功能

maxmemory <bytes>

修改淘汰策略

根据应用场景,选择淘汰策略

maxmemory-policy noeviction

常见命令修改(动态、无需重启)

扫描二维码关注公众号,回复: 9055625 查看本文章

设置最大内存

config set maxmemory 100000

设置淘汰策略

config set maxmemory-policy noeviction

内存淘汰的过程:

1、首先,客户端发起了需要申请更多内存的命令(如set)。

2、然后,Redis检查内存使用情况,如果已使用的内存大于maxmemory则开始根据用户配置的不同淘汰策略来淘汰内存(key),从而换取一定的内存。

3、最后,如果上面都没问题,则这个命令执行成功。

提示:maxmemory为0的时候表示我们对Redis的内存使用没有限制。

数据删除策略有三种

1、被动删除:只有key被操作时(如GET),Redis才会被动检查该key是否过期,如果过期则删除之并且返回NIL。

2、主动删除:定期删除过期的数据,可以配置。

3、当前已用内存超过maxmemory限定时,触发数据淘汰策略。

被动删除特点:

1、这种删除策略对CPU是友好的,删除操作只有在不得不的情况下才会进行,不会其他的expire key上浪费无谓的CPU时间。

2、但是这种策略对内存不友好,一个key已经过期,但是在它被操作之前不会被删除,仍然占据内存空间。如果有大量的过期键存在但是又很少被访问到,那会造成大量的内存空间浪费。

Redis提供了6种数据淘汰策略

1、volatile-lru 

从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰。注意:redis并不是保证取得所有数据集中最近最少使用的键值对,而只是随机挑选的几个键值对中的, 当内存达到限制的时候无法写入非过期时间的数据集。

2、volatile-ttl

从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰。注意:redis 并不是保证取得所有数据集中最近将要过期的键值对,而只是随机挑选的几个键值对中的, 当内存达到限制的时候无法写入非过期时间的数据集。

3、volatile-random

从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。当内存达到限制的时候无法写入非过期时间的数据集。

4、allkeys-lru

从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰。当内存达到限制的时候,对所有数据集挑选最近最少使用的数据淘汰,可写入新的数据集。

5、allkeys-random

从数据集(server.db[i].dict)中任意选择数据淘汰,当内存达到限制的时候,对所有数据集挑选随机淘汰,可写入新的数据集。

6、no-enviction

当内存达到限制的时候,不淘汰任何数据,不可写入任何数据集,所有引起申请内存的命令会报错。

淘汰策略选择

根据使用经验, 一般来说回收策略可以这样来配置:

  • allkeys-lru:如果期望用户请求呈现幂律分布(power-law distribution),也就是,期望一部分子集元素被访问得远比其他元素多时,可以使用allkeys-lru策略。在你不确定时这是一个好的选择

  • allkeys-random:如果期望是循环周期的访问,所有的键被连续扫描,或者期望请求符合平均分布(每个元素以相同的概率被访问),可以使用allkeys-random策略。

  • volatile-ttl:如果你期望能让 Redis 通过使用你创建缓存对象的时候设置的TTL值,确定哪些对象应该是较好的清除候选项,可以使用volatile-ttl策略。

另外值得注意的是,为键设置过期时间需要消耗内存,所以使用像allkeys-lru这样的策略会更高效,因为在内存压力下没有必要为键的回收设置过期时间。

非精准的LRU

上面提到的LRU(Least Recently Used)策略,实际上Redis实现的LRU并不是可靠的LRU,也就是名义上我们使用LRU算法淘汰键,但是实际上被淘汰的键并不一定是真正的最久没用的,这里涉及到一个权衡的问题,如果需要在全部键空间内搜索最优解,则必然会增加系统的开销,Redis是单线程的,也就是同一个实例在每一个时刻只能服务于一个客户端,所以耗时的操作一定要谨慎。为了在一定成本内实现相对的LRU,早期的Redis版本是基于采样的LRU,也就是放弃全部键空间内搜索解改为采样空间搜索最优解。自从Redis3.0版本之后,Redis作者对于基于采样的LRU进行了一些优化,目的是在一定的成本内让结果更靠近真实的LRU。

Redis热点数据问题

问题:由于内存限制,导致了Redis中只能存储最多1W条的数据信息,如何确保这1w条数据是最热门的数据?

解决方案:

1、热点数据排序(点击次数)

既然热门数据,那么就需要有排序,使用redis中的zset数据类型是很自然的想法。数据中的某个唯一字段作为zset中的value,而点击次数作为score,记为click_zset。这样就可选出最热门的数据。而数据,则直接用HashMap存储。

2、热点数据时间(近期访问)

既然只能存1w条数据且需要是热门数据,那么,点击次数是一方面,时效性也是一方面,如何保证?可以另起一个zset,数据的字段为value,而每次点击时更新当前时间戳为其score,记为time_zset这样,就可以记录时间。在后台跑一个任务,间隔一定时间段删除两个zset中长时间没有发生点击事件的键,并删除hash数据,为产生的新数据腾出数据空间。

3、处理新热点数据

如果有空间,则保存到自己的hashmap,并将key存到两个zset中。

而没有空间时,就应该在click_zset中取出点击次数排在最前第1w位后面的键,删除对应的hash数据。然后看这1w个score的值,然后把key放入两个zset中即可。

4、利用redis4.x自身特性,LFU机制发现热点数据

实现很简单,只要把redis内存淘汰机制设置为allkeys-lfu或者volatile-lfu方式,再执行

./redis-cli --hotkeys

会返回访问频率高的key,并从高到底的排序:

这就是我们的热点数据的key了。

喜欢就点个"在看"呗,留言、转发朋友圈

发布了136 篇原创文章 · 获赞 442 · 访问量 51万+

猜你喜欢

转载自blog.csdn.net/xcbeyond/article/details/103419644