Spring默认提供的:ConcurrentMapCache。使用它的原因是它是spring-context内置的,无需额外导包就能使用,超好用的

但在实际开发过程中,Spring内建提供的实现显然是满足不了日益复杂的需求的,现实情况是很小有可能直接使用ConcurrentMapCacheManagerConcurrentMapCache去作为存储方案,毕竟它提供的能力非常有限,有如下两个致命的不足:

  1. 基于本地内存的缓存,且它无法用于分布式环境
  2. 没有缓存过期时间Expire

就单单是这两点没有得到满足,在实际开发中就足以有理由抛弃内置实现,而需要引入第三方更为强大的缓存实现方案。

Spring Cache缓存抽象的实现产品

缓存标准方面:一个是JSR107,一个是Spring Cache,前面也说了Spring Cache已经成为了现实中的标准,所以市面上它的实现产品非常丰富,因此本文主要看看基于Spring Cache的实现产品的集成方案。

Spring Cache它也是支持JSR107规范的,可谓非常的友好。(请导入spring-contextr-support包)

要想了解常用的、流行的Spring Cache的实现方案有哪些,我推荐一个由SpringBoot提枚举类CacheType,它里面收纳得还是比较全面的:

此枚举是SpringBoot提供的供以参考,但本文内容和SpringBoot没有半毛钱关系
public enum CacheType {GENERIC, // 使用的SimpleCacheManager(自己手动指定Cache,可任意类型Cache实现哦)JCACHE, // 使用org.springframework.cache.jcache.JCacheCacheManagerEHCACHE, // 使用org.springframework.cache.ehcache.EhCacheCacheManagerHAZELCAST, // 使用com.hazelcast.spring.cache.HazelcastCacheManagerINFINISPAN, // 使用org.infinispan.spring.provider.SpringEmbeddedCacheManagerCOUCHBASE, // 使用com.couchbase.client.spring.cache.CouchbaseCacheManagerREDIS, // 使用org.springframework.data.redis.cache.RedisCacheManager,依赖于RedisTemplate进行操作CAFFEINE, // 使用org.springframework.cache.caffeine.CaffeineCacheManager@DeprecatedGUAVA, // 使用org.springframework.cache.guava.GuavaCacheManager,已经过期不推荐使用了SIMPLE, // 使用ConcurrentMapCacheManagerNONE; // 使用NoOpCacheManager,表示禁用缓存
}

这些就是业内最为流行的那些缓存实现,下面做简单的介绍作为参考:

  1. EhCache:一个纯Java的进程内缓存框架,具有快速、精干等特点。因为它是纯Java进程的,所以也是基于本地缓存的。(注意:EhCache2.x和EhCache3.x差异巨大且不兼容)
  2. Hazelcast:基于内存的数据网格。虽然它基于内存,但是分布式应用程序可以使用Hazelcast进行分布式缓存、同步、集群、处理、发布/订阅消息等。(如果你正在寻找基于内存的、高速的、可弹性扩展的、支持分布式的、对开发者友好的NoSQL,Hazelcast是一个很棒的选择,它的理念是用应用服务的内存换取效率,成本较高)
    1.从com.hazelcast.spring.cache.HazelcastCacheManager这个包名中也能看出,是它自己实现的Spring Cache标准,而不是spring-data帮它实现的(类似MyBatis集成Spring),但它凭借自己的足够优秀,让Spring接受了它
  3. Infinispan:基于Apache 2.0协议的分布式键值存储系统,可以以普通java lib或者独立服务的方式提供服务,支持各种协议(Hot Rod, REST, WebSockets)。支持的高级特性包括:事务、事件通知、高级查询、分布式处理、off-heap及故障迁移。 它按照署模式分为嵌入式(Embedded)模式(基于本地内存)、Client-Server(C\S)模式。
  4. Couchbase:是一个非关系型数据库,它实际上是由couchdb+membase组成,所以它既能像couchdb那样存储json文档(类似MongoDB),也能像membase那样高速存储键值对。(新一代的NoSql数据库,国外挺火的)
  5. Redis:熟悉得不能再熟悉的分布式缓存,只有Client-Server(C\S)模式,单线程让它天生具有线程安全的特性。Java一般使用Jedis/Luttuce来操纵~
  6. Caffeine(咖啡因):Caffeine是使用Java8对Guava缓存的重写版本,一个接近最佳的的缓存库(号称性能最好)。Spring5已经放弃guava,拥抱caffeine,它的API保持了近乎和guava一致,但是性能上碾压它。
    1.guava是谷歌Google Guava工具包的,使用非常广泛。Caffeine长江后浪推前浪,性能上碾压了Guava,是它的替代品。
  7. SIMPLE:。。。。。(嗯~太简单都不想解释了)

进程缓存:Ehcache、Guava、Caffeine对比

首先这仨都作为进程缓存(本地缓存)的优秀开源产品,那么若我们要使用本地缓存来加速访问,选择哪种呢?下文做一个简单的对比:

  1. EhCache:是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate、MyBatis默认的缓存提供。(备注:虽然EhCache3支持到了分布式,但它还是基于Java进程的缓存)

  2. Guava:它是Google Guava工具包中的一个非常方便易用的本地化缓存实现,基于LRU算法实现,支持多种缓存过期策略。它出现得非常早,有点廉颇老矣之感~

  3. Caffeine:是使用Java8对Guava缓存的重写版本,在Spring5中将取代了Guava,支持多种缓存过期策略。

说明:Caffeine它在性能上碾压其余两者,它可以完全的替代Guava,因为API上都差不多一致,并且它还提供了Adapter让Guava过度到Caffeine上来。
Caffeine被称为进程缓存之王

为何Guava被放弃了,但EhCache依旧坚挺?我个人认为主要是它具备了如下特点:

  1. 稳定,健壮
  2. 被认可:apache 2.0 license
  3. 读、写速度还是不错的
  4. 够简单
  5. 够秀珍(jar包很小)
  6. 够轻量(仅仅依赖slf4j这一个包)
  7. 好扩展(可自定义淘汰算法)
  8. 监听器
  9. Ehcache支持缓存数据到硬盘(它也支持内存级别的缓存,Ehcache3还支持了分布式的缓存)

成熟(MyBatis、Hibernate等知名产品都用它作为默认缓存方案)

Caffeine和Spring Cache整合

关于Caffeine的强悍之处,此处就不费笔墨了,总之两个字:优秀。若我们在Spring应用中需要使用Caffeine怎么办呢?当然最直接的使用方式是导入Jar包后,直接使用它的API:CacheManager和Cache等等。

当然,这不是本文要讲述的,本文主要是要让它和Spring集成,从而可以使用Spring Cache注解来直接操作缓存

整合Caffeine,其实Spring已经有个模块对它提供了支持:spring-context-support

<dependency><groupId>org.springframework</groupId><artifactId>spring-context-support</artifactId><version>5.1.6.RELEASE</version>
</dependency>

此包属于spring-context的支持包,一般建议导入。它的内容如下:

需要注意的是,在Spring5之前,此包还默认提供了对Guava的支持,但在Spring5后彻底移除了,这也侧面证明Guava确实该退休了~。

集成第一步:除了导入support包,当然还得导入咖啡因的包:

<dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId><!-- 2019.2最新版本 caffeine是2015年才面市的,发展还是很迅速的--><version>2.7.0</version>
</dependency>

实施之前,先简单看看spring-context-support提供的CaffeineCacheManager实现:

// @since 4.3   Requires Caffeine 2.1 or higher.显然我们都2.7版本 肯定满足呀
public class CaffeineCacheManager implements CacheManager {private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);// 默认能动态生成Cache,对使用者友好private boolean dynamic = true;// 默认使用的builder  可通过setCaffeine来自定这个cacheBuilder // cacheBuilder.build()得到一个com.github.benmanes.caffeine.cache.Cache  让可以自定义N个参数private Caffeine<Object, Object> cacheBuilder = Caffeine.newBuilder();@Nullableprivate CacheLoader<Object, Object> cacheLoader;private boolean allowNullValues = true; // 是否允许null值// 一样的,两个构造函数。你可以指定,也可以让动态生成public CaffeineCacheManager() {}public CaffeineCacheManager(String... cacheNames) {setCacheNames(Arrays.asList(cacheNames));}...@Override@Nullablepublic Cache getCache(String name) {Cache cache = this.cacheMap.get(name);if (cache == null && this.dynamic) {synchronized (this.cacheMap) {cache = this.cacheMap.get(name);if (cache == null) {cache = createCaffeineCache(name);this.cacheMap.put(name, cache);}}}return cache;}// CaffeineCache实现了org.springframework.cache.Cache接口// 内部实现都是委托给com.github.benmanes.caffeine.cache.Cache<Object, Object>来做的protected Cache createCaffeineCache(String name) {return new CaffeineCache(name, createNativeCaffeineCache(name), isAllowNullValues());}...
}

它提供的Cache实现:CaffeineCache。非常简单,所有工作都委托给com.github.benmanes.caffeine.cache.Cache了,因此省略。

第二步:准备CacheConfig 配置文件

@EnableCaching
@Configuration
public class CacheConfig extends CachingConfigurerSupport {@Beanpublic CacheManager cacheManager() {CaffeineCacheManager cacheManager = new CaffeineCacheManager();// 方案一(常用):定制化缓存CachecacheManager.setCaffeine(Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).initialCapacity(100).maximumSize(10_000))// 如果缓存种没有对应的value,通过createExpensiveGraph方法同步加载  buildAsync是异步加载//.build(key -> createExpensiveGraph(key));// 方案二:传入一个CaffeineSpec定制缓存,它的好处是可以把配置方便写在配置文件里//cacheManager.setCaffeineSpec(CaffeineSpec.parse("initialCapacity=50,maximumSize=500,expireAfterWrite=5s"));return cacheManager;}}@Service
public class CacheDemoServiceImpl implements CacheDemoService {@Cacheable(cacheNames = "demoCache", key = "#id")@Overridepublic Object getFromDB(Integer id) {System.out.println("模拟去db查询~~~" + id);return "hello cache...";}
}

运行单测:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class, CacheConfig.class})
public class TestSpringBean {@Autowiredprivate CacheDemoService cacheDemoService;@Autowiredprivate CacheManager cacheManager;@Testpublic void test1() {cacheDemoService.getFromDB(1);cacheDemoService.getFromDB(1);System.out.println("----------验证缓存是否生效----------");Cache cache = cacheManager.getCache("demoCache");System.out.println(cache);System.out.println(cache.get(1, String.class));}}

打印结果:

模拟去db查询~~~1
----------验证缓存是否生效----------
org.springframework.cache.caffeine.CaffeineCache@4f74980d
hello cache...

从结果中可以得出结论:缓存生效。

(关于Caffeine的更多API以及它的高级使用,不是本文讨论的内容)

Ehcache2.x/Ehcache3.x和Spring Cache整合

Ehcache2.x和Ehcache3.x它最大的一个特点是:3.x不向下兼容2.x。从他俩的GAV坐标也能看出这种差异:

<!-- https://mvnrepository.com/artifact/net.sf.ehcache/ehcache -->
<dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache</artifactId><version>2.10.6</version>
</dependency><!-- https://mvnrepository.com/artifact/org.ehcache/ehcache -->
<dependency><groupId>org.ehcache</groupId><artifactId>ehcache</artifactId><version>3.7.1</version>
</dependency>

不仅仅GAV变了,包名也都换了,因此是二进制不兼容的,并且3.x和2.x的API都有非常大的差异。

虽然说2.x也还是维护着(毕竟有非常重的历史包袱),但是活跃度已经远不及3.x了,因此我认为拥抱EhCache3.x是大势所趋

这里有意思的是,spring-context-support即使在Spring5后,默认支持的还是EhCache2.x版本(毕竟有很重的历史包袱在呢),并且没有提供3.x版本的支持,这应该也是为何你看到大多数人还只是在使用EhCache2.x的根本原因吧~

Ehcache2.x集成

Ehcache2.x的集成方案几乎同Caffeine,就不写了。

2.x配置CacheManager的时候,既能全用API方式。当然也能简便的使用ehcache.xml方式,内容形如下:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache><diskStore path="d:/ehcache/"></diskStore><!-- 默认缓存配置 --><defaultCache maxElementsInMemory="10000" eternal="false"timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" /><!-- User缓存配置 --><cache name="User" maxElementsInMemory="10000" eternal="false"timeToIdleSeconds="300" timeToLiveSeconds="600" overflowToDisk="true"/></ehcache>

关于xml配置文件的更多属性和含义,请参考官方文档的说明

Ehcache3.x集成

Ehcache3.x的社区比EhCache2.x活跃很多,所以拥抱和使用3.x版本似乎是必然的。但是奈何Spring并没有提供内置的CacheManager对3.x提供支持,因此此处我总结继承它的两种方案:

  1. 自己实现CacheManager和Cache等相关规范接口
  2. 使用JSR107的JCache(推荐)

上面截图我们能看到support包里是有对jcache(JSR107)的支持,而切好EhCache3.x它实现了JSR107规范(但没有实现Spring-Cache),为了集成它,我们就用现成的方案:jcache+EhCache3.x来实现对Spring的整合。

第一步:先导包

<dependency><groupId>javax.cache</groupId><artifactId>cache-api</artifactId><version>1.1.1</version>
</dependency><dependency><groupId>org.ehcache</groupId><artifactId>ehcache</artifactId><version>3.7.1</version>
</dependency>

先简单看看jcache中JCacheCacheManager的实现:

public class JCacheCacheManager extends AbstractTransactionSupportingCacheManager {// 可见JCacheCacheManager其实就相当于代理,实际做事的是javax.cache.CacheManager@Nullableprivate CacheManager cacheManager;private boolean allowNullValues = true;public JCacheCacheManager() {}public JCacheCacheManager(CacheManager cacheManager) {this.cacheManager = cacheManager;}...@Overridepublic void afterPropertiesSet() {if (getCacheManager() == null) {setCacheManager(Caching.getCachingProvider().getCacheManager());}super.afterPropertiesSet();}// 它使用的是JCacheCache俩把javax.cache.Cache包装起来  类似于适配的效果@Overrideprotected Collection<Cache> loadCaches() {CacheManager cacheManager = getCacheManager();Assert.state(cacheManager != null, "No CacheManager set");Collection<Cache> caches = new LinkedHashSet<>();for (String cacheName : cacheManager.getCacheNames()) {javax.cache.Cache<Object, Object> jcache = cacheManager.getCache(cacheName);caches.add(new JCacheCache(jcache, isAllowNullValues()));}return caches;}@Overrideprotected Cache getMissingCache(String name) {CacheManager cacheManager = getCacheManager();Assert.state(cacheManager != null, "No CacheManager set");// Check the JCache cache again (in case the cache was added at runtime)javax.cache.Cache<Object, Object> jcache = cacheManager.getCache(name);if (jcache != null) {return new JCacheCache(jcache, isAllowNullValues());}return null;}
}

由此可见,实际上JCache就相当于对JSR107做了一层适配,让所有实现了JSR107的缓存方案,都能够用在Spring环境中。

第二步:准备配置(集成)方案,此处给出两种配置方案:

一、使用最容易的JCacheManagerFactoryBean + ehcache.xml的方式:

@EnableCaching
@Configuration
public class CacheConfig extends CachingConfigurerSupport {@Beanpublic JCacheManagerFactoryBean cacheManagerFactoryBean() throws IOException {JCacheManagerFactoryBean factoryBean = new JCacheManagerFactoryBean();// 配置全部写在ehcache.xml这个配置文件内~~~~factoryBean.setCacheManagerUri(new ClassPathResource("ehcache.xml").getURI());return factoryBean;}@Beanpublic CacheManager cacheManager(javax.cache.CacheManager cacheManager) {// 它必须要包装一个javax.cache.CacheManager,也就是Eh107CacheManager才行JCacheCacheManager cacheCacheManager = new JCacheCacheManager();// 方式一:使用`JCacheManagerFactoryBean` + xml配置文件的方式cacheCacheManager.setCacheManager(cacheManager);return cacheCacheManager;
}

ehcache.xml配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'xmlns='http://www.ehcache.org/v3'xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.1.xsd"><!-- <service><jsr107:defaults><jsr107:cache name="demoCache" template="heap-cache"/></jsr107:defaults></service> --><cache-template name="heap-cache"><resources><heap unit="entries">2000</heap><offheap unit="MB">100</offheap></resources></cache-template><!-- 注意:这个cache必须手动配置上,它并不会动态生成 --><cache alias="demoCache" uses-template="heap-cache"><expiry><ttl unit="seconds">40</ttl></expiry></cache></config>

运行上面的单测,结果如下:

模拟去db查询~~~1
----------验证缓存是否生效----------
org.springframework.cache.jcache.JCacheCache@1cc680e
hello cache...

缓存生效(使用的JCacheCache)。

它的基本原理是依赖于Caching.getCachingProvider().getCacheManager()这句代码来生成CacheManager。而EhCache提供了EhcacheCachingProvider实现了CachingProvider接口从而实现了getCacheManager()

使用分布式缓存Redis确实能应对非常多的场景,但是在真正意义上的优化、高速缓存等等都是必须对本地缓存有深入了解的,路远着呢,越来越有趣了

Caffeine Cache和Ehcache3.x 进程缓存性能比较相关推荐

  1. 玩转Spring Cache --- 整合进程缓存之王Caffeine Cache和Ehcache3.x【享学Spring】

    每篇一句 人到中年就是一部西游记:悟空的压力,八戒的身材,沙僧的发型,唐僧的唠叨 前言 前面文章大篇幅详细讲解了Spring Cache缓存抽象.三大缓存注解的工作原理等等.若是细心的小伙伴会发现:讲 ...

  2. 本地缓存—Caffeine Cache

    文章目录 缓存淘汰策略 FIFO 优点 局限性 LRU 优点 局限性 LFU 优点 局限性 W-TinyLFU 维护频率 CountMin Sketch 支持随时间变化的访问模式-分段LRU(SLRU ...

  3. Caffeine Cache~高性能 Java 本地缓存之王

    前面刚说到Guava Cache,他的优点是封装了get,put操作:提供线程安全的缓存操作:提供过期策略:提供回收策略:缓存监控.当缓存的数据超过最大值时,使用LRU算法替换.这一篇我们将要谈到一个 ...

  4. 本地缓存框架:Caffeine Cache

    1. Caffine Cache 在算法上的优点-W-TinyLFU 说到优化,Caffine Cache到底优化了什么呢?我们刚提到过LRU,常见的缓存淘汰算法还有FIFO,LFU: 1.FIFO: ...

  5. HM-SpringCloud微服务系列11.1【多级缓存的意义JVM进程缓存】

    HM-SpringCloud微服务系列11:多级缓存-高级篇 1. 什么是多级缓存 多级缓存是亿级流量的缓存方案 浏览器访问静态资源时,优先读取浏览器本地缓存 访问非静态资源(ajax查询数据)时,访 ...

  6. Caffeine cache实现本地缓存(简单又清楚)

    Caffeine cache实现本地缓存题 缓存填充策略  手动加载  介绍:  使用方式:  同步加载  介绍:  使用方式:  异步加载  介绍:   注意: 异步和同步使用方式相似, 这里的话主 ...

  7. 浅入浅出Caffeine cache

    背景 公司项目中有用到caffeine cache 所以来了解一下. 本地缓存也就是我们适用内存缓存一些热点数据,使应用程序的程序处理的更加的快.以及保护我们的一些有磁盘/网络IO操作的函数/方法,以 ...

  8. Caffeine Cache

    1. 前言 互联网软件神速发展,用户的体验度是判断一个软件好坏的重要原因,所以缓存就是必不可少的一个神器.在多线程高并发场景中往往是离不开cache的,需要根据不同的应用场景来需要选择不同的cache ...

  9. 一头扎进caffeine cache的大坑

    一头扎进caffeine cache的大坑 caffeine号称性能做好的本地cache,最近想实践一下学的东西,写个小demo,就用caffeine作为本地缓存缓存一下用户的token,然后配置大概 ...

最新文章

  1. 吴军:既能得诺贝尔奖,又能生产高科技产品,美国的科研机制是如何运行的?...
  2. python常用内置模块-python常见内置模块collections
  3. Java数据结构与算法——插入排序
  4. linux 定时执行shell脚本 定时任务
  5. do filtering will real delete note in DB
  6. 求一个整数数组的最大元素,递归方法实现
  7. 2019牛客多校第七场E Find the median 权值线段树+离散化
  8. weakhashmap_Java WeakHashMap keySet()方法与示例
  9. 接口测试Fiddler实战
  10. SpringMVC通过工具类获取Request域
  11. Basler相机开发流程
  12. 小妙招:教你如何查询获取企业工商数据
  13. JS逆向-易班登录password参数(RSA加密)
  14. 计算机演示文稿实验报告,演示文稿实验报告
  15. 华农c语言程序设计教程陈湘骥,华农数信学子在第44届国际大学生程序设计竞赛勇夺金牌...
  16. 大学计算机基础实验实施,大学计算机基础实验实施的方案学生用.docx
  17. 多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么
  18. ​嘉楠往事:浮沉八载,如今剑指美股
  19. Linux的一些简单命令操作,好懂易学(1)
  20. 《高级计算机网络》之无线传感网——大连理工大学研究生课程整理笔记(非常详细,通俗易懂)

热门文章

  1. 【数据恢复】【傲梅分区助手】
  2. 离线电影管理软件 极影派
  3. 如何修改PDF文件内容,PDF怎么编辑页眉页脚
  4. 输入等值线参数绘制等值线图python_ArcGIS绘图—空气质量站点数据插值绘制等值线图...
  5. 坐标系旋转与点旋转的变换公式
  6. 谈谈我对云原生与软件供应链安全的思考
  7. C语言 求m~n(m<n)之间所有整数的和
  8. Mac 卸载/安装 maven
  9. php图片背景平铺,css如何让背景图片平铺?css背景图片平铺四种方式介绍
  10. catia中的螺旋伞齿轮画法_用catia画齿轮教程