一、EhCache介绍
在查询数据的时候,数据大多来自于数据库,我们会基于SQL语句与数据库交互,数据库一般会基于本地磁盘IO将数据读取到内存,返回给Java服务端,我们再将数据响应给前端,做数据展示。
但是MySQL这种关系型数据库查询数据相对比较慢,因为有磁盘IO,或者是全盘扫描的风险,在针对一些热点数据时,会对MySQL造成比较大的压力,此时我们可以采用缓存的方式来解决。
而缓存又分为很多种,相对服务端角度来说,可以采用Redis和JVM这两种方式。
Redis不必多说,直接基于基于内存读写,并发读写的并发能力特别强,所以很多时间,在分布式或者微服务的项目中,为了保证数据一致性,我们会采用Redis来实现缓存。
但是在一些单体项目,我们可以采用JVM级别的缓存,比如直接采用框架自带的,例如Hibernate的缓存,MyBatis的缓存,或者是Guava提供的Cache,以及今儿要玩的EhCache。
还有一种情况可以采用JVM缓存,在分布式环境下,如果并发特别大,Redis也扛不住,这是我们可以将数据平均的分散在各个节点的JVM缓存中,并且设置一个较短的生存时间,这样就可以减缓Redis的压力,从而解决热点数据Redis扛不住的问题
同时EhCache也是Hibernate框架默认使用的缓存组件实现二级缓存。类似MyBatis,就直接用的HashMap。
二、EhCache基本使用
官网:http://www.ehcache.org
通过后缀就可以看出EhCache是开源的组件。
EhCache除了开源,还有可以几乎0成本和Spring整合的有点,毕竟现在Java项目大多都是基于Spring方式构建的,这也可以让我们在使用EhCache的时候更加方便。
这里还是单独的使用EhCache来感受一下,其实使用方式和HashMap的put和get的方式类似,不过EhCache提供了更加丰富的功能。
EhCache有2.x和3.x两个常用的大版本,两个版本API差异巨大,这里咱们以3.x为讲解的内容应用
官方入门文档:
https://www.ehcache.org/documentation/3.10/getting-started.html
导入依赖,走你~
代码语言:javascript复制<dependency><groupId>org.ehcache</groupId><artifactId>ehcache</artifactId><version>3.8.1</version></dependency>
直接编写快速入门代码
代码语言:javascript复制publicstaticvoidmain(String[] args){
//1. 构建核心组件,CacheManager
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
// 设置缓存别名
.withCache("cache1",
CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class,Object.class, ResourcePoolsBuilder.heap(1))) // 同时指定缓存的key-value类型,以及缓存容纳的个数
.build();
//2. 初始化走你
cacheManager.init();
//3. 获取Cache方式一。从缓存管理器拿到设置好的Cache
Cache<String, Object> cache1 = cacheManager.getCache("cache1", String.class, Object.class);
//4. 获取Cache方式二。 基于缓存管理器构建一个Cache
Cache<String, Object> cache2 = cacheManager.createCache("cache2", CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, Object.class, ResourcePoolsBuilder.heap(5)));
//5. 操作
cache1.put("cache0","阿巴阿巴~~");
Object value1 = cache1.get("cache0");
System.out.println(value1);
}
EhCache也提供了xml的配置方式,不过现在SpringBoot项目居多,大多是没有xml配置信息的,所以这里核心以Java编码的方式配置
三、EhCache详细配置
首先在获取Cache时,可以提升指定好几个信息,依次把核心的配置搞一下
3.1 数据内存位置
EhCache3.0中不但提供了head的堆内存储方式,还提供了堆外存储以及磁盘存储
heap堆内内存:
heap表示使用堆内内存,heap(10L)表示只能存放put10个对象,当put第11个那么前面10个对象将有一个会被移除。
off-heap堆外内存:
off-heap叫做堆外内存,将你的对象从堆中脱离出来序列化,然后存储在一大块内存中,这就像它存储到磁盘上一样,但它仍然在RAM中。对象在这种状态下不能直接使用,它们必须首先反序列化,也不受垃圾收集。序列化和反序列化将会影响部分性能,使用堆外内存能够降低GC导致的暂停。
disk写到磁盘的内存:
disk表示将对象写到磁盘中,这样有个好处是当服务重启时可以直接读取磁盘上面的内容将数据加载到服务中。
可以在设置Cache对象时指定缓存的方式
EhCache提供了三种组合来实现多级缓存的效果
- heap off-heap
- heap disk
- heap off-head disk
在存储数据时,最外层的缓存是数据最全面的,而heap就属于临时存储的效果
但是heap中的性能最快的,因为无论是堆外内存还是磁盘存储,都需要对数据进行序列化和反序列化,不过其速度其实都很快。
在存储缓存数据时,数据会落到所有设置到的存储位置,获取的时候自然是由快到慢的查询方式
代码语言:javascript复制publicstaticvoidmain(String[] args){
// 设置disk存储的路径前缀
String path = "D:\";
// 声明cacheManager,并指定heap,off-heap以及磁盘存储方式
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.with(CacheManagerBuilder.persistence(new File(path, "data")))
.withCache("item",CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
// .heap(1)
.heap(1,MemoryUnit.MB)
.offheap(2, MemoryUnit.MB)
.disk(3, MemoryUnit.MB, true))
)
.build(true); // 这里的true,相当于执行了iniy方法// 获取缓存Cache
Cache<Long, String> cache = cacheManager.getCache("item", Long.class, String.class);
// 存储数据// cache.put(1L, UUID.randomUUID().toString());// 获取数据
System.out.println(cache.get(1L));
// 手动close,cacheManager,不然不会持久到磁盘
cacheManager.close();
}
还需要注意,前置缓存空间必须要小于后置缓存,比如heap要比off-heap小,off-heap要比disk小
在本地磁盘也可以看到持久化的数据,分为很多中
- meta元数据主要存放对应cache的信息,主要是数据类型和构建时间
- data就是持久化到本地的数据啦,乱码看不懂滴干活
- index类似指向data数据的具体索引信息
3.2 设置缓存的生存时间
大家熟悉的Redis中是可以设置key的生存时间的,不然长时间只吃不吐必然会内存溢出,EhCache也是这个情况,所以EhCache提供了给缓存设置生存时间的方式,一共有两种方式
代码语言:javascript复制publicstaticvoidmain(String[] args){
//1. 构建CacheManager
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build();
//2. 构建Cache并设置特性
cacheManager.createCache("item",CacheConfigurationBuilder
.newCacheConfigurationBuilder(String.class,Object.class,ResourcePoolsBuilder.heap(10))
.withExpiry(ExpiryPolicyBuilder.noExpiration()).build() // 不设置生存时间,一直活着
.withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofMillis(20000))).build()) // 从缓存存储开始计算,生存多久
.withExpiry(ExpiryPolicyBuilder.timeToIdleExpiration(Duration.ofSeconds(10))).build(); // 从缓存最后一次使用开始计算,生存多久
}
四、SpringBoot整合EhCache配置
单独使用EhCache需要考虑的内容还是比较多的,所以可以直接用SpringBoot整合EhCache,使用起来就更加方便。
首先SpringBoot是支持EhCache的,所以整体配置成本不高
4.1 导入依赖
代码语言:javascript复制<dependencies><dependency><groupId>org.ehcache</groupId><artifactId>ehcache</artifactId><version>3.8.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency></dependencies>
4.2 准备EhCache配置项
代码语言:javascript复制# 准备EhCache缓存信息ehcache:heap:1000offheap:10disk:500diskDir:D:/data/cacheNames:-user-item
Java类引入
代码语言:javascript复制@Component@ConfigurationProperties("ehcache")publicclassEhCacheProps{
privateint heap;
privateint offheap;
privateint disk;
private String diskDir;
private Set<String> cacheNames;
}
4.3 配置EhCache
因为SpringBoot默认在Cache的位置采用Redis,使用EhCache需要手动设置配置信息
并且EhCache涉及到offheap和disk时,需要序列化,那么存储的value值需要可以被序列化,直接采用Object会出问题,所以需要单独设置value的基类实现序列化接口
- value的基类 publicclassBaseObjectimplementsSerializable{ }
- 设置EhCache的CacheManager @Configuration@EnableCachingpublicclassEhCacheConfig{ @Autowiredprivate EhCacheProps ehCacheProps; @Beanpublic CacheManager ehcacheManager(){ Set<String> cacheNames = ehCacheProps.getCacheNames(); // 设置缓存存放策略 ResourcePoolsBuilder resourcePoolsBuilder = ResourcePoolsBuilder.newResourcePoolsBuilder() // 堆内缓存大小 .heap(ehCacheProps.getHeap()) // 堆外缓存大小 .offheap(ehCacheProps.getOffheap(), MemoryUnit.MB) // 磁盘缓存大小 .disk(ehCacheProps.getDisk(), MemoryUnit.MB); // 设置生存时间 ExpiryPolicy expiryPolicy = ExpiryPolicyBuilder.noExpiration(); // 设置配置项 CacheConfiguration config = CacheConfigurationBuilder .newCacheConfigurationBuilder(String.class, BaseObject.class, resourcePoolsBuilder) .withExpiry(expiryPolicy) .build(); // 设置持久化位置 CacheManagerBuilder<PersistentCacheManager> cacheManagerBuilder = CacheManagerBuilder.newCacheManagerBuilder() .with(CacheManagerBuilder.persistence(ehCacheProps.getDiskDir())); // 设置缓存名称信息for (String cacheName : cacheNames) { cacheManagerBuilder.withCache(cacheName,config); } return cacheManagerBuilder.build(); } }
配置好之后,就会采用EhCache去缓存数据了,后面采用Java规范中的Cache注解即可。
五、Cache注解使用
Cache注解是JSR-107规范中的,Spring在3.1版本后就已经支持了Cache注解。咋前面的配置搞定之后,一般咱们就可以在Service层追加好注解,来标识数据查询的方式,以及更新的手段。
前面配置好了CacheManager,而且在CacheManager中也有管理好的Cache对象,在使用时,只需要指定好采用哪种Cache注解即可。
5.1 @Cacheable
这个是命中缓存的注解,也就是在查询的时候,根据当前注解查看缓存中有木有数据,木有就走业务,让后扔缓存里,后面就可以命中缓存了。
5.1.1 添加位置
可以添加在方法或者是直接添加到类中
添加到类:当前类的所有方法都会走缓存的方式。很明显,没办法细粒度化配置,所以一般不用
添加到方法:针对当前方法采用上述缓存逻辑。
5.1.2 缓存存储方式
在添加注解之后,必须添加上要采用哪个cacheNames,明显可以缓存到多个cache中,当然value也是一样的。
针对添加的注解的方法,方法返回值就是要缓存的value,key的话,默认不写,就是当前方法传递的参数。
当然key也声明,或者自己指定key的生成策略
5.1.3 key的声明方式
首先自然是不指定key的信息,采用默认的内容即可。
其次是指定缓存的key,咱们可以基于spel的方式设置key是什么内容。
代码语言:javascript复制@Override@Cacheable(cacheNames = "item",key = "#id")public String findById(String id){
System.out.println("查询数据库。。。。。。。。。");
return id;
}
@Override@Cacheable(cacheNames = "item",key = "#user.id")public String findByUser(User user){
System.out.println("查询数据库。。。。。。。。。");
return id;
}
SPEL也支持很多其他特殊可用值
还有自定义策略的方式声明key
EhCache
一、EhCache介绍
在查询数据的时候,数据大多来自于数据库,我们会基于SQL语句与数据库交互,数据库一般会基于本地磁盘IO将数据读取到内存,返回给Java服务端,我们再将数据响应给前端,做数据展示。
但是MySQL这种关系型数据库查询数据相对比较慢,因为有磁盘IO,或者是全盘扫描的风险,在针对一些热点数据时,会对MySQL造成比较大的压力,此时我们可以采用缓存的方式来解决。
而缓存又分为很多种,相对服务端角度来说,可以采用Redis和JVM这两种方式。
Redis不必多说,直接基于基于内存读写,并发读写的并发能力特别强,所以很多时间,在分布式或者微服务的项目中,为了保证数据一致性,我们会采用Redis来实现缓存。
但是在一些单体项目,我们可以采用JVM级别的缓存,比如直接采用框架自带的,例如Hibernate的缓存,MyBatis的缓存,或者是Guava提供的Cache,以及今儿要玩的EhCache。
还有一种情况可以采用JVM缓存,在分布式环境下,如果并发特别大,Redis也扛不住,这是我们可以将数据平均的分散在各个节点的JVM缓存中,并且设置一个较短的生存时间,这样就可以减缓Redis的压力,从而解决热点数据Redis扛不住的问题
同时EhCache也是Hibernate框架默认使用的缓存组件实现二级缓存。类似MyBatis,就直接用的HashMap。
二、EhCache基本使用
官网:http://www.ehcache.org
通过后缀就可以看出EhCache是开源的组件。
EhCache除了开源,还有可以几乎0成本和Spring整合的有点,毕竟现在Java项目大多都是基于Spring方式构建的,这也可以让我们在使用EhCache的时候更加方便。
这里还是单独的使用EhCache来感受一下,其实使用方式和HashMap的put和get的方式类似,不过EhCache提供了更加丰富的功能。
EhCache有2.x和3.x两个常用的大版本,两个版本API差异巨大,这里咱们以3.x为讲解的内容应用
官方入门文档:
https://www.ehcache.org/documentation/3.10/getting-started.html
导入依赖,走你~
代码语言:javascript复制<dependency><groupId>org.ehcache</groupId><artifactId>ehcache</artifactId><version>3.8.1</version></dependency>
直接编写快速入门代码
代码语言:javascript复制publicstaticvoidmain(String[] args){
//1. 构建核心组件,CacheManager
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
// 设置缓存别名
.withCache("cache1",
CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class,Object.class, ResourcePoolsBuilder.heap(1))) // 同时指定缓存的key-value类型,以及缓存容纳的个数
.build();
//2. 初始化走你
cacheManager.init();
//3. 获取Cache方式一。从缓存管理器拿到设置好的Cache
Cache<String, Object> cache1 = cacheManager.getCache("cache1", String.class, Object.class);
//4. 获取Cache方式二。 基于缓存管理器构建一个Cache
Cache<String, Object> cache2 = cacheManager.createCache("cache2", CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, Object.class, ResourcePoolsBuilder.heap(5)));
//5. 操作
cache1.put("cache0","阿巴阿巴~~");
Object value1 = cache1.get("cache0");
System.out.println(value1);
}
EhCache也提供了xml的配置方式,不过现在SpringBoot项目居多,大多是没有xml配置信息的,所以这里核心以Java编码的方式配置
三、EhCache详细配置
首先在获取Cache时,可以提升指定好几个信息,依次把核心的配置搞一下
3.1 数据内存位置
EhCache3.0中不但提供了head的堆内存储方式,还提供了堆外存储以及磁盘存储
heap堆内内存:
heap表示使用堆内内存,heap(10L)表示只能存放put10个对象,当put第11个那么前面10个对象将有一个会被移除。
off-heap堆外内存:
off-heap叫做堆外内存,将你的对象从堆中脱离出来序列化,然后存储在一大块内存中,这就像它存储到磁盘上一样,但它仍然在RAM中。对象在这种状态下不能直接使用,它们必须首先反序列化,也不受垃圾收集。序列化和反序列化将会影响部分性能,使用堆外内存能够降低GC导致的暂停。
disk写到磁盘的内存:
disk表示将对象写到磁盘中,这样有个好处是当服务重启时可以直接读取磁盘上面的内容将数据加载到服务中。
可以在设置Cache对象时指定缓存的方式
EhCache提供了三种组合来实现多级缓存的效果
- heap off-heap
- heap disk
- heap off-head disk
在存储数据时,最外层的缓存是数据最全面的,而heap就属于临时存储的效果
但是heap中的性能最快的,因为无论是堆外内存还是磁盘存储,都需要对数据进行序列化和反序列化,不过其速度其实都很快。
在存储缓存数据时,数据会落到所有设置到的存储位置,获取的时候自然是由快到慢的查询方式
代码语言:javascript复制publicstaticvoidmain(String[] args){
// 设置disk存储的路径前缀
String path ="D:\";
// 声明cacheManager,并指定heap,off-heap以及磁盘存储方式
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.with(CacheManagerBuilder.persistence(new File(path, "data")))
.withCache("item",CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
// .heap(1)
.heap(1,MemoryUnit.MB)
.offheap(2, MemoryUnit.MB)
.disk(3, MemoryUnit.MB, true))
)
.build(true); // 这里的true,相当于执行了iniy方法// 获取缓存Cache
Cache<Long, String> cache = cacheManager.getCache("item", Long.class, String.class);
// 存储数据// cache.put(1L, UUID.randomUUID().toString());// 获取数据
System.out.println(cache.get(1L));
// 手动close,cacheManager,不然不会持久到磁盘
cacheManager.close();
}
还需要注意,前置缓存空间必须要小于后置缓存,比如heap要比off-heap小,off-heap要比disk小
在本地磁盘也可以看到持久化的数据,分为很多中
- meta元数据主要存放对应cache的信息,主要是数据类型和构建时间
- data就是持久化到本地的数据啦,乱码看不懂滴干活
- index类似指向data数据的具体索引信息
3.2 设置缓存的生存时间
大家熟悉的Redis中是可以设置key的生存时间的,不然长时间只吃不吐必然会内存溢出,EhCache也是这个情况,所以EhCache提供了给缓存设置生存时间的方式,一共有两种方式
代码语言:javascript复制publicstaticvoidmain(String[] args){
//1. 构建CacheManager
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build();
//2. 构建Cache并设置特性
cacheManager.createCache("item",CacheConfigurationBuilder
.newCacheConfigurationBuilder(String.class,Object.class,ResourcePoolsBuilder.heap(10))
.withExpiry(ExpiryPolicyBuilder.noExpiration()).build() // 不设置生存时间,一直活着
.withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofMillis(20000))).build()) // 从缓存存储开始计算,生存多久
.withExpiry(ExpiryPolicyBuilder.timeToIdleExpiration(Duration.ofSeconds(10))).build(); // 从缓存最后一次使用开始计算,生存多久
}
四、SpringBoot整合EhCache配置
单独使用EhCache需要考虑的内容还是比较多的,所以可以直接用SpringBoot整合EhCache,使用起来就更加方便。
首先SpringBoot是支持EhCache的,所以整体配置成本不高
4.1 导入依赖
代码语言:javascript复制<dependencies><dependency><groupId>org.ehcache</groupId><artifactId>ehcache</artifactId><version>3.8.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency></dependencies>
4.2 准备EhCache配置项
代码语言:javascript复制# 准备EhCache缓存信息ehcache:heap:1000offheap:10disk:500diskDir:D:/data/cacheNames:-user-item
Java类引入
代码语言:javascript复制@Component@ConfigurationProperties("ehcache")publicclassEhCacheProps{
privateint heap;
privateint offheap;
privateint disk;
privateString diskDir;
privateSet<String> cacheNames;
}
4.3 配置EhCache
因为SpringBoot默认在Cache的位置采用Redis,使用EhCache需要手动设置配置信息
并且EhCache涉及到offheap和disk时,需要序列化,那么存储的value值需要可以被序列化,直接采用Object会出问题,所以需要单独设置value的基类实现序列化接口
value的基类
publicclassBaseObjectimplementsSerializable{
}
设置EhCache的CacheManager
@Configuration@EnableCachingpublicclassEhCacheConfig{
代码语言:javascript复制@Autowiredprivate EhCacheProps ehCacheProps;
@Beanpublic CacheManager ehcacheManager(){
Set<String> cacheNames = ehCacheProps.getCacheNames();
// 设置缓存存放策略
ResourcePoolsBuilder resourcePoolsBuilder = ResourcePoolsBuilder.newResourcePoolsBuilder()
// 堆内缓存大小
.heap(ehCacheProps.getHeap())
// 堆外缓存大小
.offheap(ehCacheProps.getOffheap(), MemoryUnit.MB)
// 磁盘缓存大小
.disk(ehCacheProps.getDisk(), MemoryUnit.MB);
// 设置生存时间
ExpiryPolicy expiryPolicy = ExpiryPolicyBuilder.noExpiration();
// 设置配置项
CacheConfiguration config = CacheConfigurationBuilder
.newCacheConfigurationBuilder(String.class, BaseObject.class, resourcePoolsBuilder)
.withExpiry(expiryPolicy)
.build();
// 设置持久化位置
CacheManagerBuilder<PersistentCacheManager> cacheManagerBuilder = CacheManagerBuilder.newCacheManagerBuilder()
.with(CacheManagerBuilder.persistence(ehCacheProps.getDiskDir()));
// 设置缓存名称信息for (String cacheName : cacheNames) {
cacheManagerBuilder.withCache(cacheName,config);
}
return cacheManagerBuilder.build();
}
}
配置好之后,就会采用EhCache去缓存数据了,后面采用Java规范中的Cache注解即可。
五、Cache注解使用
Cache注解是JSR-107规范中的,Spring在3.1版本后就已经支持了Cache注解。咋前面的配置搞定之后,一般咱们就可以在Service层追加好注解,来标识数据查询的方式,以及更新的手段。
前面配置好了CacheManager,而且在CacheManager中也有管理好的Cache对象,在使用时,只需要指定好采用哪种Cache注解即可。
5.1 @Cacheable
这个是命中缓存的注解,也就是在查询的时候,根据当前注解查看缓存中有木有数据,木有就走业务,让后扔缓存里,后面就可以命中缓存了。
5.1.1 添加位置
可以添加在方法或者是直接添加到类中
添加到类:当前类的所有方法都会走缓存的方式。很明显,没办法细粒度化配置,所以一般不用
添加到方法:针对当前方法采用上述缓存逻辑。
5.1.2 缓存存储方式
在添加注解之后,必须添加上要采用哪个cacheNames,明显可以缓存到多个cache中,当然value也是一样的。
针对添加的注解的方法,方法返回值就是要缓存的value,key的话,默认不写,就是当前方法传递的参数。
当然key也声明,或者自己指定key的生成策略
5.1.3 key的声明方式
首先自然是不指定key的信息,采用默认的内容即可。
其次是指定缓存的key,咱们可以基于spel的方式设置key是什么内容。
代码语言:javascript复制@Override@Cacheable(cacheNames = "item",key = "#id")public String findById(String id){
System.out.println("查询数据库。。。。。。。。。");
returnid;
}
@Override@Cacheable(cacheNames = "item",key = "#user.id")publicStringfindByUser(User user){
System.out.println("查询数据库。。。。。。。。。");
returnid;
}
SPEL也支持很多其他特殊可用值