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

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 玩转Spring Cache --- 整合进程缓存之王Caffeine Cache和Ehcache3.x【享学Spring】(上)

前言


前面文章大篇幅详细讲解了Spring Cache缓存抽象、三大缓存注解的工作原理等等。若是细心的小伙伴会发现:讲解时的Demo我使用的缓存实现方案均是Spring默认提供的:ConcurrentMapCache。使用它的原因是它是spring-context内置的,无需额外导包就能使用,非常的方便~


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


  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.JCacheCacheManager
  EHCACHE, // 使用org.springframework.cache.ehcache.EhCacheCacheManager
  HAZELCAST, // 使用com.hazelcast.spring.cache.HazelcastCacheManager
  INFINISPAN, // 使用org.infinispan.spring.provider.SpringEmbeddedCacheManager
  COUCHBASE, // 使用com.couchbase.client.spring.cache.CouchbaseCacheManager
  REDIS, // 使用org.springframework.data.redis.cache.RedisCacheManager,依赖于RedisTemplate进行操作
  CAFFEINE, // 使用org.springframework.cache.caffeine.CaffeineCacheManager
  @Deprecated
  GUAVA, // 使用org.springframework.cache.guava.GuavaCacheManager,已经过期不推荐使用了
  SIMPLE, // 使用ConcurrentMapCacheManager
  NONE; // 使用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还支持了分布式的缓存)
  10. 成熟(MyBatis、Hibernate等知名产品都用它作为默认缓存方案)


本文讲解的是Spring Cache和`进程缓存Caffeine和EhCache的整合。


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的支持包,一般建议导入。它的内容如下:


image.png

需要注意的是,在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();
  @Nullable
  private CacheLoader<Object, Object> cacheLoader;
  private boolean allowNullValues = true; // 是否允许null值
  // 一样的,两个构造函数。你可以指定,也可以让动态生成
  public CaffeineCacheManager() {
  }
  public CaffeineCacheManager(String... cacheNames) {
    setCacheNames(Arrays.asList(cacheNames));
  }
  ...
  @Override
  @Nullable
  public 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 {
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        // 方案一(常用):定制化缓存Cache
        cacheManager.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")
    @Override
    public Object getFromDB(Integer id) {
        System.out.println("模拟去db查询~~~" + id);
        return "hello cache...";
    }
}


运行单测:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class, CacheConfig.class})
public class TestSpringBean {
    @Autowired
    private CacheDemoService cacheDemoService;
    @Autowired
    private CacheManager cacheManager;
    @Test
    public 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以及它的高级使用,不是本文讨论的内容,有兴趣的小伙伴可以自行学习和研究~



相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
2月前
|
缓存 算法 Java
Caffeine Cache~高性能 Java 本地缓存之王
Caffeine Cache~高性能 Java 本地缓存之王
65 1
|
2月前
|
XML 存储 缓存
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache管理器的实战开发指南(修正篇)
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache管理器的实战开发指南(修正篇)
29 0
|
2月前
|
存储 XML 缓存
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南(一)
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南
79 0
|
1天前
|
XML 存储 缓存
Spring缓存是如何实现的?如何扩展使其支持过期删除功能?
总之,Spring的缓存抽象提供了一种方便的方式来实现缓存功能,并且可以与各种缓存提供商集成以支持不同的过期策略。您可以根据项目的具体需求选择适合的方式来配置和扩展Spring缓存功能。
5 0
|
2天前
|
存储 缓存 Java
【Spring系列笔记】依赖注入,循环依赖以及三级缓存
依赖注入: 是指通过外部配置,将依赖关系注入到对象中。依赖注入有四种主要方式:构造器注入、setter方法注入、接口注入以及注解注入。其中注解注入在开发中最为常见,因为其使用便捷以及可维护性强;构造器注入为官方推荐,可注入不可变对象以及解决循环依赖问题。本文基于依赖注入方式引出循环依赖以及三层缓存的底层原理,以及代码的实现方式。
13 0
|
17天前
|
缓存 NoSQL Java
缓存框架-Spring Cache的使用
Spring Cache是一个注解驱动的缓存框架,它提供了一层抽象,允许切换不同的缓存实现,如EHCache、Caffeine和Redis。启用缓存只需在配置中引入相关依赖并开启`@EnableCaching`。`@Cacheable`用于方法执行前检查缓存,存在则直接返回,不存在则执行方法并将结果存入缓存。`@CachePut`在方法执行后将结果放入缓存,常用于更新操作。`@CacheEvict`用于清除缓存数据,可以按key删除或清空整个缓存。`@Caching`可以组合多个缓存操作。在Redis中,可以通过序列化处理存储复杂对象,提高可读性。
57 4
|
19天前
|
存储 缓存 Java
探秘MyBatis缓存原理:Cache接口与实现类源码分析
探秘MyBatis缓存原理:Cache接口与实现类源码分析
35 2
探秘MyBatis缓存原理:Cache接口与实现类源码分析
|
2月前
|
缓存 NoSQL Java
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南(二)
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南
34 0
|
7天前
|
缓存 NoSQL 安全
【Redis系列笔记】缓存三剑客
缓存穿透是指请求一个不存在的数据,缓存层和数据库层都没有这个数据,这种请求会穿透缓存直接到数据库进行查询。它通常发生在一些恶意用户可能故意发起不存在的请求,试图让系统陷入这种情况,以耗尽数据库连接资源或者造成性能问题。 缓存击穿发生在访问热点数据,大量请求访问同一个热点数据,当热点数据失效后同时去请求数据库,瞬间耗尽数据库资源,导致数据库无法使用。 缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。
32 1
|
5天前
|
存储 缓存 NoSQL
Redis多级缓存指南:从前端到后端全方位优化!
本文探讨了现代互联网应用中,多级缓存的重要性,特别是Redis在缓存中间件的角色。多级缓存能提升数据访问速度、系统稳定性和可扩展性,减少数据库压力,并允许灵活的缓存策略。浏览器本地内存缓存和磁盘缓存分别优化了短期数据和静态资源的存储,而服务端本地内存缓存和网络内存缓存(如Redis)则提供了高速访问和分布式系统的解决方案。服务器本地磁盘缓存因I/O性能瓶颈和复杂管理而不推荐用于缓存,强调了内存和网络缓存的优越性。
24 1