你知道吗?RedisTemplate 默认使用的 JdkSerializationRedisSerializer,很容易带来乱码、跨语言不兼容以及 NotSerializableException 等常见问题。这种情况下,通常需要将其替换为更适合生产环境的 Jackson2JsonRedisSerializer,并且在反序列化时明确指定目标类型,否则读取结果往往会变成 LinkedHashMap。

RedisTemplate 默认采用 JDK 序列化,写入 Redis 后常常是一堆不可读的乱码,直接用于线上环境风险很高。实践中应尽快改为 JSON 序列化方式,否则不仅排查问题困难,调试和维护成本也会明显上升。
为什么不建议直接使用默认的 RedisTemplate
Spring Boot 自动配置的 RedisTemplate,默认序列化器是 JdkSerializationRedisSerializer。经过它处理后,key 和 value 都会被转换成二进制字节流,因此在 Redis CLI 中看到的内容通常类似:xacxedx00x05tx00x03foo。这会带来很多实际问题:开发人员难以直接排查缓存内容,其他语言编写的服务无法方便读取这些数据,同时还存在跨版本反序列化兼容性差的问题。
- 使用 JSON 序列化可以让 Redis 数据更易读、易调试、易协作,也更适合前端、Python 服务或其他系统共同访问
- 建议同时配置
keySerializer(使用StringRedisSerializer)和valueSerializer(使用GenericJackson2JsonRedisSerializer或自定义Jackson2JsonRedisSerializer) - 如果业务对象中包含 Java 8 时间类型(如
LocalDateTime等),还需要额外注册Jackson2ObjectMapperBuilder,并引入jackson-datatype-jsr310依赖以保证序列化与反序列化正常
@Cacheable 注解缓存 null 值会带来缓存穿透风险
Spring Cache 默认允许缓存 null 返回值。一旦数据库中查询不到目标数据,@Cacheable 可能会把 null 直接写入 Redis。这样后续请求虽然能命中缓存,但拿到的依然是空结果,问题数据始终不会回源校验,进而埋下缓存穿透隐患。
- 应在
CacheManager配置中显式调用.disableCachingNullValues(),避免将空值写入缓存 - 可以结合布隆过滤器(Bloom Filter)或空对象占位方案(如
"empty"字符串)进行兜底,但要注意误判率和内存占用问题 - 不要完全依赖
@Cacheable(unless = "#result == null"),它只能控制是否写入,无法从根本上解决首次请求的缓存处理时机问题
Lettuce 连接池参数配置不当会拖慢整个应用
默认的 Lettuce 连接池参数(max-active: 8、max-wait: -1ms)在高并发场景下非常容易成为性能瓶颈。尤其是 -1 表示无限等待,线程可能长期阻塞在 getConnection() 上,最终导致 HTTP 请求超时、业务线程耗尽,甚至引发系统雪崩。
max-wait必须设置为有限时间(例如100ms),超时快速失败通常比无限阻塞更容易治理min-idle建议设置为非 0(例如2),以减少冷启动或流量突增时的连接创建延迟max-active不要盲目调得过大(例如超过32),因为 Lettuce 基于 Netty 的异步模型,连接过多反而会增加调度成本- 线上环境建议同时配置
lettuce: shutdown-timeout: 100ms,降低应用关闭时连接未及时释放的风险
缓存失效策略如果选错,一致性问题很难控制
使用 @CacheEvict 删除缓存看起来简单直接,但在分布式系统中,多个实例同时更新数据库并删除缓存时,往往容易出现一致性问题。例如“先删缓存、后改数据库”期间可能发生脏读,或者“数据库更新成功、缓存删除失败”导致缓存与数据库长期不一致。
- 更推荐使用“先更新 DB,再删除缓存”配合最终一致性补偿机制,例如监听 binlog 或发送 MQ 消息进行异步删缓存
- 不要轻易用“更新缓存”替代“删除缓存”,除非能够保证所有写入路径都完整覆盖,并且不存在并发冲突
- 对于强一致性要求极高的业务场景(如账户余额、库存扣减),建议不要过度依赖缓存,而是直接查询数据库并结合行锁或乐观锁控制一致性
