什么是内存碎片?
你可以将内存碎片简单地理解为那些不可用的空闲内存。
举个例子:操作系统为你分配了 32 字节的连续内存空间,而你存储数据实际只需要使用 24 字节内存空间,那这多余出来的 8 字节内存空间如果后续没办法再被分配存储其他数据的话,就可以被称为内存碎片。
Redis 内存碎片虽然不会影响 Redis 性能,但是会增加内存消耗
为什么会有 Redis 内存碎片?
Redis 内存碎片产生比较常见的 2 个原因:
1、Redis 存储数据的时候向操作系统申请的内存空间可能会大于数据实际需要的存储空间。
以下是这段 Redis 官方的原话:
To store user keys, Redis allocates at most as much memory as the maxmemory
setting enables (however there are small extra allocations possible).下面是这段话的一个解释:
这句话的意思是,Redis 会根据配置项 maxmemory
来限制用于存储用户键(即数据)的内存量。当你设置了 maxmemory
参数后,Redis 将不会使用超过这个设定值的内存来存储数据。
然而,需要注意的是,除了用于存储键值对的内存之外,Redis 还会进行一些额外的小量内存分配。这些额外的内存分配主要用于:
- Redis 内部的数据结构和元数据,如哈希表、跳跃列表等,它们用于高效地管理和索引键。
- 系统开销,包括但不限于客户端连接信息、命令解析、AOF重写或RDB快照时的缓冲区等。
- 在执行某些操作时,比如命令的执行过程中,可能会有临时对象被创建,这些也会占用额外的内存。
因此,虽然 maxmemory
设置限制了Redis可以用来存储用户数据的最大内存,但实际使用的总内存量可能会略高于这个值,因为包含了上述提到的额外内存开销。当Redis达到 maxmemory
限制时,它将根据配置的淘汰策略(eviction policy)开始移除一些数据以释放空间。
Redis 使用 zmalloc
方法(Redis 自己实现的内存分配方法)进行内存分配的时候,除了要分配 size
大小的内存之外,还会多分配 PREFIX_SIZE
大小的内存。
zmalloc
方法源码如下(源码地址:https://github.com/antirez/redis-tools/blob/master/zmalloc.c):
void *zmalloc(size_t size) {
// 分配指定大小的内存
void *ptr = malloc(size+PREFIX_SIZE);
if (!ptr) zmalloc_oom_handler(size);
#ifdef HAVE_MALLOC_SIZE
update_zmalloc_stat_alloc(zmalloc_size(ptr));
return ptr;
#else
*((size_t*)ptr) = size;
update_zmalloc_stat_alloc(size+PREFIX_SIZE);
return (char*)ptr+PREFIX_SIZE;
#endif
}
另外,Redis 可以使用多种内存分配器来分配内存( libc、jemalloc、tcmalloc),默认使用 jemalloc,而 jemalloc 按照一系列固定的大小(8 字节、16 字节、32 字节......)来分配内存的。jemalloc 划分的内存单元如下图所示:
当程序申请的内存最接近某个固定值时,jemalloc 会给它分配相应大小的空间,就比如说程序需要申请 17 字节的内存,jemalloc 会直接给它分配 32 字节的内存,这样会导致有 15 字节内存的浪费。不过,jemalloc 专门针对内存碎片问题做了优化,一般不会存在过度碎片化的问题。
2、频繁修改 Redis 中的数据也会产生内存碎片。
当 Redis 中的某个数据删除时,Redis 通常不会轻易释放内存给操作系统。
这个在 Redis 官方文档中也有对应的原话:
文档地址:Memory optimization | Docs
如何查看 Redis 内存碎片的信息?
使用 info memory
命令即可查看 Redis 内存相关的信息。下图中每个参数具体的含义,Redis 官方文档有详细的介绍:INFO | Docs
Redis 内存碎片率的计算公式:mem_fragmentation_ratio
(内存碎片率)= used_memory_rss
(操作系统实际分配给 Redis 的物理内存空间大小)/ used_memory
(Redis 内存分配器为了存储数据实际申请使用的内存空间大小)
也就是说,mem_fragmentation_ratio
(内存碎片率)的值越大代表内存碎片率越严重。
一定不要误认为used_memory_rss
减去 used_memory
值就是内存碎片的大小!!!这不仅包括内存碎片,还包括其他进程开销,以及共享库、堆栈等的开销。
很多小伙伴可能要问了:"多大的内存碎片率才是需要清理呢?"。
通常情况下,我们认为 mem_fragmentation_ratio > 1.5
的话才需要清理内存碎片。 mem_fragmentation_ratio > 1.5
意味着你使用 Redis 存储实际大小 2G 的数据需要使用大于 3G 的内存。
如果想要快速查看内存碎片率的话,你还可以通过下面这个命令:
redis-cli -p 6379 info | grep mem_fragmentation_ratio
如何清理 Redis 内存碎片?
Redis4.0-RC3 版本以后自带了内存整理,可以避免内存碎片率过大的问题。
直接通过 config set
命令将 activedefrag
配置项设置为 yes
即可
config set activedefrag yes
具体什么时候清理需要通过下面两个参数控制:
内存碎片占用空间达到 500mb 的时候开始清理
config set active-defrag-ignore-bytes 500mb
内存碎片率大于 1.5 的时候开始清理
config set active-defrag-threshold-lower 50
通过 Redis 自动内存碎片清理机制可能会对 Redis 的性能产生影响,我们可以通过下面两个参数来减少对 Redis 性能的影响:
内存碎片清理所占用 CPU 时间的比例不低于 20%
config set active-defrag-cycle-min 20
内存碎片清理所占用 CPU 时间的比例不高于 50%
config set active-defrag-cycle-max 50
另外,重启节点可以做到内存碎片重新整理。如果你采用的是高可用架构的 Redis 集群的话,你可以将碎片率过高的主节点转换为从节点,以便进行安全重启。