Spring @Cacheable较手动ReactiveRedisTemplate实现快2倍的原因咨询
Spring @Cacheable较手动ReactiveRedisTemplate实现快2倍的原因咨询
你遇到的这个性能差异确实挺有意思的,结合你贴的代码细节,我觉得主要有这几个可能的原因,咱们一步步拆解分析:
1. 阻塞式 vs 反应式客户端的核心开销差异
你的@Cacheable用的是基于RedisConnectionFactory的RedisCacheManager,底层依赖的是阻塞式Redis客户端(比如Lettuce的阻塞API或者Jedis);而手动实现用的是ReactiveRedisTemplate,基于反应式Redis客户端。
在单Key、低并发的场景下,阻塞式操作反而可能更高效:
- 反应式客户端需要处理Mono/Flux的订阅、线程调度、流处理等额外逻辑,这些开销在简单的单请求操作中占比会很高;
- 阻塞式操作在协程的
Dispatchers.IO调度器下,直接同步调用Redis命令,线程切换和调度的开销反而更小。
这大概率是你看到2倍性能差的核心原因——反应式的优势要在高并发、批量操作时才会体现,单请求场景下反而会有“过度设计”的额外开销。
2. 序列化/反序列化路径的细微差异
虽然两者都用了kotlinx.serialization,但实际的序列化链路有细微区别:
@Cacheable的KotlinSerializationRedisSerializer直接将对象序列化为ByteArray后存入Redis,读取时直接从Redis拿ByteArray反序列化,中间没有多余的字符串转译;- 手动实现用的是
ReactiveRedisTemplate<String, String>,需要先把对象序列化为String,再通过StringRedisSerializer转成ByteArray存入Redis;读取时又要先转成String,再反序列化为对象。这多了一层String与ByteArray的转换开销,单次看起来不大,但累积起来会放大性能差异。
另外,手动实现中用了reified泛型的json.decodeFromString<T>(it),而@Cacheable用的是指定的TrainDetails.serializer(),前者在泛型序列化器的获取上可能有微小的额外开销(虽然Kotlin的inline reified已经做了优化,但具体场景下还是可能存在差异)。
3. Spring Cache的底层细节优化
Spring的RedisCacheManager在底层做了不少细节优化:
- 比如缓存键的统一处理、缓存配置的复用、避免重复创建序列化上下文;
- 对于协程的支持,Spring Cache会自动将阻塞式Redis调用适配到协程的IO调度器,减少了手动协程调度的开销;
- 还可能做了缓存命中的快速路径优化,比如避免不必要的对象包装。
4. 连接池配置的隐性差异
你没有贴连接池的配置,但默认情况下,阻塞式和反应式Redis客户端的连接池配置可能不同:
- 比如阻塞式客户端的连接池大小默认更大,或者连接复用策略更高效;
- 反应式客户端的连接池如果参数配置不合理(比如Lettuce反应式连接池的默认边界参数),可能导致连接等待的额外开销。
验证建议
你可以通过以下方式定位具体原因:
- 把
CacheConfig改成用ReactiveRedisCacheManager(基于ReactiveRedisConnectionFactory),看看性能是否和手动实现接近,验证是不是阻塞vs反应式的原因; - 把手动实现的
ReactiveRedisTemplate改成ReactiveRedisTemplate<String, TrainDetails>,直接用KotlinSerializationRedisSerializer作为value序列化器,去掉中间的String转译,对比性能变化; - 用Redis的
MONITOR命令或客户端监控工具,查看两种方式的Redis命令调用是否有差异,比如命令执行时间、是否有额外命令。
内容来源于stack exchange
相关产品推荐
相关产品推荐

