如何通过MEMORY DOCTOR命令检测Redis第三方模块导致的内存泄漏问题?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1455个文字,预计阅读时间需要6分钟。
Redis 自带的 MEMORY DOCTOR 并非万能的诊断工具,但它会基于当前内存使用模式、键分布、客户端连接、Lua 脚本缓存等维度,指出哪些行为正在健康地偏离基准线。第三方模块(如 RedisJSON、RediSearch、RedisAI)可以通过自定义命令或内部数据结构注册到 Redis 中,它们不会改变标准的 dict 或 ziplist 内存路径,但会暴露在 MEMORY STATS 和 MEMORY USAGE 的聚合指标中——MEMORY DOCTOR 正是基于这些异常聚合值发出警告。
常见错误现象:MEMORY DOCTOR 返回 "The server is using more memory than expected for its workload",但 INFO memory 看不出明显大 key;或者反复执行 MEMORY USAGE 某个普通字符串 key,返回值却持续增长。
- 它不直接定位到某个模块的 C 代码,但会提示
"You have a lot of client output buffers"或"You are running many Lua scripts that are not being garbage collected"—— 这些往往是第三方模块触发的副作用 - 某些模块(如 RedisJSON v2.0+)默认启用惰性 JSON 解析,解析中间态会暂存在客户端缓冲区,而
MEMORY DOCTOR会把这部分归为「client output buffer bloat」 - 运行
MEMORY DOCTOR前必须确保已执行过MEMORY PURGE(仅限启用了 jemalloc 的 Redis),否则碎片堆积会干扰判断
怎么用 MEMORY DOCTOR 配合 MODULE LIST 锁定可疑模块
MEMORY DOCTOR 本身不输出模块名,但它的诊断结论和 MODULE LIST 的加载顺序、模块版本能交叉验证。例如,当它提示 "Your memory allocator is not optimized for Redis workloads",而你用的是 RedisAI 2.8 + jemalloc 5.2.1,那就大概率是该模块的 tensor 缓存未正确释放。
- 先运行
MODULE LIST,记录每个模块的name和ver,重点关注非官方模块(redisjson、ft、ai) - 对每个模块查文档确认其内存行为:RedisJSON 默认开启
json.max-str-len限制,若设为 -1 且存入超长字符串,会导致内部 buffer 不释放 - 用
MEMORY USAGE测试模块创建的 key 类型:对一个JSON.SET写入的 key 执行MEMORY USAGE,再JSON.GET一次,再测——如果第二次结果比第一次大数百 KB,说明解析态驻留,不是正常行为 - 注意
MEMORY DOCTOR的输出依赖于采样窗口,默认只看最近 60 秒的分配峰值,瞬时泄露可能被忽略;可临时加CONFIG SET maxmemory-samples 200提高精度
第三方模块内存泄露的典型表现和绕过验证法
真正的泄露不一定表现为 RSS 持续上涨,更常见的是「RSS 不涨,但 used_memory_peak 越来越高,且 used_memory_rss / used_memory 比值 > 2.5」——这说明分配器碎片严重,而很多模块(尤其是带 JIT 或 tensor pool 的)会绕过 Redis 的内存统计钩子。
- RediSearch 在
FT.SEARCH大量命中后,used_memory_dataset可能不变,但used_memory_overhead持续上升,这是索引缓存未回收的信号 - RedisAI 的
AI.TENSORSET如果复用同一key多次写入不同 shape 的 tensor,旧 tensor 内存可能滞留在 GPU 显存或 pinned host memory 中,而 Redis 进程 RSS 完全不体现 - 验证方法:停掉业务流量,执行
MODULE UNLOAD <module_name>(需模块支持热卸载),观察INFO memory中used_memory_rss是否回落;若回落明显,基本锁定该模块 - 不要依赖
redis-cli --stat看内存,它只轮询used_memory,漏掉overhead和dataset的分离变化
为什么不能只靠 INFO memory 就排除模块问题
INFO memory 是快照式统计,所有字段都是 Redis 主循环内聚合计数,而第三方模块的内存申请常发生在异步线程、回调函数或外部库(如 ONNX Runtime、BLAS)中,根本不会经过 Redis 的 zmalloc 钩子。
-
used_memory只统计 Redis 自己调用zmalloc的部分,模块用malloc或cudaMalloc分配的内存完全不计入 -
mem_allocator字段显示jemalloc并不代表所有内存都由它管理;模块若静态链接了libc的malloc,就会形成双 allocator 共存,MEMORY STATS无法反映后者 - 真正管用的是操作系统级观测:用
pstack <pid>看线程栈里有没有模块相关符号(如redis_json_parse、rai_tensor_create),再配合cat /proc/<pid>/smaps | awk '/^Size:/ {sum+=$2} END {print sum}'看总物理内存,和INFO memory的used_memory_rss对比,差值大就说明有“黑内存”
复杂点在于:同一个模块在不同 Redis 版本下内存行为可能完全不同,比如 RedisJSON 在 7.0 后改用新的序列化格式,老版本的 dump 文件加载进新实例会导致解析时额外拷贝。这种问题 MEMORY DOCTOR 不会报,得靠 DEBUG HTSTATS 和模块自己的 JSON.DEBUG 命令交叉验证。
本文共计1455个文字,预计阅读时间需要6分钟。
Redis 自带的 MEMORY DOCTOR 并非万能的诊断工具,但它会基于当前内存使用模式、键分布、客户端连接、Lua 脚本缓存等维度,指出哪些行为正在健康地偏离基准线。第三方模块(如 RedisJSON、RediSearch、RedisAI)可以通过自定义命令或内部数据结构注册到 Redis 中,它们不会改变标准的 dict 或 ziplist 内存路径,但会暴露在 MEMORY STATS 和 MEMORY USAGE 的聚合指标中——MEMORY DOCTOR 正是基于这些异常聚合值发出警告。
常见错误现象:MEMORY DOCTOR 返回 "The server is using more memory than expected for its workload",但 INFO memory 看不出明显大 key;或者反复执行 MEMORY USAGE 某个普通字符串 key,返回值却持续增长。
- 它不直接定位到某个模块的 C 代码,但会提示
"You have a lot of client output buffers"或"You are running many Lua scripts that are not being garbage collected"—— 这些往往是第三方模块触发的副作用 - 某些模块(如 RedisJSON v2.0+)默认启用惰性 JSON 解析,解析中间态会暂存在客户端缓冲区,而
MEMORY DOCTOR会把这部分归为「client output buffer bloat」 - 运行
MEMORY DOCTOR前必须确保已执行过MEMORY PURGE(仅限启用了 jemalloc 的 Redis),否则碎片堆积会干扰判断
怎么用 MEMORY DOCTOR 配合 MODULE LIST 锁定可疑模块
MEMORY DOCTOR 本身不输出模块名,但它的诊断结论和 MODULE LIST 的加载顺序、模块版本能交叉验证。例如,当它提示 "Your memory allocator is not optimized for Redis workloads",而你用的是 RedisAI 2.8 + jemalloc 5.2.1,那就大概率是该模块的 tensor 缓存未正确释放。
- 先运行
MODULE LIST,记录每个模块的name和ver,重点关注非官方模块(redisjson、ft、ai) - 对每个模块查文档确认其内存行为:RedisJSON 默认开启
json.max-str-len限制,若设为 -1 且存入超长字符串,会导致内部 buffer 不释放 - 用
MEMORY USAGE测试模块创建的 key 类型:对一个JSON.SET写入的 key 执行MEMORY USAGE,再JSON.GET一次,再测——如果第二次结果比第一次大数百 KB,说明解析态驻留,不是正常行为 - 注意
MEMORY DOCTOR的输出依赖于采样窗口,默认只看最近 60 秒的分配峰值,瞬时泄露可能被忽略;可临时加CONFIG SET maxmemory-samples 200提高精度
第三方模块内存泄露的典型表现和绕过验证法
真正的泄露不一定表现为 RSS 持续上涨,更常见的是「RSS 不涨,但 used_memory_peak 越来越高,且 used_memory_rss / used_memory 比值 > 2.5」——这说明分配器碎片严重,而很多模块(尤其是带 JIT 或 tensor pool 的)会绕过 Redis 的内存统计钩子。
- RediSearch 在
FT.SEARCH大量命中后,used_memory_dataset可能不变,但used_memory_overhead持续上升,这是索引缓存未回收的信号 - RedisAI 的
AI.TENSORSET如果复用同一key多次写入不同 shape 的 tensor,旧 tensor 内存可能滞留在 GPU 显存或 pinned host memory 中,而 Redis 进程 RSS 完全不体现 - 验证方法:停掉业务流量,执行
MODULE UNLOAD <module_name>(需模块支持热卸载),观察INFO memory中used_memory_rss是否回落;若回落明显,基本锁定该模块 - 不要依赖
redis-cli --stat看内存,它只轮询used_memory,漏掉overhead和dataset的分离变化
为什么不能只靠 INFO memory 就排除模块问题
INFO memory 是快照式统计,所有字段都是 Redis 主循环内聚合计数,而第三方模块的内存申请常发生在异步线程、回调函数或外部库(如 ONNX Runtime、BLAS)中,根本不会经过 Redis 的 zmalloc 钩子。
-
used_memory只统计 Redis 自己调用zmalloc的部分,模块用malloc或cudaMalloc分配的内存完全不计入 -
mem_allocator字段显示jemalloc并不代表所有内存都由它管理;模块若静态链接了libc的malloc,就会形成双 allocator 共存,MEMORY STATS无法反映后者 - 真正管用的是操作系统级观测:用
pstack <pid>看线程栈里有没有模块相关符号(如redis_json_parse、rai_tensor_create),再配合cat /proc/<pid>/smaps | awk '/^Size:/ {sum+=$2} END {print sum}'看总物理内存,和INFO memory的used_memory_rss对比,差值大就说明有“黑内存”
复杂点在于:同一个模块在不同 Redis 版本下内存行为可能完全不同,比如 RedisJSON 在 7.0 后改用新的序列化格式,老版本的 dump 文件加载进新实例会导致解析时额外拷贝。这种问题 MEMORY DOCTOR 不会报,得靠 DEBUG HTSTATS 和模块自己的 JSON.DEBUG 命令交叉验证。

