Spring Boot教程(19) – 缓存入门

缓存是啥?某些重复的操作太耗时,不如把结果存起来,下次需要直接拿出来。

咱说个场景吧,比如你的系统里需要生成报表,然而这个报表需要做各种SQL查询和计算,总计要个10秒才能运行结束,如果每次请求都来个10秒,频繁看数据的老板可是要发飙了。你可以把报表缓存起来,只有第一次生成的时候慢一点,以后生成都可以瞬间完成。

Spring框架可以帮助你快速实现一个简单的缓存,直接看代码:

首先你需要在任意一个@Configuration配置类上加上@EnableCaching以开启缓存的功能。图中间的getReport方法的作用是生成报表,参数类型是LocalDate,意思就是生成这一天的报表。当这个方法加了@Cacheable注解之后,它的意义就变化了。当你调用这个方法的时候,Spring会去缓存里找找这一天的报表有没有生成过,有的话就直接返回,没有的话就运行一遍方法的代码,生成报表对象,放入缓存,返回给调用者(以上行为是通过AOP实现的)。这样你每次调用getReport的时候,只要参数一致,返回的对象就是一致的,在上图中最下面的代码里,我们可以看到,report1report2report3变量引用的都是同一个对象Report@6627@CacheablecacheNames参数表示它与哪个缓存相关联,没有的话会自动生成,你程序里也可能有多个缓存,比如你还可能还需要一个”userCache”用来缓存用户信息等。

对于以上代码,我知道你肯定有两个疑问,@EnableCaching@Cacheable是从哪里来的?以及Report对象是缓存在哪里了?

先说从哪里来吧。Spring框架将缓存相关的东西抽象了一层,文档里叫做 Cache Abstraction,这个抽象层,实际来说还是比较简单的,除了上图的@Cacheable注解之外,Spring缓存还提供了@CacheEvict用来从缓存中删除对象,以及@CachePut来更新缓存,还有@CacheConfig可以放在类上,让类里的缓存相关方法共享一些配置。这些类在Spring的spring-context模块里,只要你引入了Web Starter,spring-context依赖已经自动被引入了。

上图演示了这些注解的基本用法。你可能会好奇,既然@CachePut@Cacheable的方法内容是一样的,干脆注解都放一起得了。其实不行的,@CachePut 对应的方法每次调用都会执行的,而@Cacheable不是,只有缓存里查不到的时候才执行。把这俩注解放到同一方法上,不就冲突了嘛。如果他俩的执行逻辑相同,那就提取出来个公共方法,供他俩分别调用。

抽象层提供了一堆注解来方便你写缓存相关的逻辑,但是没有规定对象存在哪里,我们再说说存储。

Cache Abstraction里抽象出了CacheCacheManager接口,前者代表了单个缓存,可以进行数据的存取操作,后者用来管理Cache对象。市面上有很多的缓存工具,Spring Boot对大多数都做了支持,实现了其对应的CacheCacheManager,比如RedisCaffeineEhCache等等。Spring Boot会查找哪一个被引入了项目,就用哪一个,即自动生成CacheManager类型的Bean,如果你哪个都没引入,它会使用ConcurrentHashMap作为默认的存储实现。在上面的报表的例子里,Report对象正是存在ConcurrentHashMap里的。

使用ConcurrentHashMap作为缓存有许多问题,它虽然速度快,但是并没有自带的过期策略,也不能方便地将数据持久化到磁盘上。怎么办?当然用已经造好的轮子啦。轮子有:Ehcache、Caffeine、Guava CacheInfinispan等。Ehcache宣称自己是Java生态使用最广泛的,实现了JSR107 JCACHE API。Caffeine自己做的Benchmarks表明它的性能是最强的,Guava Cache应用得也比较广泛,但是好像因为性能问题现在已经被Caffeine取代了,而且Spring Boot也没有提供对Guava Cache的默认支持。Spring Boot默认支持Infinispan,不过只支持嵌入模式,用它的人不多,就不多说了。

上面说的,都是跟应用程序跑在一个进程中的场景,如果应用部署在多个服务器上,缓存数据共享起来不太方便。这个时候可以使用Redis或者MemcachedSpring Data Redis对Redis单独做了详尽的支持,在Spring Boot项目中,引入其对应的Starter就行。很奇怪Memcached没有默认被支持,不过你可以使用第三方的Starter来使用它。

如果单机Redis不够你用的,比如你的数据量比较大、请求比较多或者需要动态扩容的时候,你可能需要建立Redis集群。如果你感觉Redis的网络开销有点大,还可以配合应用内部缓存配合一起使用,比如Ehcache和Caffeine。如果本文所介绍的所有方式都不够用,那你们公司已经很赚钱了吧,还招人不,半年经验这种?

还有一些Spring支持的缓存工具,比如HazelcastCouchbase,因为实在没有精力去了解这么多,就先不管了。本来我是打算将本文提到的所有的缓存工具的用法都写一遍的,最后发现越写越长,没完没了,还是算了,以后用到了哪个再写吧。毕竟我做的微型项目,都还没有使用缓存的必要。如果将来需要用缓存的话,我会优先使用Caffeine以及Redis。

发表评论

电子邮件地址不会被公开。