一文了解 Redis 内存监控和内存消耗( 二 )


从客户端用于主从复制,主节点会为每个从节点单独建立一条连接用于命令复制,默认配置为 client-output-buffer-limit slave 256mb 64mb 60 。当主从节点之间网络延迟较高或主节点挂载大量从节点时这部分内存消耗将占用很大一部分,建议主节点挂载的从节点不要多于 2 个,主从节点不要部署在较差的网络环境下,如异地跨机房环境,防止复制客户端连接缓慢造成溢出 。与主从复制相关的一共有两类缓冲区,一个是从客户端输出缓冲区,另外一个是下面会介绍到的复制积压缓冲区 。

一文了解 Redis 内存监控和内存消耗

文章插图
 
订阅客户端用于发布订阅功能,连接客户端使用单独的输出缓冲区,默认配置为 client-output-buffer-limit pubsub 32mb 8mb 60,当订阅服务的消息生产快于消费速度时,输出缓冲区会产生积压造成内存空间溢出 。
输入输出缓冲区在大流量场景中容易失控,造成 Redis 内存不稳定,需要重点监控 。可以定期执行 client list 命令,监控每个客户端的输入输出缓冲区大小和其他信息 。
一文了解 Redis 内存监控和内存消耗

文章插图
 
127.0.0.1:6379> client listid=3 addr=127.0.0.1:58161 fd=8 name= age=1408 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 obl=0 oll=0 omem=0 events=r cmd=clientclient list 命令执行速度慢,客户端较多时频繁执行存在阻塞redis的可能,所以一般可以先使用 info clients 命令获取最大的客户端缓冲区大小 。
127.0.0.1:6379> info clients# Clientsconnected_clients:1client_recent_max_input_buffer:2client_recent_max_output_buffer:0blocked_clients:0复制积压缓冲区是Redis 在 2.8 版本后提供的一个可重用的固定大小缓冲区,用于实现部分复制功能 。根据 repl-backlog-size 参数控制,默认 1MB 。对于复制积压缓冲区整个主节点只有一个,所有的从节点共享此缓冲区 。因此可以设置较大的缓冲区空间,比如说 100MB,可以有效避免全量复制 。有关复制积压缓冲区的详情可以看我的旧文章 Redis 复制过程详解 。
AOF 重写缓冲区:这部分空间用于在 Redis AOF 重写期间保存最近的写入命令 。AOF 重写缓冲区的大小用户无法控制,取决于 AOF 重写时间和写入命令量,不过一般都很小 。有关 AOF 持久化的详情可以看我的旧文章 Redis AOF 持久化详解 。
Redis 内存碎片
Redis 默认的内存分配器采用 jemalloc,可选的分配器还有:glibc、tcmalloc 。内存分配器为了更好地管理和重复利用内存,分配内存策略一般采用固定范围的内存块进行分配 。具体的分配策略后续会具体讲解,但是 Redis 正常碎片率一般在 1.03 左右(为什么是这个值) 。但是当存储的数据长度长度差异较大时,以下场景容易出现高内存碎片问题:
  • 频繁做更新操作,例如频繁对已经存在的键执行 Append、setrange 等更新操作 。
  • 大量过期键删除,键对象过期删除后,释放的空间无法得到重复利用,导致碎片率上升 。
这部分内容我们后续再详细讲解 jemalloc,因为大量的框架都会使用内存分配器,比如说 Netty 等 。
子进程内存消耗子进程内存消耗主要指执行 AOF 重写 或者进行 RDB 保存时 Redis 创建的子进程内存消耗 。Redis 执行 fork 操作产生的子进程内存占用量表现为与父进程相同,理论上需要一倍的物理内存来完成相应的操作 。但是 linux 具有写时复制技术 (copy-on-write),父子进程会共享相同的物理内存页,当父进程处理写请求时会对需要修改的页复制出一份副本完成写操作,而子进程依然读取 fork 时整个父进程的内存快照 。
一文了解 Redis 内存监控和内存消耗

文章插图
 
如上图所示,fork 时只拷贝 page table,也就是页表 。只有等到某一页发生修改时,才真正进行页的复制 。
但是 Linux Kernel 在 2.6.38 内存增加了 Transparent Huge Pages (THP) 机制,简单理解,它就是让页大小变大,本来一页为 4KB,开启 THP 机制后,一页大小为 2MB 。它虽然可以加快 fork 速度( 要拷贝的页的数量减少 ),但是会导致 copy-on-write 复制内存页的单位从 4KB 增大为 2MB,如果父进程有大量写命令,会加重内存拷贝量,都是修改一个页的内容,但是页单位变大了,从而造成过度内存消耗 。例如,以下两个执行 AOF 重写时的内存消耗日志:
// 开启 THPC * AOF rewrite: 1039 MB of memory used by copy-on-write// 关闭 THPC * AOF rewrite: 9MB of memory used by copy-on-write这两个日志出自同一个 Redis 进程,used_memory 总量是 1.5GB,子进程执行期间每秒写命令量都在 200 左右 。当分别开启和关闭 THP 时,子进程内存消耗有天壤之别 。所以,在高并发写的场景下开启 THP,子进程内存消耗可能是父进程的数倍,造成机器物理内存溢出 。


推荐阅读