在上一集中,我们使用了默认的ConcurrentMapCacheManager作为Spring Cache的默认实现。在小型的应用中,这已经足够了,但在实际的项目中,我们可能需要引入JCache,Redis,MemCache等更加成熟的缓存技术.下面我们就来看看吧.
JCache
JCache通常也叫JSR-107,JCache是一套规范.
JCACHE规范承诺为Java提供一套标准API,通过这套API,编程人员可以透明地操作数据,不用关心数据放在哪里.
我们来看看,它的初衷和Spring Cache的目的是一样的,都是一套规范.
同时,JCache提供了一套类似于Spring Cache的annotation来标注方法和类.包括@CacheResult,@CachePut,@CacheRemove等,这些注解都位于javax.cache.annotation包下.
在使用Spring Cache的时候,我们也可以使用JCache的注解,Spring 能正确的根据注解实现相应的缓存逻辑.
JCache有各种实现,比较典型的是EhCache3,Hazelcast等,Spring Cache可以直接使用JCache的各种实现来作为自己的Cache实现. 下面我们以Spring Boot使用EhCache为例。改造上一次的代码
首先,导入必要依赖
org.springframework.boot
spring-boot-starter-cache
javax.cache
cache-api
org.ehcache
ehcache
这里的cache-api就是JSR-107所定义的相关的Cache的接口.
ehcache就是ehcache3.
启用Spring Cache
@SpringBootApplication
@EnableCaching
public class EhCacheApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(EhCacheApplication.class, args);
}
}
我们所有的代码没有变化,在controller中加入一个接口,用来获取系统中实际使用的cacheManager
/**
* 注入Spring Boot生成的CacheManager
*/
@Autowired
private CacheManager cacheManager;
/**
* 获取并显示实际使用的CacheManager
*/
@GetMapping("manager")
public String getManager() {
return cacheManager.toString();
}
在浏览器中输入相关URL,可以获得结果
org.springframework.cache.jcache.JCacheCacheManager@xxxx
我们可以注释掉相关的依赖,即pom.xml中的cache-api和ehcache两项,再运行相关的接口,接口会返回相应的数据
org.springframework.cache.concurrent.ConcurrentMapCacheManager@xxxx.
Spring Boot会按照如下优先级来自动装配一个CacheManager
Generic
JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, etc)
EhCache 2.x
Hazelcast
Infinispan
Couchbase
Redis
Caffeine
Guava (deprecated)
Simple
参见Spring Boot文档.
当Spring Boot按照以上顺序检测到某一个缓存实现存在的话,会自动构建一个使用相应实现的CacheManager.并跳过后续检测。
配置JCache的配置文件
我们使用ehcache3作为缓存实现的时候,可以对ehcache进行配置,以实现缓存过期等策略。具体的包括
- 简单配置
我们的代码,到目前为止,当我们启用了ehcache3,并进行了相关的接口调用的时候,会抛出异常java.lang.IllegalArgumentException,Cannot find cache named 'test' for Builder.这是因为ehcahce需要我们显示的声明Cache.
我们可以在Spring Boot的配置文件 src\main\resources\application.yml中声明Cache
spring:
cache:
cache-names:
- test
cache-names是一个列表,声明了在项目中用到的cache的名称。
- 完整的Ehcache配置
在yml/properties文件中,我们只能进行一些简单的配置。如果要进行复杂的配置,我们需要指定一个配置文件所在的路径。
修改后的yml如下
spring:
cache:
jcache:
config: classpath:cache.xml
并且,我们在resources目录下面建立cache.xml文件。进行cache的配置
1
10
在这里,我们通过alias值定了cache的名称,通过ttl指定了cache过期时间,这里指定为10秒。我们重新访问接口时,当距离上次缓存的时间大于10秒钟的时候,都会重新执行一次方法。
更多ehcache的配置文件,可以参考Ehcache官方网站,不在这里展开。
- 缓存并发穿透
并发穿透,指的是当缓存过期失效后。如果瞬间有大量的请求进来,这些请求在执行的时候,均会查询缓存,这些查询都会导致缓存没有命中,进而执行实际的代码。如果这些代码是持久层的操作,或者是比较耗时的操作的,会导致计算压力瞬间倍增。
通常要解决这些场景的问题,需要一些线程同步的能力,但在Spring Cache 4.3以后的版本中,这种情况大为改善。我们只需要在Cacheable的注解中,指定sync=true即可。示例代码如下。
@Override
@Cacheable(cacheNames = "test", sync = true)
public String get(String id) {
// 记录数据产生的时间,用于测试对比
long time = new Date().getTime();
// 打印使用到的cacheManager
logger.info("The cacheManager is" + cacheManager);
// 当数据不是从cache里面获取时,打印日志
logger.info("Get value by id=" + id + ", The time is " + time);
return "Get value by id=" + id + ",the value is " + enties.get(id);
}
当缓存失效之后,有请求并发访问到这里的时,只会有一个线程实际执行方法体,其它的请求等待之前的线程执行并缓存结果。这大大简化了并发的处理逻辑。
Redis
redis是一个常用的集中式缓存服务。Spring对Redis也进行了集成,我们可以方法的使用RedisTemplate进行Redis的读写操作。当然,我们也可以非常方便的将Spring的缓存实现更改为Redis实现,你只需要加入Redis相关的依赖。
org.springframework.boot
spring-boot-starter-data-redis
然后在配置文件中配置相关的redis连接信息。以上的代码不用更改,即可实现使用Redis作为缓存的实现。
指定缓存类型
当项目中同时存在多种技术时,Spring会按照一定的顺序去寻找缓存的实现。但有时候我们需要显示的指定缓存的实现,比如当比如Jcache和redis共存时,Spring会使用JCache作为缓存的实现,而事实上我们可能需要的是Redis.这时,就需要我们手工显示的指定实现。在yml/properties文件中指定即可
spring:
cache:
type: redis
可供选择的类型在org.springframework.boot.autoconfigure.cache.CacheType枚举中。
点击这里下载相关代码
小结:缓存在互联网时代是非常重要的技术,也不是一两篇文章就能讲完的,大家一起研究,一起学习。
距离上一篇已经过去了一个月了,码字的速度好慢。
过年了,新年快乐。