Redis内存管理


Redis所有的数据都存在内存中,因此如何高效利用Redis内存变得非常重要。

内存使用统计

Redis自身使用内存的统计数据,可通过执行info memory命令获取内存相关指标。

属性名 含义
used_memory Redis分配器分配的内存总量,也就是内部存储的所有数据内存总量
used_memory_human 已可读的格式返回used_memory
used_memory_rss 已操作系统的角度显示Redis进程占用的物理内存总量
used_memory_peak 内存使用的最大值,表示used_memory的峰值
used_memory_peak_human 已可读的格式返回used_memory_peak
used_memory_lua Lua引擎所消耗的内存大小
mem_fragmentation_ratio used_memory_rss/used_memory比值,表示内存碎片
mem_allocator Redis所使用的内存分配器。默认为jemalloc

需要重点关注的指标有:used_memory_rssused_memory以及它们的比值mem_fragmentation_ratio

mem_fragmentation_ratio > 1时,说明used_memory_rss - used_memory多出的部分内存并没有用于数据存储,而是被内存碎片所消耗,如果两者相差很大,说明碎片率严重。

mem_fragmentation_ratio < 1时,这种情况一般出现在操作系统把Redis内存交换(Swap)到硬盘导致,出现这种情况时要格外关注,由于硬盘速度远远慢于内存,Redis性能会变得很差,甚至僵死。

内存消耗划分

Redis进程内消耗主要包括:自身内存+对象内存+缓冲内存+内存碎片,其中Redis空进程自身内存消耗非常少,通常used_memory_rss在3MB左右,used_memory在800KB左右,一个空的Redis进程消耗内存可以忽略不计。

redis_memory.png

对象内存

对象内存是Redis内存占用最大的一块,存储着用户所有的数据。Redis所有的数据都采用key-value数据类型,每次创建键值对时,至少创建两个类型对象:key对象和value对象。在使用时,应当避免使用过长的键、合理预估并监控value对象占用情况。

缓冲内存

缓冲内存主要包括:客户端缓冲、复制积压缓冲区、AOF缓冲区。

客户端缓冲

客户端缓冲指的是所有接入到Redis服务器TCP连接的输入输出缓冲。

输入缓冲无法控制,最大空间为1G,如果超过将断开连接。

输出缓冲通过参数client-output-buffer-limit控制,默认配置是:client-output-buffer-limit normal 0 0 0,并没有对普通客户端的输出缓冲区做限制。

从客户端:主节点会为每个从节点单独建立一条连接用于命令复制,默认配置是:client-output-buffer-limit slave 256mb 64mb 60

订阅客户端:当使用发布订阅功能时,连接客户端使用单独的输出缓冲区,默认配置为:client-output-buffer-limit pubsub 32mb 8mb 60

输入输出缓冲区在大流量的场景中容易失控,造成Redis内存的不稳定,需要重点监控

复制积压缓冲区

Redis在2.8版本之后提供了一个可重用的固定大小缓冲区用于实现部分复制功能,根据repl-backlog-size参数控制,默认1MB。对于复制积压缓冲区整个主节点只有一个,所有的从节点共享此缓冲区,因此可以设置较大的缓冲区空间,如100MB,这部分内存投入是有价值的,可以有效避免全量复制。

AOF缓冲区

AOF缓冲区空间消耗用户无法控制,消耗的内存取决于AOF重写时间和写入命令量,这部分空间占用通常很小。

内存碎片

Redis默认的内存分配器采用jemalloc,可选的分配器还有:glibc、tcmalloc。jemalloc针对碎片化问题专门做了优化,一般不会存在过度碎片化的问题,正常的碎片率(mem_fragmentation_ratio)在1.03左右。

以下场景容易出现高内存碎片问题:

  • 频繁做更新操作,例如频繁对已存在的键执行append、setrange等更新操作。
  • 大量过期键删除,键对象过期删除后,释放的空间无法得到充分利用,导致碎片率上升。

子进程内存消耗

子进程内存消耗主要指执行AOF/RDB重写时Redis创建的子进程内存消耗。Linux具有写时复制技术
(copy-on-write),父子进程会共享相同的物理内页,当父进程处理写请求时会对需要修改的页复制出一份副本完成写操作,而子进程依然读取fork时整个父进程的内存快照。

内存管理

Redis主要通过控制内存上限和回收策略实现内存管理。

设置内存上限

Redis使用maxmemory参数限制最大可用内存。限制内存的目的主要有:

  • 用于缓存场景,当超出内存上限maxmemory时使用LRU等删除策略释放空间。
  • 防止所用内存超过服务器物理内存。

maxmemory限制的是Redis实际使用的内存量,也就used_memory统计项对应的内存。由于内存碎片率的存在,实际消耗的内存可能会比maxmemory设置的更大,实际使用时要小心这部分内存溢出。

动态调整内存上限

Redis的内存上限可以通过config set maxmemory进行动态修改,即修改最大可用内存。

127.0.0.1:6379> config set maxmemory 6G

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

内存回收策略

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

  • 删除到达过期时间的键对象。
  • 内存使用达到maxmemory上限时触发内存溢出控制策略。
删除过期键对象

Redis所有的键都可以设置过期属性,内部保存在过期字典中。Redis采用惰性删除和定时任务删除机制实现过期键的内存回收。

惰性删除

惰性删除用于当客户端读取带有超时属性的键时,如果已经超过键设置的过期时间,会执行删除操作并返回空。但是单独用这种方式存在内存泄露的问题,当过期键一直没有访问将无法得到及时删除,从而导致内存不能及时释放。

定时任务删除

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

  1. 定时任务在每个数据库空间随机检查20个键,当发现过期时删除对应的键。

  2. 如果超过检查数25%的键过期,循环执行回收逻辑直到不足25%或运行超时为止,慢模式下超时时间为25毫秒。

  3. 如果之前回收键逻辑超时,则在Redis触发内部事件之前再次以快模式运行回收过期键任务,快模式下超时时间为1毫秒且2秒内只能运行1次。

  4. 快慢两种模式内部删除逻辑相同,只是执行的超时时间不同。

内存溢出控制策略

当Redis所用内存达到maxmemory上限时会触发相应的溢出控制策略。具体策略受maxmemory-policy参数控制,Redis支持6种策略。

noeviction

默认策略,不会删除任何数据,拒绝所有写入操作并返回客户端错误信息:OOM command not allowed when used memory,此时Redis只响应读操作。

volatile-lru

根据LRU算法删除设置了超时属性(expire)的键,直到腾出足够空间为止。如果没有可删除的键对象,回退到noeviction策略。

allkeys-lru

根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。

allkeys-random

随机删除所有键,直到腾出足够空间为止。

volatile-random

随机删除过期键,直到腾出足够空间为止。

volatile-ttl

根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。

内存溢出控制策略可以采用config set maxmemory-policy {policy}动态配置。

可以通过执行info stats命令查看evicted_keys指标找出当前Redis服务器已剔除的键数量。

当Redis一直工作在内存溢(used_memory > maxmemory)的状态下且设置非noeviction策略时,会频繁地触发回收内存的操作,影响Redis服务器的性能。