缓存是啥?某些重复的操作太耗时,不如把结果存起来,下次需要直接拿出来。
咱说个场景吧,比如你的系统里需要生成报表,然而这个报表需要做各种SQL查询和计算,总计要个10秒才能运行结束,如果每次请求都来个10秒,频繁看数据的老板可是要发飙了。你可以把报表缓存起来,只有第一次生成的时候慢一点,以后生成都可以瞬间完成。
Spring框架 可以帮助你快速实现一个简单的缓存,直接看代码:
首先你需要在任意一个 @Configuration
配置类上加上 @EnableCaching
以开启缓存的功能。图中间的 getReport
方法的作用是生成报表,参数类型是 LocalDate
,意思就是生成这一天的报表。当这个方法加了 @Cacheable
注解之后,它的意义就变化了。当你调用这个方法的时候,Spring会去缓存里找找这一天的报表有没有生成过,有的话就直接返回,没有的话就运行一遍方法的代码,生成报表对象,放入缓存,返回给调用者(以上行为是通过AOP实现的)。这样你每次调用 getReport
的时候,只要参数一致,返回的对象就是一致的,在上图中最下面的代码里,我们可以看到, report1
, report2
和 report3
变量引用的都是同一个对象 Report@6627
。 @Cacheable
的 cacheNames
参数表示它与哪个缓存相关联,没有的话会自动生成,你程序里也可能有多个缓存,比如你还可能还需要一个”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里抽象出了 Cache
和 CacheManager
接口,前者代表了单个缓存,可以进行数据的存取操作,后者用来管理 Cache
对象。市面上有很多的缓存工具,Spring Boot对大多数都做了支持,实现了其对应的 Cache
和 CacheManager
,比如 Redis , Caffeine 和 EhCache 等等。Spring Boot会查找哪一个被引入了项目,就用哪一个,即自动生成 CacheManager
类型的Bean,如果你哪个都没引入,它会使用 ConcurrentHashMap
作为默认的存储实现。在上面的报表的例子里, Report
对象正是存在 ConcurrentHashMap
里的。
使用 ConcurrentHashMap
作为缓存有许多问题,它虽然速度快,但是并没有自带的过期策略,也不能方便地将数据持久化到磁盘上。怎么办?当然用已经造好的轮子啦。轮子有:Ehcache、Caffeine、 Guava Cache 、 Infinispan 等。Ehcache宣称自己是Java生态使用最广泛的,实现了 JSR107 JCACHE API 。Caffeine 自己做的Benchmarks 表明它的性能是最强的,Guava Cache应用得也比较广泛,但是好像 因为性能问题 现在已经被Caffeine取代了,而且Spring Boot也没有提供对Guava Cache的默认支持。Spring Boot默认支持Infinispan,不过只支持嵌入模式,用它的人不多,就不多说了。
上面说的,都是跟应用程序跑在一个进程中的场景,如果应用部署在多个服务器上,缓存数据共享起来不太方便。这个时候可以使用Redis或者 Memcached , Spring Data Redis 对Redis单独做了详尽的支持,在Spring Boot项目中,引入其对应的Starter就行。很奇怪Memcached没有默认被支持,不过你可以使用第三方的Starter来使用它。
如果单机Redis不够你用的,比如你的数据量比较大、请求比较多或者需要动态扩容的时候,你可能需要建立Redis集群。如果你感觉Redis的网络开销有点大,还可以配合应用内部缓存配合一起使用,比如Ehcache和Caffeine。如果本文所介绍的所有方式都不够用,那你们公司已经很赚钱了吧,还招人不,半年经验这种?
还有一些Spring支持的缓存工具,比如 Hazelcast 和 Couchbase ,因为实在没有精力去了解这么多,就先不管了。本来我是打算将本文提到的所有的缓存工具的用法都写一遍的,最后发现越写越长,没完没了,还是算了,以后用到了哪个再写吧。毕竟我做的微型项目,都还没有使用缓存的必要。如果将来需要用缓存的话,我会优先使用Caffeine以及Redis。