我们知道 Redis 是分布式内存数据库,基于内存运行,访问速度非常快。可是,市面上比较好的服务器内存也不过几百G(像那种上 T 内存大小的服务器列外),那么能存多少数据呢?当服务器内存占用满了之后该怎么办呢?Redis 的内存是否可以设置限制? Redis 过期的 key 是怎么从内存中删除的?
在 Redis 中是可以设置内存最大限制的,因此我们不用担心 Redis 占满服务器的内存,从而影响其他服务。通过配置 maxmemory 参数就可以实现内存大小限制,语法如下:
# 配置文件 maxmemory <bytes>
下面的 maxmemory 参数写法均合法:
maxmemory 1024000 maxmemory 1GB maxmemory 1G maxmemory 1024KB maxmemory 1024K maxmemory 1024MB
除了修改 redis.config 的 maxmemory 参数外,还可以通过命令行修改内存大小限制,例如:
# 命令行 127.0.0.1:6379> config get maxmemory 1) "maxmemory" 2) "0" 127.0.0.1:6379> config set maxmemory 1GB OK 127.0.0.1:6379> config get maxmemory 1) "maxmemory" 2) "1073741824"
小提示:
maxmemory 参数默认值为 0。因 32 位系统支持的最大内存为 4GB,所以在 32 位系统上 Redis 的默认最大内存限制为3GB;在 64 位系统上默认 Redis 最大内存即为物理机的可用内存;
Redis 中共有下面八种内存淘汰策略:
volatile-lru:设置了过期时间的 key 使用 LRU 算法淘汰;
allkeys-lru:所有 key 使用 LRU 算法淘汰;
volatile-lfu:设置了过期时间的 key 使用 LFU 算法淘汰;
allkeys-lfu:所有 key 使用 LFU 算法淘汰;
volatile-random:设置了过期时间的 key 使用随机淘汰;
allkeys-random:所有 key 使用随机淘汰;
volatile-ttl:设置了过期时间的 key 根据过期时间淘汰,越早过期越早淘汰;
noeviction:默认策略,当内存达到设置的最大值时,所有申请内存的操作都会报错(如 set, lpush 等),只读操作如 get 命令可以正常执行;
注意:LRU、LFU 和 volatile-ttl 都是近似随机算法;
在 redis 中,可以使用 maxmemory-policy 参数配置淘汰策略,例如:
(1)修改 redis.conf 配置文件
# 配置文件 maxmemory-policy noeviction
(2)通过 redis 命令行
127.0.0.1:6379> config get maxmemory-policy 1) "maxmemory-policy" 2) "noeviction" 127.0.0.1:6379> config set maxmemory-policy allkeys-random OK 127.0.0.1:6379> config get maxmemory-policy 1) "maxmemory-policy" 2) "allkeys-random
在缓存的内存淘汰策略中有 FIFO(先进先出)、LRU(最近最少使用到的)、LFU(最近最不经常使用)三种,Redis 使用了 LRU 和 LFU 淘汰策略。
按照 “先进先出(First In,First Out)” 的原理淘汰数据,正好符合队列的特性,数据结构上使用队列 Queue 来实现。如下图:
LRU(Least Recently Used)表示最近最少使用,该算法根据数据的历史访问记录来进行淘汰数据,其核心思想是 “如果数据最近被访问过,那么将来被访问的几率也更高”。
LRU 算法的常见实现方式为链表:新数据放在链表头部,链表中的数据被访问就移动到链头,链表满的时候从链表尾部移出数据。如下图:
而在 Redis 中使用的是近似 LRU 算法,为什么说是近似呢?Redis 中是随机采样 5 个(可以修改参数 maxmemory-samples 配置)key,然后从中选择访问时间最早的 key 进行淘汰,因此当采样 key 的数量与 Redis 库中 key 的数量越接近,淘汰的规则就越接近 LRU 算法。但官方推荐5个就足够了,最多不超过10个,越大就越消耗 CPU 的资源。
但在 LRU 算法下,如果一个热点数据最近很少访问,而非热点数据近期访问了,就会误把热点数据淘汰而留下了非热点数据,因此在 Redis4.x 中新增了 LFU 算法。
在 LRU 算法下,Redis 会为每个 key 新增一个3字节的内存空间用于存储 key 的访问时间;
LFU(Least Frequently Used)表示最不经常使用,它是根据数据的历史访问频率来淘汰数据,其核心思想是“如果数据过去被访问多次,那么将来被访问的频率也更高”。
LFU算法反映了一个key的热度情况,不会因LRU算法的偶尔一次被访问被误认为是热点数据。
LFU算法的常见实现方式为链表:新数据放在链表尾部 ,链表中的数据按照被访问次数降序排列,访问次数相同的按最近访问时间降序排列,链表满的时候从链表尾部移出数据。如下图:
前面介绍的 LRU 和 LFU 算法都是在 Redis 内存占用满的情况下的淘汰策略,那么当内存没占满时在 Redis 中过期的key 是如何从内存中删除以达到优化内存占用的呢?
官网:https://redis.io/commands/expire#expire-accuracy
在Redis中过期的key不会立刻从内存中删除,而是会同时以下面两种策略进行删除:
惰性删除:当key被访问时检查该key的过期时间,若已过期则删除;已过期未被访问的数据仍保持在内存中,消耗内存资源;
定期删除:每隔一段时间,随机检查设置了过期的key并删除已过期的key;维护定时器消耗CPU资源;
Redis每10秒进行一次过期扫描:
随机取20个设置了过期策略的key;
检查20个key中过期时间中已过期的key并删除;
如果有超过25%的key已过期则重复第一步;
这种循环随机操作会持续到过期key可能仅占全部key的25%以下时,并且为了保证不会出现循环过多的情况,默认扫描时间不会超过25ms;
前面介绍了Redis的持久化策略 RDB 和 AOF,当 Redis 中的 key 已过期未删除时,如果进行 RDB 和 AOF 的持久化操作时候会怎么操作呢?
在 RDB 持久化模式中我们可以使用 save 和 bgsave 命令进行数据持久化操作
在 AOF 持久化模式中使用 rewriteaof 和 bgrewriteaof 命令进行持久化操作
这四个命令都不会将过期 key 持久化到 RDB 文件或 AOF 文件中,可以保证重启服务时不会将过期 key 载入 Redis。
为了保证一致性,在 AOF 持久化模式中,当 key 过期时候,会同时发送 DEL 命令给 AOF 文件和所有节点;
从节点不会主动的删除过期 key 除非它升级为主节点或收到主节点发来的 DEL 命令;