Springboot实现缓存很简单,最多两处配置(Maven工程的pom.xml引入依赖包和系统配置文件application.yml定义参数)即可引入缓存,然后再使用注解,就可以很方便是开始使用缓存了。
这样是比较简单,书写代码也很高效。可是执行的代码高效吗,当然的不高效:
1、单机的redis,并发访问量有限吧?
2、集群redis,并发访问量还是有限吧?
3、redis的命中率、网络传输,都需要一定的损耗时间吧?
站在前人的肩膀上,才能更接近成功。参考了一些前人的资料,我目前整理出下面的两级缓存机制。
一、两级缓存数据流原理
1、Cacheable(读取数据流程)
读取数据,先从一级缓存中读取,一级缓存中有值则直接读取,没有则再读取二级缓存;二级缓存中有值则更新一级缓存返回值,没有则从数据库中获取数据,依次更新一二级缓存。
2、CachePut(数据变更流程)
3、CacheEvict(缓存删除流程)
4、利用redis的消息发布订阅模式,实现其它站点缓存同步
二、一级缓存引入
Spring Boot的本地缓存有好几种,我采用Caffeine。
Caffeine是一个基于Google开源的Guava设计理念的一个高性能内存缓存,使用java8开发,spring boot引入Caffeine后已经逐步废弃Guava的整合了。Caffeine源码及介绍地址:caffeine
caffeine提供了多种缓存填充策略、值回收策略,同时也包含了缓存命中次数等统计数据,对缓存的优化能够提供很大帮助
caffeine的介绍可以参考://www.jb51.net/article/134242.htm
这里简单说下caffeine基于时间的回收策略有以下几种:
- expireAfterAccess:访问后到期,从上次读或写发生后的过期时间
- expireAfterWrite:写入后到期,从上次写入发生之后的过期时间
- 自定义策略:到期时间由实现Expiry接口后单独计算
在工程的pom.xml中增加一下依赖
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
三、代码
1、增加属性文件CacheRedisCaffeineProperties.java
package com.demo.example.security.core.cache;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "spring.cache.multi")
public class CacheRedisCaffeineProperties {
/**
* 本机的ip地址
*/
private String localIp="127.0.0.1";
private Set<String> cacheNames = new HashSet<>();
/** 是否存储空值,默认true,防止缓存穿透 */
private boolean cacheNullValues = true;
/** 是否动态根据cacheName创建Cache的实现,默认true */
private boolean dynamic = true;
/** 缓存key的前缀 */
private String cachePrefix;
private Redis redis = new Redis();
private Caffeine caffeine = new Caffeine();
public class Redis {
/** 全局过期时间,单位毫秒,默认不过期 */
private long defaultExpiration = 0;
/** 每个cacheName的过期时间,单位毫秒,优先级比defaultExpiration高 */
private Map<String, Long> expires = new HashMap<>();
/** 缓存更新时通知其他节点的topic名称 */
private String topic = "cache:redis:caffeine:topic";
public long getDefaultExpiration() {
return defaultExpiration;
}
public void setDefaultExpiration(long defaultExpiration) {
this.defaultExpiration = defaultExpiration;
}
public Map<String, Long> getExpires() {
return expires;
}
public void setExpires(Map<String, Long> expires) {
this.expires = expires;
}
public String getTopic() {
return topic;
}
public void setTopic(String topic) {
this.topic = topic;
}
}
public class Caffeine {
/** 访问后过期时间,单位毫秒 */
private long expireAfterAccess;
/** 写入后过期时间,单位毫秒 */
private long expireAfterWrite;
/** 写入后刷新时间,单位毫秒 */
private long refreshAfterWrite;
/** 初始化大小 */
private int initialCapacity;
/** 最大缓存对象个数,超过此数量时之前放入的缓存将失效 */
private long maximumSize;
/** 由于权重需要缓存对象来提供,对于使用spring cache这种场景不是很适合,所以暂不支持配置 */
// private long maximumWeight;
public long getExpireAfterAccess() {
return expireAfterAccess;
}
public void setExpireAfterAccess(long expireAfterAccess) {
this.expireAfterAccess = expireAfterAccess;
}
public long getExpireAfterWrite() {
return expireAfterWrite;
}
public void setExpireAfterWrite(long expireAfterWrite) {
this.expireAfterWrite = expireAfterWrite;
}
public long getRefreshAfterWrite() {
return refreshAfterWrite;
}
public void setRefreshAfterWrite(long refreshAfterWrite) {
this.refreshAfterWrite = refreshAfterWrite;
}
public int getInitialCapacity() {
return initialCapacity;
}
public void setInitialCapacity(int initialCapacity) {
this.initialCapacity = initialCapacity;
}
public long getMaximumSize() {
return maximumSi