作为一台服务器来说,内存并不是无限的,所以总会存在内存耗尽的情况,那么当 Redis 服务器的内存耗尽后,如果继续执行请求命令,Redis 会如何处理呢?
内存回收使用Redis 服务时,很多情况下某些键值对只会在特定的时间内有效,为了防止这种类型的数据一直占有内存,我们可以给键值对设置有效期。Redis 中可以通过 4 个独立的命令来给一个键设置过期时间:
expire key ttl:将 key 值的过期时间设置为 ttl 秒。
pexpire key ttl:将 key 值的过期时间设置为 ttl 毫秒。
expireat key timestamp:将 key 值的过期时间设置为指定的 timestamp 秒数。
pexpireat key timestamp:将 key 值的过期时间设置为指定的 timestamp 毫秒数。
PS:不管使用哪一个命令,最终 Redis 底层都是使用 pexpireat 命令来实现的。另外,set 等命令也可以设置 key 的同时加上过期时间,这样可以保证设值和设过期时间的原子性。
设置了有效期后,可以通过 ttl 和 pttl 两个命令来查询剩余过期时间(如果未设置过期时间则下面两个命令返回 -1,如果设置了一个非法的过期时间,则都返回 -2):
ttl key 返回 key 剩余过期秒数。
pttl key 返回 key 剩余过期的毫秒数。
过期策略如果将一个过期的键删除,我们一般都会有三种策略:
定时删除:为每个键设置一个定时器,一旦过期时间到了,则将键删除。这种策略对内存很友好,但是对 CPU 不友好,因为每个定时器都会占用一定的 CPU 资源。
惰性删除:不管键有没有过期都不主动删除,等到每次去获取键时再判断是否过期,如果过期就删除该键,否则返回键对应的值。这种策略对内存不够友好,可能会浪费很多内存。
定期扫描:系统每隔一段时间就定期扫描一次,发现过期的键就进行删除。这种策略相对来说是上面两种策略的折中方案,需要注意的是这个定期的频率要结合实际情况掌控好,使用这种方案有一个缺陷就是可能会出现已经过期的键也被返回。
在 Redis 当中,其选择的是策略 2 和策略 3 的综合使用。不过 Redis 的定期扫描只会扫描设置了过期时间的键,因为设置了过期时间的键 Redis 会单独存储,所以不会出现扫描所有键的情况:
typedef struct redisDb { dict *dict; //所有的键值对 dict *expires; //设置了过期时间的键值对 dict *blocking_keys; //被阻塞的key,如客户端执行BLPOP等阻塞指令时 dict *watched_keys; //WATCHED keys int id; //Database ID //... 省略了其他属性 } redisDb; 8 种淘汰策略假如 Redis 当中所有的键都没有过期,而且此时内存满了,那么客户端继续执行 set 等命令时 Redis 会怎么处理呢?Redis 当中提供了不同的淘汰策略来处理这种场景。
首先 Redis 提供了一个参数 maxmemory 来配置 Redis 最大使用内存:
maxmemory <bytes>或者也可以通过命令 config set maxmemory 1GB 来动态修改。
如果没有设置该参数,那么在 32 位的操作系统中 Redis 最多使用 3GB 内存,而在 64 位的操作系统中则不作限制。
Redis 中提供了 8 种淘汰策略,可以通过参数 maxmemory-policy 进行配置:
淘汰策略 说明volatile-lru 根据 LRU 算法删除设置了过期时间的键,直到腾出可用空间。如果没有可删除的键对象,且内存还是不够用时,则报错
allkeys-lru 根据 LRU 算法删除所有的键,直到腾出可用空间。如果没有可删除的键对象,且内存还是不够用时,则报错
volatile-lfu 根据 LFU 算法删除设置了过期时间的键,直到腾出可用空间。如果没有可删除的键对象,且内存还是不够用时,则报错
allkeys-lfu 根据 LFU 算法删除所有的键,直到腾出可用空间。如果没有可删除的键对象,且内存还是不够用时,则报错
volatile-random 随机删除设置了过期时间的键,直到腾出可用空间。如果没有可删除的键对象,且内存还是不够用时,则报错
allkeys-random 随机删除所有键,直到腾出可用空间。如果没有可删除的键对象,且内存还是不够用时,则报错
volatile-ttl 根据键值对象的 ttl 属性, 删除最近将要过期数据。 如果没有,则直接报错
noeviction 默认策略,不作任何处理,直接报错
PS:淘汰策略也可以直接使用命令 config set maxmemory-policy <策略> 来进行动态配置。
LRU 算法LRU 全称为:Least Recently Used。即:最近最长时间未被使用。这个主要针对的是使用时间。
Redis 改进后的 LRU 算法在 Redis 当中,并没有采用传统的 LRU 算法,因为传统的 LRU 算法存在 2 个问题:
需要额外的空间进行存储。
可能存在某些 key 值使用很频繁,但是最近没被使用,从而被 LRU 算法删除。
为了避免以上 2 个问题,Redis 当中对传统的 LRU 算法进行了改造,通过抽样的方式进行删除。