排查redis内存增长不应误判为查询结果缓存,因@cacheable等不自动缓存数据库查询过程;真正原因包括:序列化体积膨胀、stringredistemplate误存大二进制、caffeine与redis双写失控、定时任务写入超大map、lettuce堆外内存泄漏(netty directbytebuffer未释放),以及key无规律或未清理导致无限增长。

不是查询结果缓存导致的内存增长 —— Spring Boot 的 @Cacheable 等注解默认只缓存方法返回值,不缓存数据库查询过程本身;MyBatis-Plus 或 JPA 的查询结果也不会被自动塞进 Redis,除非你显式调用 redisTemplate.opsForValue().set() 或类似操作。
为什么排查方向容易误判为“查询结果缓存”
很多开发者看到接口查数据库 + Redis 有写入,就默认“查完数据库顺手缓存了”,但实际代码里往往没这行逻辑。真正让内存涨起来的,通常是以下几种更隐蔽的情况:
- 缓存对象序列化后体积膨胀(比如把整个
ArrayList<order></order>直接JSON.toJSONString()存 Redis,而对象含冗余字段、循环引用、大字符串) - 使用
StringRedisTemplate存二进制数据(如图片 Base64),但没控制单条 value 大小,Redis 内部碎片 + Netty 堆外缓冲区一起吃内存 - 本地未设限的
CaffeineCache和 Redis 双写,且 key 无规律或带时间戳,导致缓存项无限增长 - 定时任务反复调用
redisTemplate.opsForHash().putAll()写入超大 Map,却没清理旧 key
检查 Redis 客户端堆外内存泄漏(最常见真凶)
Spring Boot 2.0+ 默认用 Lettuce,它依赖 Netty,而 io.netty.util.internal.OutOfDirectMemoryError 就是典型症状。这不是你代码缓存了什么,而是 Netty 分配的堆外内存没及时释放:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 现象:压测时物理内存飙升,
jcmd pid VM.native_memory显示DirectByteBuffer区域持续增长,JVM 堆内却正常 - 原因:Lettuce 在高并发短连接场景下,Netty
PooledByteBufAllocator的 arena 未正确回收,尤其在连接频繁创建/关闭时 - 验证方式:加 JVM 参数
-XX:NativeMemoryTracking=detail,启动后执行jcmd pid VM.native_memory summary scale=MB,重点关注Internal和Direct两栏 - 临时缓解:配置
spring.redis.lettuce.pool.max-active=8+min-idle=2,避免连接风暴 - 根治方案:排除
lettuce-core,引入jedis(注意JedisConnectionFactory需手动配置,且不支持响应式)
确认是否真有“不该存的数据”进了 Redis
别只看业务代码有没有 set(),要直接查 Redis 本身:
- 用
redis-cli --scan --pattern "*"快速列出所有 key,观察是否有大量带时间戳、UUID、用户 ID 的 key(比如user:12345:profile:202605251023) - 对可疑 key 执行
MEMORY USAGE keyname,看单个 value 占多少 MB(>1MB 就该警惕) - 检查是否误用
redisTemplate.opsForList().leftPushAll()往一个 list 塞了几万条日志——这种操作会让 Redis 内存线性上涨且无法自动淘汰 - 留意
RedisCacheManager是否配置了cacheDefaults.setUsePrefix(true)却没设keyGenerator,导致 key 拼接出错、重复写入
真正要盯住的不是“有没有缓存查询结果”,而是“谁在往 Redis 写、写了什么、写了多大、写了多久没删”。堆外内存泄漏和无意识的全量 dump,比缓存策略本身更容易让服务半夜 OOM。










