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;}
}

SpringInstrumentedCalculatorManuallyInstrumentedCalculatorPlainCalculator完全相同,但是它们分别由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生成的子类,而aspectJCustomManuallyInstrumentedCalculator的实例,其中编织了我的自定义方面。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/370282.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电工接线模拟仿真软件_VERICUT数控加工仿真软件,最强的数控加工模拟软件,你知道么?...

VERICUT数控加工仿真软件,最强的数控加工模拟软件VERICUT软件及功能简介1、VERICUT软件简介VERICUT是美国CGTech公司开发一款专业的数控加工仿真软件&#xff0c;是当前全球数控加工程序验证、机床模拟、工艺程序优化软件领域的领导者。该软件自1988年开始推向市场以来&#xf…

php数据库创建文件失败怎么回事,安装zblogPHP提示“创建c_option.php失败”解决方法...

有zblog用户反应在安装zblog的最后一步时提示“创建c_option.php失败”&#xff0c;如下图&#xff1a;本文来说明下这个问题的原因和解决办法。问题产生的原因&#xff1a;c_option.php是zblog的数据库配置文件&#xff0c;当安装完成的时候程序会自动创建这个文件。如果你的主…

一次搞清楚Mysql联合索引,以及联合索引究竟用了多少

一群DBA朋友聊天&#xff0c;突然抛出一个某公司联合索引的面试题&#xff0c;当时好多人都蒙了&#xff0c;这次针对这个问题&#xff0c;做了个简单的实验&#xff0c;把联合索引的作用一次搞清楚 问题大概是这样的&#xff0c;联合索引&#xff08;a,b,c,d&#xff09;下面这…

CSS Variables

CSS原生变量(CSS自定义属性) 示例地址&#xff1a;https://github.com/ccyinghua/Css-Variables 一、css原生变量的基础用法 变量声明使用两根连词线"--"表示变量&#xff0c;"$color"是属于Sass的语法&#xff0c;"color"是属于Less的语法&a…

【基础中的基础】引用类型和值类型,以及引用传递和值传递

一直在博客园怼人&#xff0c;非常惭愧。所以郑重决定&#xff1a; 好好写一篇干货&#xff0c;然后再接着怼人。 这是一起帮上陈百万同学的求助&#xff0c;讲了一会之后&#xff0c;我觉得很有些普世价值&#xff0c;干脆就发到园子来。面向小白&#xff0c;高手轻拍。 我们从…

Java 7:使用NIO.2进行文件过滤–第3部分

大家好。 这是使用NIO.2系列进行文件过滤的第3部分。 对于那些尚未阅读第1 部分或第2部分的人 &#xff0c;这里有个回顾。 NIO.2是自Java 7起JDK中包含的用于I / O操作的新API。使用此新API&#xff0c;您可以执行与java.io相同的操作&#xff0c;以及许多出色的功能&#xf…

python众数问题给定含有n个元素的多重集合s_分治法求众数 给定含有n个元素的多重集合S 联合开发网 - pudn.com...

分治法求众数所属分类&#xff1a;数据结构开发工具&#xff1a;C/C文件大小&#xff1a;240KB下载次数&#xff1a;3上传日期&#xff1a;2018-01-04 20:19:09上 传 者&#xff1a;九鼎说明&#xff1a; 给定含有n个元素的多重集合S&#xff0c;每个元素在S中出现的次数称为该…

mysql 5.0 乱码,解决MySQL 5.0.16的乱码问题

导读&#xff1a;问&#xff1a;怎样解决MySQL 5.0.16的乱码问题?答&#xff1a;MySQL 5.0.16的乱码问题可以用下面的方法解决&#xff1a;1.设置phpMyAdminLanguage:Chinese simplified (zh-utf-8)MySQL 字符集&#xff1a;UTF-8 Unicode (utf8)MySQL 连接校对 gbk_chinese_c…

Hadoop Serialization -- hadoop序列化具体解释 (2)【Text,BytesWritable,NullWritable】

回想&#xff1a;回想序列化&#xff0c;事实上原书的结构非常清晰&#xff0c;我截图给出书中的章节结构&#xff1a;序列化最基本的&#xff0c;最底层的是实现writable接口&#xff0c;wiritable规定读和写的游戏规则 &#xff08;void write(DataOutput out) throws IOExce…

我需要多少个线程?

这取决于您的应用程序。 但是对于那些希望对如何从生产站点购买的所有昂贵内核中挤出更多资金的人来说&#xff0c;请多多包涵&#xff0c;我将阐明围绕多线程 Java应用程序的奥秘。 内容针对最典型的Java EE应用程序进行了“优化”&#xff0c;该应用程序具有Web前端&#xff…

H5网页适配 iPhoneX,就是这么简单

iPhoneX 取消了物理按键&#xff0c;改成底部小黑条&#xff0c;这一改动导致网页出现了比较尴尬的屏幕适配问题。对于网页而言&#xff0c;顶部&#xff08;刘海部位&#xff09;的适配问题浏览器已经做了处理&#xff0c;所以我们只需要关注底部与小黑条的适配问题即可&#…

python为什么closed_为什么python类的函数被调用两次[关闭](Why a function of python class is called twice [closed])...

为什么python类的函数被调用两次[关闭](Why a function of python class is called twice [closed])我遇到了两次调用的python类函数的问题。 我正在使用Spyder IDE。这是我的简单代码class Test:def f(self):print("a")from Test import *t Test()t.f()当我按“运行…

php关联数组和哈希表,php遍历哈希表及关联数组的实例代码

有关php数组的分类&#xff0c;PHP数组分为&#xff1a;数字索引数组和关联数组。其中数字索引数组和C语言中的数组一样&#xff0c;下标是为0&#xff0c;1&#xff0c;2…而关联数组下标可能是任意类型&#xff0c;与其它语言中的hash&#xff0c;map等结构相似。PHP遍历关联…

数字校园-云资源平台 2014.10.26-人人通共享空间

近期,教育部在统计学校信息化建设情况,当中一项重要内容,作为三通两平台的一个环节,就是学校开通人人通空间的情况,网上普及了一下知识,不就是十多年前就玩的学校博客的变种吗,网上有一些产品,也是没有热闹起来,为要求而要求的多,既然要求,就来一个吧,花了几天时间,也做了一个.…

VUE.js 中取得后台原生HTML字符串 原样显示问题

今天使用vue调试页面&#xff0c;发现了页面上的一个问题&#xff0c;后台数据传过来的HTML字符串并没有被转换为正常的HTML代码&#xff0c;一拍脑门&#xff0c;发现忘记转换了&#xff0c;于是满心欢喜加上了{{{}}}。但是之后构建发现报错&#xff1a; 为此去官网上查了下…

高性能持久消息

总览 尽管有许多可用于Java的高性能消息传递系统&#xff0c;但大多数都避免引用基准&#xff0c;包括持久消息传递和消息的序列化/反序列化。 这样做有很多原因。 1&#xff09;您并不总是需要或想要持久消息2&#xff09;您希望使用自己的序列化选项。 避免使用它们的一个重要…

python去掉重复内容并按原来次序输出元素_在Python中,从列表中删除重复项以使所有元素在保留顺序时都是唯一的最快的算法是什么?...

饮歌长啸使用方法&#xff1a;lst [8, 8, 9, 9, 7, 15, 15, 2, 20, 13, 2, 24, 6, 11, 7, 12, 4, 10, 18, 13, 23, 11, 3, 11, 12, 10, 4, 5, 4, 22, 6, 3, 19, 14, 21, 11, 1, 5, 14, 8, 0, 1, 16, 5, 10, 13, 17, 1, 16, 17, 12, 6, 10, 0, 3, 9, 9, 3, 7, 7, 6, 6, 7, 5, 1…

Lucene –快速添加索引和搜索功能

什么是Lucene&#xff1f; Apache LuceneTM是完全用Java编写的高性能&#xff0c;功能齐全的文本搜索引擎库。 它是一项适用于几乎所有需要全文搜索的应用程序的技术&#xff0c;尤其是跨平台的应用程序。 Lucene可以纯文本&#xff0c;整数&#xff0c;索引PDF&#xff0c;Of…

td 双击 编辑 php,双击表格td进行编辑

$(function(){//隔行换色// $("tbody tr:odd").css("background-color","#eee");var numId $(".tbody td");numId.dblclick(function(){var tdIns $(this);var tdpar $(this).parents("tr");//tdpar.remove();//current_…

前端开发之基础知识-HTML(一)

1.1 html概述和基本结构 html概述 HTML是 HyperText Mark-up Language 的首字母简写&#xff0c;意思是超文本标记语言&#xff0c;超文本指的是超链接&#xff0c;标记指的是标签&#xff0c;是一种用来制作网页的语言&#xff0c;这种语言由一个个的标签组成&#xff0c;用…