Spring中的@Cacheable开销
Spring 3.1引入了很棒的缓存抽象层 。 最后,我们可以放弃所有本地化的方面,装饰器和污染我们与缓存相关的业务逻辑的代码。
从那时起,我们可以简单地注释重量级方法,并让Spring和AOP机械完成工作:
@Cacheable("books")
public Book findBook(ISBN isbn) {...}
"books"
是一个缓存名称, isbn
参数成为缓存键,返回的Book
对象将放置在该键下。 缓存名称的含义取决于基础缓存管理器(EhCache,并发映射等)– Spring使插入不同的缓存提供程序变得容易。 但是这篇文章与Spring的缓存功能无关 ...
前段时间,我的队友正在优化底层代码,并发现了缓存的机会。 他Swift应用@Cacheable
只是为了发现代码的性能比以前差。 他摆脱了注释,并使用了良好的旧java.util.ConcurrentHashMap
手动实现了自己的缓存。 性能要好得多。 他指责@Cacheable
和Spring AOP的开销和复杂性。 我不敢相信缓存层的性能如此之差,直到我不得不自己几次调试Spring缓存方面(代码中的一些讨厌的错误,缓存无效化是CS中最难的两件事之一 )。 好吧,缓存抽象代码比人们期望的要复杂得多(毕竟只是获取和放入 !),但这并不一定意味着它一定那么慢吗?
在科学中,我们不相信和信任,我们进行衡量和基准测试。 因此,我写了一个基准来精确测量@Cacheable
层的开销。 Spring中的缓存抽象层是在Spring AOP之上实现的,可以进一步在Java代理,CGLIB生成的子类或AspectJ工具的之上实现。 因此,我将测试以下配置:
- 完全没有缓存–无需中间层即可测量代码的速度
- 在业务代码中使用
ConcurrentHashMap
进行手动缓存处理 @Cacheable
与实现AOP的CGLIB@Cacheable
与实现AOP的java.lang.reflect.Proxy
@Cacheable
与AspectJ的编译时编织(如类似的基准测试所示, CTW比LTW稍快 )- 本地的AspectJ缓存方面–在业务代码中的手动缓存和Spring抽象之间的某种程度
让我重申一下:我们没有衡量缓存的性能提升,也没有比较各种缓存提供程序。 这就是我们的测试方法尽可能快的原因,我将使用Spring中最简单的ConcurrentMapCacheManager
。 所以这是一个有问题的方法:
public interface Calculator {int identity(int x);}public class PlainCalculator implements Calculator {@Cacheable("identity")@Overridepublic int identity(int x) {return x;}}
我知道,我知道缓存这种方法毫无意义。 但是我想衡量缓存层的开销(在缓存命中期间)。 每个缓存配置将具有其自己的ApplicationContext
因为您不能在一个上下文中混合使用不同的代理模式:
public abstract class BaseConfig {@Beanpublic Calculator calculator() {return new PlainCalculator();}}@Configuration
class NoCachingConfig extends BaseConfig {}@Configuration
class ManualCachingConfig extends BaseConfig {@Bean@Overridepublic Calculator calculator() {return new CachingCalculatorDecorator(super.calculator());}
}@Configuration
abstract class CacheManagerConfig extends BaseConfig {@Beanpublic CacheManager cacheManager() {return new ConcurrentMapCacheManager();}}@Configuration
@EnableCaching(proxyTargetClass = true)
class CacheableCglibConfig extends CacheManagerConfig {}@Configuration
@EnableCaching(proxyTargetClass = false)
class CacheableJdkProxyConfig extends CacheManagerConfig {}@Configuration
@EnableCaching(mode = AdviceMode.ASPECTJ)
class CacheableAspectJWeaving extends CacheManagerConfig {@Bean@Overridepublic Calculator calculator() {return new SpringInstrumentedCalculator();}}@Configuration
@EnableCaching(mode = AdviceMode.ASPECTJ)
class AspectJCustomAspect extends CacheManagerConfig {@Bean@Overridepublic Calculator calculator() {return new ManuallyInstrumentedCalculator();}}
每个@Configuration
类代表一个应用程序上下文。 CachingCalculatorDecorator
是围绕真正的计算器进行装饰的装饰器(欢迎使用1990年代):
public class CachingCalculatorDecorator implements Calculator {private final Map<Integer, Integer> cache = new java.util.concurrent.ConcurrentHashMap<Integer, Integer>();private final Calculator target;public CachingCalculatorDecorator(Calculator target) {this.target = target;}@Overridepublic int identity(int x) {final Integer existing = cache.get(x);if (existing != null) {return existing;}final int newValue = target.identity(x);cache.put(x, newValue);return newValue;}
}
SpringInstrumentedCalculator
和ManuallyInstrumentedCalculator
与PlainCalculator
完全相同,但是它们分别由AspectJ编译时织布器(带有Spring和自定义方面)进行检测。 我的自定义缓存方面如下所示:
public aspect ManualCachingAspect {private final Map<Integer, Integer> cache = new ConcurrentHashMap<Integer, Integer>();pointcut cacheMethodExecution(int x): execution(int com.blogspot.nurkiewicz.cacheable.calculator.ManuallyInstrumentedCalculator.identity(int)) && args(x);Object around(int x): cacheMethodExecution(x) {final Integer existing = cache.get(x);if (existing != null) {return existing;}final Object newValue = proceed(x);cache.put(x, (Integer)newValue);return newValue;}}
经过所有准备工作,我们终于可以编写基准测试了。 首先,我启动所有应用程序上下文并获取Calculator
实例。 每个实例都不同。 例如, noCaching
是没有包装的PlainCalculator
实例, cacheableCglib
是CGLIB生成的子类,而aspectJCustom
是ManuallyInstrumentedCalculator
的实例,其中编织了我的自定义方面。
private final Calculator noCaching = fromSpringContext(NoCachingConfig.class);
private final Calculator manualCaching = fromSpringContext(ManualCachingConfig.class);
private final Calculator cacheableCglib = fromSpringContext(CacheableCglibConfig.class);
private final Calculator cacheableJdkProxy = fromSpringContext(CacheableJdkProxyConfig.class);
private final Calculator cacheableAspectJ = fromSpringContext(CacheableAspectJWeaving.class);
private final Calculator aspectJCustom = fromSpringContext(AspectJCustomAspect.class);private static <T extends BaseConfig> Calculator fromSpringContext(Class<T> config) {return new AnnotationConfigApplicationContext(config).getBean(Calculator.class);
}
我将通过以下测试来练习每个Calculator
实例。 附加的累加器是必需的,否则JVM可能会优化整个循环(!):
private int benchmarkWith(Calculator calculator, int reps) {int accum = 0;for (int i = 0; i < reps; ++i) {accum += calculator.identity(i % 16);}return accum;
}
这是完整的卡尺测试,没有讨论任何部件:
public class CacheableBenchmark extends SimpleBenchmark {//...public int timeNoCaching(int reps) {return benchmarkWith(noCaching, reps);}public int timeManualCaching(int reps) {return benchmarkWith(manualCaching, reps);}public int timeCacheableWithCglib(int reps) {return benchmarkWith(cacheableCglib, reps);}public int timeCacheableWithJdkProxy(int reps) {return benchmarkWith(cacheableJdkProxy, reps);}public int timeCacheableWithAspectJWeaving(int reps) {return benchmarkWith(cacheableAspectJ, reps);}public int timeAspectJCustom(int reps) {return benchmarkWith(aspectJCustom, reps);}
}
希望您仍在继续我们的实验。 现在,我们将执行Calculate.identity()
数百万次,并查看哪种缓存配置效果最佳。 由于我们仅使用16个不同的参数调用identity()
,因此几乎永远不会碰到方法本身,因为我们总是会遇到缓存命中的情况。 想知道结果吗?
benchmark ns linear runtimeNoCaching 1.77 =ManualCaching 23.84 =CacheableWithCglib 1576.42 ==============================CacheableWithJdkProxy 1551.03 =============================
CacheableWithAspectJWeaving 1514.83 ============================AspectJCustom 22.98 =
解释
让我们一步一步走。 首先,在Java中调用方法相当快! 1.77 纳秒 ,我们在这里谈论的是我的Intel(R)Core(TM)2 Duo CPU T7300 @ 2.00GHz上的3个CPU周期 ! 如果这不能使您确信Java是快速的,那么我不知道会怎样。 但是回到我们的测试。
手工缓存装饰器也相当快。 当然,与纯函数调用相比,它慢了一个数量级,但与所有@Scheduled
基准测试相比,它仍然非常快。 我们看到下降了3个数量级 ,从1.8 ns下降到1.5μs。 我对由AspectJ支持的@Cacheable
感到特别失望。 将所有缓存方面直接预编译到我的Java .class
文件中之后,我希望它比动态代理和CGLIB快得多。 但这似乎并非如此。 所有这三种Spring AOP技术都是相似的。
最大的惊喜是我自定义的AspectJ方面。 它甚至比CachingCalculatorDecorator
还要快! 也许是由于装饰器中的多态调用? 我强烈建议您在GitHub上克隆此基准测试并运行它( mvn clean test
,大约需要2分钟)以比较您的结果。
结论
您可能想知道为什么Spring抽象层这么慢? 好吧,首先,请检查CacheAspectSupport
的核心实现-它实际上非常复杂。 其次,真的那么慢吗? 算一下-您通常在数据库,网络和外部API成为瓶颈的业务应用程序中使用Spring。 您通常会看到什么延迟? 毫秒? 几百或几百毫秒? 现在添加2μs的开销(最坏的情况)。 对于缓存数据库查询或REST调用,这是完全可以忽略的。 选择哪种技术都没关系 。
但是,如果要在非常接近金属的地方缓存非常低级的方法,例如CPU密集型的内存中计算,那么Spring抽象层可能会显得过大。 底线:测量!
PS: Markdown格式的本文 基准和内容均可免费获得。
参考:来自Java和社区博客的JCG合作伙伴 Tomasz Nurkiewicz提供的@ @ Spring的可缓存开销 。
翻译自: https://www.javacodegeeks.com/2013/01/cacheable-overhead-in-spring.html
Spring中的@Cacheable开销相关推荐
- Spring中使用缓存时你应该知道的知识
2019独角兽企业重金招聘Python工程师标准>>> 常见问题 缓存穿透,雪崩,击穿 下面的文章里都有详细介绍 http://blog.csdn.net/zeb_perfect/a ...
- spring中这些能升华代码的技巧,可能会让你爱不释手
前言 最近越来越多的读者认可我的文章,还是件挺让人高兴的事情.有些读者私信我说希望后面多分享spring方面的文章,这样能够在实际工作中派上用场.正好我对spring源码有过一定的研究,并结合我这几年 ...
- (转)Spring中Singleton模式的线程安全
不知道哪里的文章,总结性还是比较好的.但是代码凌乱,有的还没有图.如果找到原文了可以进行替换! spring中的单例 spring中管理的bean实例默认情况下是单例的[sigleton类型],就还有 ...
- spring 中单利模式的理解
一.Spring单例模式与线程安全 Spring框架里的bean,或者说组件,获取实例的时候都是默认的单例模式,这是在多线程开发的时候要尤其注意的地方. 单例模式的意思就是只有一个实例.单例模式确保某 ...
- redis spring 切面缓存_今日份学习: Spring中使用AOP并实现redis缓存?
笔记 在Spring中如何使用AOP? Spring是如何切换JDK动态代理和CGLIB的? spring.aop.proxy-target-class=true (在下方第二个链接中,原生doc中提 ...
- Spring中注解大全
Spring中注解大全 @Controller 标识一个该类是Spring MVC controller 处理器,用来创建处理http请求的对象 @Controller public class Te ...
- 如果你每次面试前都要去背一篇Spring中Bean的生命周期,请看完这篇文章
前言 当你准备去复习Spring中Bean的生命周期的时候,这个时候你开始上网找资料,很大概率会看到下面这张图: 先不论这张图上是否全面,但是就说这张图吧,你是不是背了又忘,忘了又背? 究其原因在于, ...
- Spring中常用注解的介绍
spring中使用注解时配置文件的写法: <?xml version="1.0" encoding="UTF-8"?> <span style ...
- Spring 中那些让你爱不释手的代码技巧
前言 最近越来越多的读者认可我的文章,还是件挺让人高兴的事情.有些读者私信我说希望后面多分享spring方面的文章,这样能够在实际工作中派上用场.正好我对spring源码有过一定的研究,并结合我这几年 ...
最新文章
- 牛客网里刷题:JS获取输入的数组
- 修改文件后git只用两步push文件
- java程序运行必须得三个io类_Java基础知识(三)
- k8s 手动恢复redis 集群_二进制手动部署k8s-1.14高可用集群(二、集群部署)
- 前端学习(705):do-while
- 凉了!张三同学没答好「进程间通信」,被面试官挂了....
- 全新版个人博客小程序,无需开发服务端接口即可使用
- nosql----redis持久化详解
- PXE+Kickstart无人值守安装操作系统
- 区块链 Fisco bcos 智能合约(12)-Solidity的高级特性
- c++ inline 函数及变量
- C/C++[PAT B1022]D进制的A+B
- 遥控器油门摇杆电位器封装尺寸图
- 最全的CDR抠图教程
- 编译原理复习总结及思维导图
- android 安全知识总结
- 雷电3接口能干嘛_把雷电3插到TypeC接口了?不认识电脑接口的小伙伴看过来
- aes相关资料整理及代码C/C++
- 博客园的整改了,我们还能去哪些技术社区写博客
- 涂鸦智能2021年Q1营收5690万美元:亏损规模扩大,已跌破发行价
热门文章
- mybatis_user_guide(3)XML配置
- java 抛异常 jvm_邪恶的Java技巧使JVM忘记检查异常
- java 接口 私有_Java 9:好的,坏的和私有的接口方法
- swarm 和 k8s_Wildfly Swarm,朝着成熟和一小部分贡献
- 投行数据_投行对Java的二十大核心访谈问答
- 大屏可视化分配率是什么意思_什么是分配率?
- 绩效工作流_流绩效–您的想法
- git hok json_从战中反弹:将Git提交信息作为JSON返回
- maf中anglearc_Oracle MAF中的LOV
- java 解析日期格式_日期/时间格式/解析,Java 8样式