TrumanWong

Redis内存碎片分析

TrumanWong
4/6/2022

背景

在实际使用Redis中可能会发现一个问题:删除数据后,内存占用率依然很高。这是因为删除数据后Redis释放的内存空间会由内存分配器管理,并不会立即返回给操作系统,所以操作系统仍然记录着给Redis分配了大量内存。这往往伴随着一个潜在的风险:Redis释放的内存空间可能并不是连续的,这些不连续的内存空间很可能处于一种闲置的状态。这就会导致Redis无法使用这些释放的空闲空间来保存数据,这种情况不仅会减少Redis可保存的数据量,还会降低Redis对机器内存的使用率

Redis内存碎片的形成

Redis内存碎片的形成可能由两方面引起:一个是操作系统的内存分配机制,另一个是Redis的负载特性。

内存分配机制

内存分配器的分配策略决定了操作系统无法做到按需分配。这是因为内存分配器一般是按固定大小来分配内存的,而不是完全按照应用程序申请的内存空间大小来给程序分配内存的。Redis可以使用libcjemalloctcmalloc多种内存分配器来分配内存,默认使用jemallocjemalloc的分配策略之一是按照固定的大小划分内存空间,比如8字节、16字节、32字节、48字节等。当程序申请的内存大小最接近某个固定值时,jemalloc会给它分配相应大小的空间。如果Redis每次向分配器申请的内存大小不一样,那么这种分配方式就会形成内存碎片。

Redis的负载特征

在业务中,我们会对键值对进行修改和删除等操作,产生对内存空间的申请和释放操作。如果修改后的键值对变大或变小了,就需要占用额外的内存空间或者释放不用的内存空间,或者删除的键值对不再需要内存空间了,此时会把内存空间释放出来,形成空闲的内存空间。

内存使用情况

可以用info memory命令来查看Redis的内存状态,执行结果如下所示:

127.0.0.1:6379> info memory
# Memory
// Redis为了保存数据实际申请使用的内存空间,单位为字节
used_memory:866832
// 用户数据所占用的内存空间,也就是缓存数据的大小
used_memory_human:846.52K
// Redis内存使用的峰值
used_memory_rss:4120576
// 用户缓存数据的峰值大小
used_memory_rss_human:3.93M
used_memory_peak:1218808
used_memory_peak_human:1.16M
used_memory_peak_perc:71.12%
used_memory_overhead:824640
used_memory_startup:803352
used_memory_dataset:42192
used_memory_dataset_perc:66.47%
allocator_allocated:897960
allocator_active:1159168
allocator_resident:3600384
total_system_memory:4122103808
total_system_memory_human:3.84G
// 执行Lua脚本所占用的内存空间
used_memory_lua:38912
used_memory_lua_human:38.00K
used_memory_scripts:200
used_memory_scripts_human:200B
number_of_cached_scripts:1
maxmemory:0
maxmemory_human:0B
maxmemory_policy:noeviction
allocator_frag_ratio:1.29
allocator_frag_bytes:261208
allocator_rss_ratio:3.11
allocator_rss_bytes:2441216
rss_overhead_ratio:1.14
rss_overhead_bytes:520192
// 内存碎片率
mem_fragmentation_ratio:4.99
mem_fragmentation_bytes:3294760
mem_not_counted_for_evict:480
mem_replication_backlog:0
mem_clients_slaves:0
mem_clients_normal:20504
mem_aof_buffer:512
mem_allocator:jemalloc-5.1.0
active_defrag_running:0
lazyfree_pending_objects:0

内存碎片率的计算公式如下:

mem_fragmentation_ratio = used_memory_rss / used_memory

内存碎片率mem_fragmentation_ratio 大于且小于1.5是比较合理的,造成内存碎片的原因是无法避免的。mem_fragmentation_ratio大于1.5时,表明内存碎片率超过了50%,这时就需要采取一些措施来降低内存碎片率。

清理内存碎片

Redis 4版本之前,我们只能重启Redis服务,但是这有可能出现下面两个问题:

  • 如果Redis中的数据没有持久化,那么重启之后数据会丢失。
  • 即使Redis数据持久化了,也需要通过AOFRDB进行恢复,恢复时间比较长,那么恢复阶段将无法提供服务。

Redis 4版本之后,Redis自身提供了一种内存碎片自动清理的方法,在规定的范围内进行内存空间的合并。

相关参数如下:

  • activedefrag no:是否启用碎片整理,默认是no
  • active-defrag-ignore-bytes 100mb:内存碎片的字节数达到100MB时开始清理碎片。
  • active-defrag-threshold-lower 10:内存碎片空间占操作系统分配给Redis的总内存空间的比例达到10%时开始清理碎片。
  • active-defrag-cycle-min 1:自动清理过程所用CPU时间的比例不低于1%,以保证能正常清理。
  • active-defrag-cycle-max 25:自动清理过程所用CPU时间的比例不高于25%,一旦超过就停止清理,从而避免在清理时大量的内存复制阻塞了Redis,导致Redis服务相应缓慢。