一、为什么使用缓存
一个应用主要瓶颈在于数据库的IO,大家都知道内存的速度是远远快于硬盘的速度(即使固态硬盘与内存也无法比拟)。
应用之中经常会遇到某些数据变化的可能性很小。假如我们使用传统的方式每次都通过接口与数据库打交道去请求获得,每次既消耗了内存资源、网络资源、数据库资源、CPU资源,又导致大量的时间耗费在数据库查询,及远程方法调用上,从而导致程序性能的恶化。
这种场景就是需要使用缓存来解决这类问题。我们把数据缓存在内存之中,以后每次获取直接内存之中获得;使得程序获得极大的性能提升。
1、SpringCache介绍
SpringCache相当于一个抽象接口,在其底层可以切换各种Cache的实现,当缓存数据时,只需要注入SpringCache对应的注解,即可实现缓存的功能,大大的提高了缓存的效率,这就是Spring自带缓存SpringCache的使用原理
Springcache默认的缓存实现是simple(内存级),当然可以更换成其他实现,常见的有如下
EhCache:此缓存框架一直伴随着Spring,Hibernate,Mybatis等等。在SpringBoot出来之前都已经广泛的使用来做为一级缓存
Guava: Google出品的框架,也支持缓存
Redis:在我们日常开发之中经常使用的;并且被大众广泛接受的速度极快的缓存
memcached:是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。
Spring缓存的接口:
org.springframework.cache.Cache ;
org.springframework.cache.CacheManager
这两个接口都在context中,一个是用来提供缓存,一个是用来提供管理缓存。
CacheManager是Spring提供的各种缓存技术抽象接口,
Cache接口包含缓存的各种操作(增加、删除、获得缓存,我们一般不会直接和此接口打交道)。
Spring支持的CacheManager实现如下图:
使用缓存需要引入的依赖是
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>
2、注解
2.1、配置注解
EnableCaching
标注于SpringBoot应用启动类上,添加此注解表示开启Spring Cache缓存;移除表示关闭缓存。如果在全局配置文件中添加如下配置,即使在启动类上标注EnableCaching注解,Spring Cache缓存然后是关闭状态。
spring: cache: type: none
如果应用中自定义独立于Spring容器的缓存,则不受此配置影响。
CacheConfig
标注于类上,更具体的说是标注于业务服务类上。统一配置如下参数信息:
参数 | 含义 | 使用说明 |
cacheManager |
缓存管理器 | 缺省指首要的CacheManager |
cacheNames |
缓存名 | |
keyGenerator |
key值生成器 |
在类上统一进行配置,类下的方法自动继承相应的配置。
2.2、缓存注解
Cacheable
添加缓存的核心注解,分两种情况:一是对应key值未有缓存数据,先执行方法,然后根据condition和unless条件决定是否添加缓存;二是对应key值已有缓存,不执行方法体,直接返回数据。
/** *value:自定义缓存空间名 *key:缓存的key */ @Cacheable(value = "cacheSpace",key ="#id" ) public List list(String id){ return Arrays.asList("1","2","3"); }
基础参数
参数 | 含义 | 使用说明 |
cacheManager |
缓存管理器 | 缺省指首要的CacheManager |
cacheNames |
缓存名 | |
keyGenerator |
key值生成器 | |
key |
key值 |
参数keyGenerator
与key
是互斥的,当key
存在时keyGenerator
配置自动失效。
高级参数
参数 | 含义 | 默认值 | 使用说明 |
condition |
缓存条件 | 指示满足条件方执行缓存操作,一般使用参数作为条件 | |
unless |
否定缓存 | 当条件为 true ,方法的返回值不会被缓存 | |
sync |
同步状态 | false | 表示将方法执行结果以何种方式存入缓存 |
CachePut
更新缓存注解。不管对应key值是否有缓存数据,都执行。
基础参数
参数 | 含义 | 使用说明 |
cacheManager |
缓存管理器 | 缺省指首要的CacheManager |
cacheNames |
缓存名 | |
keyGenerator |
key值生成器 | |
key |
key值 |
高级参数
参数 | 含义 | 使用说明 |
condition |
缓存条件 | 指示满足条件方执行缓存操作,一般使用参数作为条件 |
unless |
否定缓存 | 当条件为 true ,方法的返回值不会被缓存 |
CacheEvict
主动清除缓存注解。
基础参数
参数 | 含义 | 使用说明 |
cacheManager |
缓存管理器 | 缺省指首要的CacheManager |
cacheNames |
缓存名 | |
keyGenerator |
key值生成器 | |
key |
key值 |
高级参数
参数 | 含义 | 默认值 | 使用说明 |
condition |
缓存条件 | 指示满足条件方执行缓存操作,一般使用参数作为条件 | |
allEntries |
所有缓存 | false | 表示是否清空当前CacheName对应的所有缓存 |
beforeInvocation |
调用前 | false | 表示是否在方法调用前清空缓存 |
3、实现方式
默认cache接口的实现simple(内存级),直接使用上面的注解即可,没有什么需要配置的,可以自己选择合适的缓存,
3.1、Ehcache作为缓存
可以在spring-boot-dependencies发现有Echcache依赖,所以我们只需引入依赖,不需要版本
需要导入的依赖
<dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> </dependency>
配置
spring: cache: type: EHCACHE
还需要一个Ehcache的配置文件ehcache.xml
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false"> <!-- diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下: user.home – 用户主目录 user.dir – 用户当前工作目录 java.io.tmpdir – 默认临时文件路径 --> <diskStore path="java.io.tmpdir/Tmp_EhCache"/> <!-- defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。 --> <!-- name:缓存名称。 maxElementsInMemory:缓存最大数目 maxElementsOnDisk:硬盘最大缓存个数。 eternal:对象是否永久有效,一但设置了,timeout将不起作用。 overflowToDisk:是否保存到磁盘,当系统当机时 timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。 timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。 diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。 diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。 clearOnFlush:内存数量最大时是否清除。 memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。 FIFO,first in first out,这个是大家最熟的,先进先出。 LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。 LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。 --> <defaultCache eternal="false" maxElementsInMemory="10000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="259200" memoryStoreEvictionPolicy="LRU"/> <cache name="cloud_user" eternal="false" maxElementsInMemory="5000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="1800" memoryStoreEvictionPolicy="LRU"/> </ehcache>
使用方式还是用上面的注解
3.2、redis作为缓存
引入依赖
<!--springcache依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!--使用redis作为缓存--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
注意:这里是把redis作为cache实现进行整合,即springcache整合redis,并不是springboot整合redis
配置
spring: cache: type: EHCACHE redis: #是否缓存空值 cache-null-values: true #是否使用前缀 use-key-prefix: true #前缀 key-prefix: test #缓存时间 time-to-live: 10s redis: host: localhost port: 6379
使用方式还是用上面的注解
3.3、memcached作为缓存
下载安装memcached
地址Windows 下安装 Memcached | 菜鸟教程
下载后解压,cmd执行exe文件
memcached.exe -d install
安装如果出现failed to install servise or service is already installed,此时使用管理员身份打开cmd执行上面安装命令即可
然后启动和停止服务
memcached.exe -d start
memcached.exe -d stop
boot对memcached没有提供相应的整合,需要使用编码方式实现客户端管理,memcached客户端有一下三个
memcached client for java:最早期客户端 稳定可靠 用户群广(类似redis中的jedis)
SpyMemcached :效率更高
Xmemcached:并发处理更好
这里演示三个最好的Xmemcached使用
<dependency> <groupId>com.googlecode.xmemcached</groupId> <artifactId>xmemcached</artifactId> <version>2.4.7</version> </dependency>
配置bean
@Configuration public class XMemcachedConfig { @Bean public MemcachedClient memcachedClient() throws IOException { XMemcachedClientBuilder xMemcachedClientBuilder = new XMemcachedClientBuilder("localhost:11211"); //自己根据需要设置其他参数 xMemcachedClientBuilder.setConnectionPoolSize(20); MemcachedClient memcachedClient = xMemcachedClientBuilder.build(); return memcachedClient; } }
测试
@RequestMapping("mSet") @ResponseBody public String mSet() throws InterruptedException, TimeoutException, MemcachedException { memcachedClient.set("token",0,Arrays.asList("1","2","3")); return "ok"; } @RequestMapping("mGet") @ResponseBody public String mGet() throws InterruptedException, TimeoutException, MemcachedException { return memcachedClient.get("token").toString(); }
详情教程
3.4、jetcache作为缓存
上面几种缓存技术有以下几点特点
1、配置方式多样性,有xml中配置的、有yml配置文件的、有代码中写的,比较散乱
2、缓存有本地方案和远程方案(redis)
阿里提供的jetcache设定了本地缓存和远程缓存的多级缓存解决方案,用来替代spring-cache的
- 本地缓存
LinkedHashMap
Caffeine
- 远程缓存
Redis(本文选择远程方案)
Tair
使用jetcache之后就需要引入spring-boot-starter-cache,加入jetcache依赖
<dependency> <groupId>com.alicp.jetcache</groupId> <artifactId>jetcache-starter-redis</artifactId> <version>2.6.2</version> </dependency>
注意:boot整合jetcache只需引入上面依赖即可,如果不是boot整合,需要引入的是
jetcache anno和jetcache core两个依赖
jetcache远程&本地缓存方案
配置
jetcache: #area是否进入拼接到缓存的key areaInCacheName: false #远程配置 默认default即可,远程方案使用redis ,其中poolConfig必须配置 remote: default: type: redis host: localhost port: 6379 keyConvertor: fastjson valueEncode: java valueDecode: java poolConfig: maxTotal: 50 minIdle: 5 maxIdle: 20 test: type: redis host: localhost port: 6379 keyConvertor: fastjson valueEncode: java valueDecode: java poolConfig: maxTotal: 50 minIdle: 5 maxIdle: 20 #本地缓存配置 local: default: type: linkedhashmap keyConvertor: fastjson valueEncode: java valueDecode: java
启动类开启缓存
@SpringBootApplication //jetache缓存开启 @EnableCreateCacheAnnotation public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } }
/** *定义缓存对象 * area:和配置文件配置的要对应,如果配置文件remote只有default 这个area可以省略 * name:空间配置 最好使用下划线或者其他符号和缓存的key隔开 * expire:过期时间 默认单位秒 * timuit:可选配置,默认单位秒 * cacheType:当前缓存类型,只在本地缓存还是远程, * 取值CacheType.BOTH,CacheType.REMOTE CacheType.LOCAL三个取值 默认REMOTE */ @CreateCache(area = "test",name = "testCache_",expire = 3600,timeUnit = TimeUnit.SECONDS,cacheType = CacheType.REMOTE) private Cache<String,String> jetCache; public void jetCachePut(){ //redis中的key是:test_testCache_token jetCache.put("token","111111111111111111"); } public String jetCacheGet(){ return jetCache.get("token"); }
jetcache方法缓存
需要在启动类开启方法缓存注解
@SpringBootApplication //jetache缓存开启 @EnableCreateCacheAnnotation //开启方法缓存 @EnableMethodCache(basePackages = {"com.test"}) public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } }
使用如下
/** * 方法缓存注解(使用方法缓存时放入缓存的实体类要实现序列化接口) */ @Cached(name = "user_",key="#id",expire = 3600,cacheType = CacheType.BOTH) public User getById(Integer id){ return userDao.getById(id); } /** * 更新数据时同时把缓存中的数据更新 * vaule表示新的数据 */ @CacheUpdate(name = "user_",key="#user.id",value = "#user") public void updateUser(User user){ userDao.update(user); } /** * 删除数据时候同时删除缓存中的数据 * vaule表示新的数据 */ @CacheInvalidate(name = "user_",key = "#id") public void delUser(Integer id){ userDao.delUser(id); }
如果同一套系统部署到两台机器,a机器改了数据但是b没有收到通知,此时可以使用cache提供的一个定义刷新缓存的注解
@Cached(name = "user_",key="#id",expire = 3600,cacheType = CacheType.BOTH) @CacheRefresh(refresh = 5) public User getById(Integer id){ return userDao.getById(id); }
查看jetcache缓存报告报表
只需增加一个配置
jetcache: #查看缓存报告:1min后 statIntervalMinutes: 1
控制台定时打印缓存命中相关
3.5、j2cache
j2cache是一个缓存整合框架,使各种缓存配合使用,自身不提供缓存
使用参考视频
实用开发篇-116-j2cache相关配置_哔哩哔哩_bilibili