Redis内存碎片怎么解决?

在 Redis 中,内存碎片可以从两种角度来理解:

  1. 外部碎片

这是指由于内存分配和释放模式造成的可用内存空间的不连续性。在 Redis 中,如果大量的小对象被分配和释放,但它们的分配和释放顺序不一致,就可能导致许多小的、不可用的内存区域散落在更大的、可使用的内存区域之间。这使得 Redis 在尝试分配大块内存时遇到困难,因为没有足够的连续内存空间。

  1. 内部碎片

这种碎片是指分配给对象的内存块大于对象实际需要的大小。在 Redis 中,这可能是由于内存分配器的固定大小分配策略或对齐要求导致的。例如,如果 Redis 使用了一个最小分配单元为 16 字节的内存分配器,那么即使一个对象只需要 1 字节,它也会占用 16 字节的空间,从而产生内部碎片。

针对这两种情况我们分开来说,最后给出解决建议。

一 外部碎片

举个例子大家就明白了。

假设我们有一个初始状态为空的、大小固定的内存区域,内存管理器试图在这个区域内分配和释放多个内存块。下面是内存分配和释放的一个简化示例:

  1. 初始状态

假设内存区域的大小为 100 字节,初始时整个区域是空闲的。

  1. 第一次分配

分配一个 30 字节的内存块,假设从地址 0 开始。

  1. 第二次分配

再次分配一个 40 字节的内存块,假设从地址 50 开始。

此时,内存布局如下:

1
2
3
4
+----------------+----------------+----------------+
| 30 bytes (used)| 20 bytes (free)| 40 bytes (used)|
+----------------+----------------+----------------+
0 30 50 90
  1. 第一次释放

释放第一个 30 字节的内存块。

现在内存布局如下:

1
2
3
4
+----------------+----------------+----------------+
| 30 bytes (free)| 20 bytes (free)| 40 bytes (used)|
+----------------+----------------+----------------+
0 30 50 90
  1. 第二次释放

释放第二个 40 字节的内存块。

最终内存布局如下:

1
2
3
4
+----------------+----------------+----------------+
| 30 bytes (free)| 20 bytes (free)| 40 bytes (free)|
+----------------+----------------+----------------+
0 30 50 90

现在,内存中有三个空闲的区域,但它们不连续。如果一个新的请求需要分配一个 50 字节的内存块,即使空闲内存的总量是 90 字节,但由于这些空闲块不连续,内存管理器无法满足这个请求,这就产生了外部碎片。

解决外部碎片的方法

  • 内存紧缩(Memory Compaction):通过移动已分配的内存块,使空闲内存块集中到一起,从而消除外部碎片。
  • 使用内存池:预先分配大块内存,然后从中切割出更小的块供程序使用,以减少外部碎片。
  • 智能的分配算法:如最佳适配算法(Best Fit)、首次适配算法(First Fit)或循环首次适配算法(Circular First Fit)等,可以更高效地管理内存,减少外部碎片。

在Redis这样的数据库中,使用高效的内存分配器(如jemalloc)和内存管理策略可以有效地减少外部碎片,提高内存使用效率。

二 内部碎片

内部碎片就是分配给某个对象的内存大小超过了该对象实际所需的大小,多余的部分就构成了内部碎片。内部碎片的产生通常是因为内存分配器需要按照一定的规则分配内存,这些规则可能基于对齐要求、分配单元大小或内存分配器的内部机制。

举个例子:

假设一个系统中,内存分配器的最小分配单位是 16 字节,这意味着无论你请求多少字节,内存分配器都会向上取最接近的 16 字节倍数进行分配。例如:

  1. 分配请求

假设一个程序需要分配一个变量,该变量只需要存储一个整数。在 C 语言中,一个整数(int)通常占用 4 字节的内存。

  1. 内存分配

程序向内存分配器请求 4 字节的内存。但是,由于内存分配器的最小分配单位是 16 字节,所以它会分配 16 字节的内存给这个变量。

  1. 内部碎片

变量实际使用了 4 字节,而分配的内存大小为 16 字节,这意味着还有 12 字节未被使用,这 12 字节就构成了内部碎片。

三 解决之道

3.1 发现问题

首先第一点,就是我们如何发现 Redis 中存在内存碎片呢?

  1. 使用INFO命令

运行 INFO memory 命令可以查看有关 Redis 内存使用情况的详细信息。特别注意 used_memoryused_memory_rss 这两个指标。

  • used_memory 显示 Redis 服务器为了保存数据使用的内存总量,而 used_memory_rss(resident set size)操作系统实际分配给 Redis 的内存总量,这个里边就包含了碎片。
  • 如果 used_memory 远小于 used_memory_rss,这可能表明存在内部碎片或 Redis 使用的内存分配器(如 jemalloc)正在预留额外的内存,但这并不直接意味着外部碎片。

  1. 检查 maxmemory 配置和使用率

查看 Redis 配置文件中的 maxmemory 设置,以及 INFO memory 输出中的 mem_fragmentation_ratio 指标,这个比率表示 used_memory_rssused_memory 的比例。
如果 mem_fragmentation_ratio 大于1 小于 1.5,一般认为是合理的,但是如果超过 1.5,就意味着内存分配效率不高,存在内部碎片或内存分配器的额外开销,需要我们去解决了。

  1. 分析数据结构和键的大小
  • 使用 DEBUG OBJECT key 命令检查特定键的内存使用情况,这可以帮助你了解每个键的实际内存使用,包括内部存储结构的开销。
  • 定期检查和优化数据结构的使用,例如,避免使用过大的字符串或列表,考虑使用更节省空间的数据结构,如 ziplist 或 intset。
  1. 监控内存分配器统计信息
  • 如果你的 Redis 实例使用 jemalloc 作为内存分配器,你可以通过 jemalloc 的统计接口(例如,mallctl 命令)来获取关于内存分配的详细信息,包括可能的内部碎片。
  1. 使用外部监控工具
  • 利用如 tophtopps 等系统监控工具,观察 Redis 进程的内存使用情况,与 INFO memory 的输出对比,分析内存使用效率。

通过以上步骤,我们可以分析出来是否存在内存碎片。

3.2 解决问题

在旧的 Redis 版本中(Redis4 之前),内存碎片问题只能通过重启 Redis 来解决。

从 Redis4.0 往后,只要使用的内存分配器是 jemalloc,那么就可以开启内存碎片自动清理的功能(一般默认使用的都是 jemalloc,如果不是,就需要重新编译 Redis 并安装了)。

内存碎片自动清理的原理很简单,就是内存不连续的时候,自动将分散的数据挪动到一起,这样剩余的空间就会形成连续空间。

具体怎么做呢?

首先我们需要开启内存碎片自动整理功能。

开启方式就是将这个 activedefrag 设置为 yes,默认 no 表示没有开启内存碎片自动整理功能。

那么开启之后,什么时候会去整理内存碎片呢?这又涉及到两个参数。

  • active-defrag-ignore-bytes:这个表示内存碎片占用的最小空间,达到这个数据之后,就开启碎片整理了,默认是 100MB。
  • active-defrag-threshold-lower:这个表示内存碎片占用 Redis 内存总空间的比例,达到这个比例的时候,就开启碎片整理了,默认是 10%。

不过内存碎片整理要对数据进行移动,频繁操作肯定会影响 Redis 的性能,所以还有两个参数来约束碎片整理过程:

  • active-defrag-cycle-min:内存自动整理占用资源的最小百分比,设定了占用 CPU 时间的下限。当内存碎片整理过程占用的CPU时间比例低于 active-defrag-cycle-min 设置的值时,可能会导致整理过程进行得不够充分,影响内存碎片的回收效果。
  • active-defrag-cycle-max:内存自动整理占用资源的最大百分比,设定了占用 CPU 时间的上限。当占用的CPU时间比例超过 active-defrag-cycle-max 设置的值时,为了避免对Redis服务的正常请求造成过大的影响,Redis会停止内存碎片整理过程。

好啦,Redis 内存碎片问题,现在小伙伴们搞懂了吧~