一、缓存简介
我们知道一个系统的瓶颈通常在与数据库交互的过程中。内存的速度远远快于硬盘速度。所以,当我们需要重复地获取相同的数据的时候,我们一次又一次的请求数据库或者远程服务,这无疑是性能上的浪费——会导致大量的时间耗费在数据库查询或者远程方法调用上(这些资源简直太奢侈了),导致程序性能的恶化——于是有了“缓存”。缓存(Cache)就是数据交换的缓冲区。
随着时间的积累,应用的使用用户不断增加,数据规模也越来越大,往往数据库查询操作会成为影响用户使用体验的瓶颈,此时使用缓存往往是解决这一问题非常好的手段之一。Spring 3开始提供了强大的基于注解的缓存支持,可以通过注解配置方式低侵入的给原有Spring应用增加缓存功能,提高数据访问性能。
在Spring Boot中对于缓存的支持,提供了一系列的自动化配置,使我们可以非常方便的使用缓存。
二、JSR107
Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry和 Expiry。
- CachingProvider定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider。
- CacheManager定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache 存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。
- Cache是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个 CacheManager所拥有。
- Entry是一个存储在Cache中的key-value对。
- Expiry 每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期 的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。
三、Spring缓存抽象
Spring从3.1开始定义了org.springframework.cache.Cache
和org.springframework.cache.CacheManager
接口来统一不同的缓存技术; 并支持使用JCache(JSR-107)注解简化我们开发;
- Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
- Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache ,ConcurrentMapCache等;
- 每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否 已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法 并缓存结果后返回给用户。下次调用直接从缓存中获取。
- 使用Spring缓存抽象时我们需要关注以下两点;
1、确定方法需要被缓存以及他们的缓存策略
2、从缓存中读取之前缓存存储的数据
四、Spring Boot缓存的使用
1、引入spring-boot-starter-cache模块
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2、@EnableCaching开启缓存
@SpringBootApplication
@EnableCaching
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3、使用缓存注解
@Cacheable(value = Cache.CONSTANT, key = "'" + CacheKey.ROLES_NAME + "'+#roleIds")
public String getRoleName(String roleIds) {
五、Cache注解详解
下面结合例子看一下:
@CacheConfig(cacheNames = "users")
public interface UserRepository extends JpaRepository<User, Long> {
@Cacheable
User findByName(String name);
}
@CacheConfig:主要用于配置该类中会用到的一些共用的缓存配置。在这里**@CacheConfig(cacheNames = “users”)**:配置了该数据访问对象中返回的内容将存储于名为users的缓存对象中,我们也可以不使用该注解,直接通过@Cacheable自己配置缓存集的名字来定义。
@Cacheable:配置了findByName函数的返回值将被加入缓存。同时在查询时,会先从缓存中获取,若不存在才再发起对数据库的访问。该注解主要有下面几个参数:
- value、cacheNames:两个等同的参数(cacheNames为Spring 4新增,作为value的别名),用于指定缓存存储的集合名。由于Spring 4中新增了@CacheConfig,因此在Spring 3中原本必须有的value属性,也成为非必需项了。
- key:缓存对象存储在Map集合中的key值,非必需,缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式,比如:@Cacheable(key = “#p0”):使用函数第一个参数作为缓存的key值,更多关于SpEL表达式的详细内容可参考官方文档。
- condition:缓存对象的条件,非必需,也需使用SpEL表达式,只有满足表达式条件的内容才会被缓存,比如:@Cacheable(key = “#p0”, condition = “#p0.length() < 3”),表示只有当第一个参数的长度小于3的时候才会被缓存,若做此配置上面的AAA用户就不会被缓存,读者可自行实验尝试。
- unless:另外一个缓存条件参数,非必需,需使用SpEL表达式。它不同于condition参数的地方在于它的判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对result进行判断。
- keyGenerator:用于指定key生成器,非必需。若需要指定一个自定义的key生成器,我们需要去实现org.springframework.cache.interceptor.KeyGenerator接口,并使用该参数来指定。需要注意的是:该参数与key是互斥的。
- cacheManager:用于指定使用哪个缓存管理器,非必需。只有当有多个时才需要使用。
- cacheResolver:用于指定使用那个缓存解析器,非必需。需通过org.springframework.cache.interceptor.CacheResolver接口来实现自己的缓存解析器,并用该参数指定。
除了这里用到的两个注解之外,还有下面几个核心注解:
@CachePut:配置于函数上,能够根据参数定义条件来进行缓存,它与@Cacheable不同的是,它每次都会真是调用函数,所以主要用于数据新增和修改操作上。它的参数与@Cacheable类似,具体功能可参考上面对@Cacheable参数的解析。
@CacheEvict:配置于函数上,通常用在删除方法上,用来从缓存中移除相应数据。除了同@Cacheable一样的参数之外,它还有下面两个参数:
- allEntries:非必需,默认为false。当为true时,会移除所有数据。
- beforeInvocation:非必需,默认为false,会在调用方法之后移除数据。当为true时,会在调用方法之前移除数据。
六、切换为其他缓存
下面以常用的EhCache为例,看看如何配置来使用EhCache进行缓存管理。
在Spring Boot中开启EhCache非常简单,只需要在工程中加入ehcache.xml
配置文件并在pom.xml
中增加ehcache依赖,框架只要发现该文件,就会创建EhCache的缓存管理器。
在src/main/resources目录下创建:ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd"
updateCheck="false" monitoring="autodetect"
dynamicConfig="true">
<diskStore path="java.io.tmpdir/ehcache"/>
<defaultCache
maxElementsInMemory="50000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="3600"
overflowToDisk="true"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
<!-- 全局变量:永不过期-->
<cache name="CONSTANT"
maxElementsInMemory="50000"
eternal="true"
clearOnFlush="false"
overflowToDisk="true"
diskSpoolBufferSizeMB="1024"
maxElementsOnDisk="100000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LFU"
transactionalMode="off">
</cache>
</ehcache>
<!--
maxElementsInMemory="10000" //Cache中最多允许保存的数据对象的数量
external="false" //缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期
timeToLiveSeconds="3600" //缓存的存活时间,从开始创建的时间算起
timeToIdleSeconds="3600" //多长时间不访问该缓存,那么ehcache 就会清除该缓存
这两个参数很容易误解,看文档根本没用,我仔细分析了ehcache的代码。结论如下:
1、timeToLiveSeconds的定义是:以创建时间为基准开始计算的超时时长;
2、timeToIdleSeconds的定义是:在创建时间和最近访问时间中取出离现在最近的时间作为基准计算的超时时长;
3、如果仅设置了timeToLiveSeconds,则该对象的超时时间=创建时间+timeToLiveSeconds,假设为A;
4、如果没设置timeToLiveSeconds,则该对象的超时时间=min(创建时间,最近访问时间)+timeToIdleSeconds,假设为B;
5、如果两者都设置了,则取出A、B最少的值,即min(A,B),表示只要有一个超时成立即算超时。
overflowToDisk="true" //内存不足时,是否启用磁盘缓存
diskSpoolBufferSizeMB //设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区
maxElementsOnDisk //硬盘最大缓存个数
diskPersistent //是否缓存虚拟机重启期数据The default value is false
diskExpiryThreadIntervalSeconds //磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy="LRU" //当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush //内存数量最大时是否清除
maxEntriesLocalHeap="0" //堆内存中最大缓存对象数,0没有限制
maxEntriesLocalDisk="1000" //硬盘最大缓存个数。
-->
在pom.xml中加入
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.6.1</version>
</dependency>
编写ehcache配置类
@Configuration
@EnableCaching
public class EhCacheConfig {
/**
* EhCache的配置
*/
@Bean
public EhCacheCacheManager cacheManager(CacheManager cacheManager) {
return new EhCacheCacheManager(cacheManager);
}
/**
* EhCache的配置
*/
@Bean
public EhCacheManagerFactoryBean ehcache() {
EhCacheManagerFactoryBean ehCacheManagerFactoryBean = new EhCacheManagerFactoryBean();
ehCacheManagerFactoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
return ehCacheManagerFactoryBean;
}
}
完成上面的配置之后,EhCache开启成功。