Redis 内存是如何划分的?

查询内存命令

info memory

查询Redis自身使用内存的统计数据。通过这个命令,可以了解 Redis 实例的内存分配情况、内存碎片、键空间使用情况等

  1. 内存分配情况:

    • used_memory:Redis 实例当前使用的内存总量(以字节为单位)。

    • used_memory_human:以人类可读格式显示的内存使用量。

    • used_memory_rss:Redis 占用的物理内存总量(包括操作系统使用的内存)。

    • used_memory_peak:Redis 实例历史上使用的最大内存总量。

    • used_memory_peak_human:以人类可读格式显示的历史内存使用的最大值。

  2. 内存碎片情况:

    • mem_fragmentation_ratio:内存碎片比率,表示内存碎片与已分配内存的比率。

    • mem_allocator:Redis 使用的内存分配器。

  3. 键空间相关信息:

    • keyspace_hits:成功查找键的次数。

    • keyspace_misses:未能查找键的次数。

    • expired_keys:过期的键的数量。

    • evicted_keys:因为内存满而被驱逐的键的数量。

  4. 持久化相关信息:

    • rdb_changes_since_last_save:上次 RDB 快照以来的更改数量。

    • rdb_last_save_time:上次成功创建 RDB 快照的时间戳。

    • rdb_last_bgsave_time_sec:最近一次创建 RDB 快照所花费的秒数。

  5. 客户端和对象相关信息:

    • connected_clients:当前连接到服务器的客户端数量。

    • client_longest_output_list:客户端输出缓冲区中最长的等待时间(以字节为单位)。

需要重点关注的指标有: 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内存占用最大的一块, 存储着用户所有的数据。 Redis 所有的数据都采用key-value数据类型, 每次创建键值对时, 至少创建两个类型对象: key对象和value对象。 对象内存消耗可以简单理解为sizeof(keys)+sizeof(values) 。 键对象都是字符串, 在使用Redis时很容易忽略键对内存消耗的影响, 应当避免使用过长的键。

缓冲内存

缓冲内存主要包括: 客户端缓冲、 复制积压缓冲区、 AOF缓冲区。客户端缓冲指的是所有接入到Redis服务器TCP连接的输入输出缓冲。输入缓冲无法控制, 最大空间为1G, 如果超过将断开连接。 输出缓冲通过参数client-output-buffer-limit控制 。

客户端缓冲
普通客户端

除了复制和订阅的客户端之外的所有连接, Redis的默认配置是: client-output-buffer-limit normal000

Redis并没有对普通客户端的输出缓冲区做限制, 一般普通客户端的内存消耗可以忽略不计, 但是当有大量慢连接客户端接入时这部分内存消耗就不能忽略了, 可以设置maxclients做限制

从客户端

主节点会为每个从节点单独建立一条连接用于命令复制,默认配置是: client-output-buffer-limit slave256mb64mb60。 当主从节点之间网络延迟较高或主节点挂载大量从节点时这部分内存消耗将占用很大一部分, 建议主节点挂载的从节点不要多于2个

订阅客户端

当使用发布订阅功能时, 连接客户端使用单独的输出缓冲区, 默认配置为: client-output-buffer-limit pubsub32mb8mb60, 当订阅服务的消息生产快于消费速度时, 输出缓冲区会产生积压造成输出缓冲区空间溢出。

复制积压缓冲区

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

AOF缓冲区

这部分空间用于在Redis重写期间保存最近的写入命令,具体细节见5.2节。 AOF缓冲区空间消耗用户无法控制, 消耗的内存取决于AOF重写时间和写入命令量,这部分空间占用通常很小。

碎片

Redis默认的内存分配器采用jemalloc, 可选的分配器还有: glibc、tcmalloc。 内存分配器为了更好地管理和重复利用内存, 分配内存策略一般采用固定范围的内存块进行分配。 例如jemalloc在64位系统中将内存空间划分为: 小、 大、 巨大三个范围。 每个范围内又划分为多个小的内存块单位, 如下所示:

  • 小: [8byte], [16byte, 32byte, 48byte, ..., 128byte], [192byte,428256byte, ..., 512byte], [768byte, 1024byte, ..., 3840byte]

  • 大: [4KB, 8KB, 12KB, ..., 4072KB]

  • 巨大: [4MB, 8MB, 12MB, ...]

比如当保存5KB对象时jemalloc可能会采用8KB的块存储, 而剩下的3KB空间变为了内存碎片不能再分配给其他对象存储。 内存碎片问题虽然是所有内存服务的通病, 但是jemalloc针对碎片化问题专门做了优化, 一般不会存在过度碎片化的问题, 正常的碎片率( mem_fragmentation_ratio) 在1.03左右。 但是当存储的数据长短差异较大时, 以下场景容易出现高内存碎片问题:

  • 频繁做更新操作, 例如频繁对已存在的键执行append、 setrange等更新操作。

  • 大量过期键删除, 键对象过期删除后, 释放的空间无法得到充分利用, 导致碎片率上升。

出现高内存碎片问题时常见的解决方式如下:

  • 数据对齐: 在条件允许的情况下尽量做数据对齐, 比如数据尽量采用数字类型或者固定长度字符串等, 但是这要视具体的业务而定, 有些场景无法做到。

  • 安全重启: 重启节点可以做到内存碎片重新整理, 因此可以利用高可用架构, 如Sentinel或Cluster, 将碎片率过高的主节点转换为从节点, 进行安全重启。

子进程内存消耗

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

  • Redis产生的子进程并不需要消耗1倍的父进程内存, 实际消耗根据期间写入命令量决定, 但是依然要预留出一些内存防止溢出。

  • 需要设置sysctl vm.overcommit_memory=1允许内核可以分配所有的物理内存, 防止Redis进程执行fork时因系统剩余内存不足而失败。

  • 排查当前系统是否支持并开启THP, 如果开启建议关闭, 防止copy-onwrite期间内存过度消耗。

上一篇:嵌入式学习day38 HTML


下一篇:【Godot4.2】2D导航02 - AstarGrid2D及其使用方法