Redis 内存回收

Redis默认无限使用服务器内存,为防止极端情况下导致系统内存耗尽,建议所有的Redis进程都要配置maxmemory。

Redis的内存回收机制主要体现在以下两个方面:

  1. 删除键(主动或者删除到达过期时间的键)
  2. 内存使用达到maxmemory上限时触发内存溢出控制策略。

一、删除过期键对象

Redis所有的键都可以设置过期属性,内部保存在过期字典中。由于进程内保存大量的键,维护每个键精准的过期删除机制会导致消耗大量的CPU,对于单线程的Redis来说成本过高,因此Redis采用惰性删除和定时任务删除机制实现过期键的内存回收。

1.惰性删除

惰性删除用于当客户端读取带有超时属性的键时,如果已经超过键设置的过期时间,会执行删除操作并返回空,这种策略是出于节省CPU成本考虑,不需要单独维护TTL链表来处理过期键的删除。但是单独用这种方式存在内存泄露的问题,当过期键一直没有访问将无法得到及时删除,从而导致内存不能及时释放。正因为如此,Redis还提供另一种定时任务删除机制作为惰性删除的补充。

2.定时任务删除

Redis内部维护一个定时任务,默认每秒运行10次(通过配置hz控制)。定时任务中删除过期键逻辑采用了自适应算法,根据键的过期比例、使用快慢两种速率模式回收键,大概过程如下

  • 从过期字典中随机 20 个 key;
  • 删除这 20 个 key 中已经过期的 key;
  • 如果过期的 key 比率超过 1/4,那就重复步骤 1;

同时,为了保证过期扫描不会出现循环过度,导致线程卡死现象,算法还增加了扫描时间的上限,默认不会超过 25ms

设想一个大型的 Redis 实例中所有的 key 在同一时间过期了,会出现怎样的结果?

毫无疑问,Redis 会持续扫描过期字典 (循环多次),直到过期字典中过期的 key 变得稀疏,才会停止 (循环次数明显下降)。这就会导致线上读写请求出现明显的卡顿现象。导致这种卡顿的另外一种原因是内存管理器需要频繁回收内存页,这也会产生一定的 CPU 消耗。

当客户端请求到来时,服务器如果正好进入过期扫描状态,客户端的请求将会等待至少 25ms 后才会进行处理,如果客户端将超时时间设置的比较短,比如 10ms,那么就会出现大量的链接因为超时而关闭,业务端就会出现很多异常。而且这时你还无法从 Redis 的 slowlog 中看到慢查询记录,因为慢查询指的是逻辑处理过程慢,不包含等待时间。

所以业务开发人员一定要注意过期时间,如果有大批量的 key 过期,要给过期时间设置一个随机范围,而不宜全部在同一时间过期,分散过期处理的压力

、内存溢出控制策略

当Redis所用内存达到maxmemory上限时会触发相应的溢出控制策略。具体策略受  maxmemory-policy 参数控制(不控制的话,当内存达到最大物理内存就会出现内存交换),Redis支持8种策略(后两种4.0以上提供):

  1. noeviction: 不会继续服务写请求 (DEL 请求可以继续服务),拒绝所有写入操作并返回客户端错误信息(error)OOM command not allowed when used memory,此时Redis只响应读操作。这样可以保证不会丢失数据,但是会让线上的业务不能持续进行。这是默认的淘汰策略
  1. volatile-lru :根据LRU算法(最近最少使用)删除设置了超时属性(expire)的键,直到腾出足够空间为止。如果没有可删除的键对象,回退到noeviction策略。
  2. volatile-ttl 跟上面一样,除了淘汰的策略不是 LRU,而是 key 的剩余寿命 ttl 的值,ttl 越小越优先被淘汰(将要过期的)。
  3. volatile-random: 跟上面一样,不过淘汰的 key 是过期 key 集合中随机的 key(直到腾出足够空间为止)。
  4. allkeys-lru  :区别于 volatile-lru,这个策略要淘汰的 key 对象是全体的 key 集合,而不只是过期的 key 集合。这意味着没有设置过期时间的 key 也会被淘汰。
  5. allkeys-random: 跟上面一样,不过淘汰的策略是随机的 key。
  1. allkey-lfu: 根据LFU算法随机淘汰全部key
  2. volatile-lfu: 根据LFU算法随机淘汰过期的key

值得一提的是后两者淘汰策略在redis4.0及以上版本才提供。Redis4.0提供了LFU算法还可以通过object命令获取某个key 的访问频次

        object freq key

基于LFU机制,用户可以使用 scan + object freq 来发现热点key

三、redis4.0之lazyfree

unlink

删除指令 del 会直接释放对象的内存,大部分情况下,这个指令非常快,没有明显延迟。不过如果删除的 key是一个非常大的对象,比如一个包含了千万元素的 hash,那么删除操作就会导致单线程卡顿。

Redis 为了解决这个卡顿问题,在 4.0 版本引入了 unlink 指令,它能对删除操作进行懒处理,丢给后台线程来异步回收内存。

        unlink key [key ...]

flushdb/flushall

Redis 提供了 flushdb 和 flushall 指令,用来清空数据库,这也是极其缓慢的操作。flushdb/flushall在redis-4.0中新引入了选项,可以指定是否使用Lazyfree的方式来清空整个内存。

        > flushall async

        OK

        Rename

执行 rename oldkey newkey时,如果newkey已经存在,redis会先删除,这也会引发上面提到的删除大key问题,如果想让redis在这种场景下也使用lazyfree的方式来删除,可以在控制台上打开如下配置:

            lazyfree-lazy-server-del yes/no

       其他场景

       key 的过期、LRU 淘汰及从库全量同步时接受完 rdb 文件后会立即进行的 flush 操作,Redis4.0 为这些删除点也带来了异步   删除机制,打开这些点需要额外的配置选项。

  1. slave-lazy-flush  yes/no 从库接受完 rdb 文件后的 flush 操作
  2. lazyfree-lazy-eviction yes/no 内存达到 maxmemory 时进行淘汰
  3. lazyfree-lazy-expire key yes/no 过期删除

  AOF RDB 和复制功能对过期键的处理

  1. 生成RDB文件:
  2. 在执行save命令或者bgsave命令创建一个新的RDB文件时,程序会对数据中的键进行检查,已过期的键不会保存
  3. 载入RDB文件:
    • 主服务器运行:载入的同时忽略过期键
    1. 从服务器运行:全部key载入(过期也载入)。但是,因为主从在数据同步的时候,从服务器会先被清空,所以一般来说,过期键对载入rdb文件的从服务器不会造成影响
  4. aof文件写入:当过期键呗惰性删除或者定期删除之后,程序会像aof文件追加一个del命令,来显示的记录该键已被删除
  5. aof重写:在执行aof重写的过程中,程序会对数据库中的键进行检查,已过期的不会被保存到重写的aof 文件中。具体过程见上篇
  6. 复制:
    1. 主服务器在删除一个过期键之后,会显示的向所有从服务器发送一个del命令
    2. 从服务器执行客户端发送的读命令时,即使碰到过期键也不会将过期键删除,而是继续像处理未过期键一样处理它
    3. 从服务器只有在接到主服务器发来的del命令之后,才会删除该键
原创文章 15 获赞 22 访问量 6936

猜你喜欢

转载自blog.csdn.net/qq_31387317/article/details/97382188