缓存概述
对于访问频率高、一段时间内变动频率低的数据,可以加缓存,以缓解服务器cpu、数据库的压力。如果数据库连接池max-active设置很大但还经常报连接不够,可以考虑缓存部分结果集。
缓存可分为2大类
- 本地缓存:速度快、效率高,但分布式环境下容易出现数据不一致的问题、且缓存的数据量一般不能太大,常见的比如guava、ehcache、caffeine
- 分布式缓存:速度比本地缓存慢,常用于多个服务、应用之间的数据共享,常见的比如redis、memcached
本地缓存
- 可以自己用ConcurrentLinkedHashMap实现
- ehcache:纯java的进程内缓存框架,快速精干,是hibernate默认的缓存管理器,缓存数据的存储有内存、硬盘两级,无需担心容量问题
- caffeine:基于java8,高性能、高命中率、低内存占用,性能极高,在spring 2.x中取代guava成为默认的本地缓存组件,只在内存中存储缓存的数据
分布式缓存
- redis、memcached的相同点:都是把全量数据存储在内存中,高性能,支持海量数据存储
- redis:数据类型丰富,支持数据持久化,可备份数据至硬盘,容灾能力强,可用于存储缓存之外的其他数据,比如可当做队列使用
- memcached:只支持String类型;缺乏认证、安全管制机制,使用时需要将memcached服务器放在防火墙后;不支持持久化,宕机全部数据丢失,一般只用于存储缓存
- redis只使用单核,IO多路复用+减少线程间切换,memcached可以使用多核,存储少量数据时redis单核性能高于memcached,存储大量数据时memcached整体性能高于redis。
如何选择
- 本地缓存极大地提升了效率,如果只有一台服务器(一个服务节点),可以使用本地缓存
- 在分布式环境下,本地缓存容易出现节点之间数据不一致的问题,尽量不要使用本地缓存,使用分布式缓存代替
springboot整合caffeine
依赖
创建项目时勾选 I/O -> Spring cache abstraction,并加上caffeine的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
引导类
加spring的缓存注解 @EnableCaching 开启缓存
yml
spring:
cache:
type: caffeine
cache-names: caffeineCache
caffeine:
spec: initialCapacity=100,maximumSize=1000,expireAfterAccess=1800s
也可以用bean进行配置
/**
* 配置caffeine缓存管理器
*/
@Bean(name = "caffeineCache") //指定缓存管理器名称
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
//初始容量
.initialCapacity(100)
//最大个数
.maximumSize(1000)
//写入缓存或最后一次访问该缓存对象后,多长时间内没访问就过期
.expireAfterAccess(30, TimeUnit.SECONDS));
return cacheManager;
}
caffeine提供三类驱逐策略:基于size(size-based)、基于time(time-based)、基于引用(reference-based),此处是size、time搭配使用。
实体类
@Data
public class Goods implements Serializable {
private Integer goodsId;
private String goodsName;
private BigDecimal goodsPrice;
}
spring缓存注解的使用
缓存注解一般加在controller或service的方法上,controller常常要记录请求日志(请求参数、返回给前端的数据),建议加在service中的方法上。
@Service
public class GoodsService {
@Cacheable(cacheNames = "caffeineCache", key = "#goodsId")
public Goods findGoodsById(Integer goodsId) {
Goods goods = new Goods();
return goods;
}
@CachePut(cacheNames = "caffeineCache", key = "#goods.goodsId")
public Goods updateGoods(Goods goods) {
return goods;
}
@CachePut(cacheNames = "caffeineCache", key = "#goods.goodsId")
public Goods addGoods(Goods goods) {
return goods;
}
@CacheEvict(cacheNames = "caffeineCache", key = "#goodsId")
public void deleteGoods(Integer goodsId) {
}
}
spring提供了通用的缓存注解,由具体的缓存框架提供实现,spring常用的缓存注解如下
- @Cacheable:和查询搭配使用,先查本地缓存,本地缓存中有就直接返回结果,不再执行方法;本地缓存中没有就执行方法,执行成功后将结果写入本地缓存
- @CacheEvict:和删除搭配使用,如果本地有对应的缓存,执行成功后清除缓存
- @CachePut:执行成功后将返回值写入缓存,如果本地没有对应的缓存则为写入缓存,如果本地已有相应的缓存,则会更新缓存。insert、update更新缓存需要返回最新的实体对象,也可以使用@CacheEvict清除缓存,后续查询时@Cacheable写入缓存。
这3个注解的写入|清除缓存都是方法执行成功后才写入|清除缓存,如果方法执行失败|抛出异常,则不会写入|清除缓存。
这3个注解都具有以下属性
- cacheNames:指定要使用的缓存管理器名称,也可以使用cacheNames属性的别名value指定
- key:指定缓存的key
- condition:指定注解生效的条件,当方法执行成功且condition的值为true时才会清除缓存
key、condition可以使用SpEL取值,也可以使用内置对象root
- #root.methodName:当前方法名
- #root.method.name:当前方法
- #root.target:当前方法所属的对象
- #root.targetClass:当前方法所属的对象的Class对象
- #root.args:实参表, #root.args[0]表示第一个参数
springboot整合ehcache
依赖
创建项目时勾选 I/O -> Spring cache abstraction,并加上ehcache的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- ehcache3.x -->
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.9.0</version>
</dependency>
<!-- ehcahce 2.x,已不再维护 -->
<!--<dependency>-->
<!-- <groupId>net.sf.ehcache</groupId>-->
<!-- <artifactId>ehcache</artifactId>-->
<!-- <version>2.10.6</version>-->
<!--</dependency>-->
引导类
加@EnableCaching
resources下新建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">
<!-- 磁盘缓存位置。java.io.tmpdir是系统默认的临时文件目录,也可以使用自定义的路径 -->
<diskStore path="java.io.tmpdir" />
<!-- 默认缓存配置 -->
<defaultCache
overflowToDisk="true"
maxEntriesLocalDisk="5000"
memoryStoreEvictionPolicy="LRU"
eternal="false"
timeToLiveSeconds="3600"
timeToIdleSeconds="600" />
<!--自定义的缓存配置,name指定缓存管理器名称,可以有多个自定义的缓存配置 -->
<cache name="ehcacheCache"
maxElementsInMemory="2000"
overflowToDisk="true"
memoryStoreEvictionPolicy="LRU"
eternal="false"
timeToLiveSeconds="3600"
timeToIdleSeconds="600" />
</ehcache>
- maxElementsInMemory:内存中的最大缓存个数
- overflowToDisk:内存最大缓存个数达到上限时是否缓存到硬盘
- maxEntriesLocalDisk:硬盘最大缓存个数
- memoryStoreEvictionPolicy:缓存个数达到上限时的缓存清除策略,FIFO:类似队列,先进先出,优先清除先放入缓存的;LRU:Least Recently Used,优先清除最近未使用的;LFU:Least Frequently Used,优先清除使用频率低的。
- eternal:缓存是否永久有效
- timeToLiveSeconds:缓存的有效期,到期自动清除
- timeToIdleSeconds:在有效期内缓存的闲置时间,如果在指定时间范围内缓存从未被访问,会自动清除
这依然是ehcache2.x的配置文件,3.x依然兼容2.x的方式
yml
spring:
cache:
#指定ehcache配置文件位置
jcache:
config: classpath:ehcache.xml
# ehcache:
# config: classpath:ehcache.xml
3.x用jcache.config,2.x使用ehcache.config。3.x向下兼容,也可以使用ehcache.config。
实体类
@Data //要实现序列化接口,缓存至硬盘时要用到
public class Goods implements Serializable{
private Integer goodsId;
private String goodsName;
private BigDecimal goodsPrice;
}
spring缓存注解的使用
@Service
public class GoodsService {
@Cacheable(cacheNames = "ehcacheCache", key = "#goodsId")
public Goods findGoodsById(Integer goodsId) {
Goods goods = new Goods();
return goods;
}
@CachePut(cacheNames = "ehcacheCache", key = "#goods.goodsId")
public Goods updateGoods(Goods goods) {
return goods;
}
@CachePut(cacheNames = "ehcacheCache", key = "#goods.goodsId")
public Goods addGoods(Goods goods) {
return goods;
}
@CacheEvict(cacheNames = "ehcacheCache", key = "#goodsId")
public void deleteGoods(Integer goodsId) {
}
}