redis会需要网络通信,本地缓存不需要,如果采用二级缓存会提高效率
本地缓存也可以叫做应用缓存,网络开销很小
如果采用用redis,会涉及到请求,有网络上的开销
本地缓存常用选型本人现在没有过多研究选型相关,参考链接如下:
Java本地缓存技术选型(Guava Cache、Caffeine、Encache)
本地缓存选型(Guava/Caffeine/Ohc)及性能对比
使用缓存流程springboot中集成spring cache,已经有了多种缓存方式的实现,例如Redis、Caffeine、JCache、EhCache等
- 从一级缓存读出数据,如果存在则直接进行业务
- 不存在则读取二级缓存,如果存在则更新一级缓存
- 不存在则从数据库读,然后依次更新二级缓存、一级缓存,然后业务处理
springcache本身并没有提供缓存的实现,但是他提供了一套的接口、代码规范、配置、注解等,这样很容易就可以整合各种缓存,springcache主要包含了两个核心的接口来统一管理缓存相关的实现。分别是:
- org.springframework.cache.Cache 定义了通用缓存操作的接口
- org.springframework.cache.CacheManager 是一个spring的缓存管理器,主要用于管理不同的缓存
引入pom
Caffeineorg.springframework.boot spring-boot-starter-cache
我们使用Caffeine来作为本地的缓存,只需要引入
实现步骤com.github.ben-manes.caffeine caffeine
- 定义缓存的配置文件
- 实现Cache 接口
- 实现CacheManager接口
- 增加spring boot配置类
定义properties配置属性类,主要用于以配置文件进行配置。
@ConfigurationProperties(prefix = "model.cache.multi")
public class RedisCaffeineCacheProperties {
private Set cacheNames = new HashSet<>();
private Set firstCacheNames = new HashSet<>();
private boolean storeNullValues = true;
private String redisKeyPrefix = "";
private Redis redis = new Redis();
private Caffeine caffeine = new Caffeine();
public class Redis {
//设置全局过期时间,默认8天
private long defaultExpiration = 60 * 60 * 24 * 8;
//缓存更新时通知其他节点的topic名称
private String topic = "cache:redis:caffeine:topic";
//每个cacheName的过期时间,单位毫秒,优先级比defaultExpiration高
private Map expires = new HashMap<>();
public Map getExpires() {
return expires;
}
public void setExpires(Map expires) {
this.expires = expires;
}
public long getDefaultExpiration() {
return defaultExpiration;
}
public void setDefaultExpiration(long defaultExpiration) {
this.defaultExpiration = defaultExpiration;
}
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 maximumSize;
//初始化大小
private int initialCapacity;
//每个cacheName的写入后过期时间(expireAfterWrite),找不到就用上面定义的配置。单位毫秒
private Map expires = new HashMap<>();
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 getMaximumSize() {
return maximumSize;
}
public void setMaximumSize(int maximumSize) {
this.maximumSize = maximumSize;
}
public int getInitialCapacity() {
return initialCapacity;
}
public void setInitialCapacity(int initialCapacity) {
this.initialCapacity = initialCapacity;
}
public Map getExpires() {
return expires;
}
public void setExpires(Map expires) {
this.expires = expires;
}
}
public boolean isStoreNullValues() {
return storeNullValues;
}
public void setStoreNullValues(boolean storeNullValues) {
this.storeNullValues = storeNullValues;
}
public String getRedisKeyPrefix() {
return redisKeyPrefix;
}
public void setRedisKeyPrefix(String redisKeyPrefix) {
this.redisKeyPrefix = redisKeyPrefix;
}
public Set getCacheNames() {
return cacheNames;
}
public void setCacheNames(Set cacheNames) {
this.cacheNames = cacheNames;
}
public Redis getRedis() {
return redis;
}
public void setRedis(Redis redis) {
this.redis = redis;
}
public Caffeine getCaffeine() {
return caffeine;
}
public void setCaffeine(Caffeine caffeine) {
this.caffeine = caffeine;
}
public Set getFirstCacheNames() {
return firstCacheNames;
}
public void setFirstCacheNames(Set firstCacheNames) {
this.firstCacheNames = firstCacheNames;
}
}
实现Cache 接口
对多级缓存的具体操作实现,例如对缓存的get、put、evict操作
public class RedisCaffeineCache extends AbstractValueAdaptingCache {
private static final Logger log = LoggerFactory.getLogger(RedisCaffeineCache.class);
private RedisTemplate redisTemplate;
private String uniqueKey = UUID.randomUUID().toString();
private String name;
private String cachePrefix;
private Map expires;
private long defaultExpiration = 0;
private static ReentrantLock fairLock = new ReentrantLock(true);
private boolean firstCache;
private Cache
其中的push方法主要是通知其他缓存删除特定的一级缓存
CacheMessage实现如下
public class CacheMessage implements Serializable {
private static final long serialVersionUID = -3742394520710710440L;
private String name;
private Object key;
private String redisCaffeineCacheUniqueKey;
public CacheMessage() {
}
public CacheMessage(String name, Object key, String redisCaffeineCacheUniqueKey) {
this.name = name;
this.key = key;
this.redisCaffeineCacheUniqueKey = redisCaffeineCacheUniqueKey;
}
public CacheMessage(String name, Object key) {
this.name = name;
this.key = key;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Object getKey() {
return key;
}
public void setKey(Object key) {
this.key = key;
}
public String getRedisCaffeineCacheUniqueKey() {
return redisCaffeineCacheUniqueKey;
}
public void setRedisCaffeineCacheUniqueKey(String redisCaffeineCacheUniqueKey) {
this.redisCaffeineCacheUniqueKey = redisCaffeineCacheUniqueKey;
}
}
当给redis发送消息后,redis接收到消息会做处理,需要重写redis的消息监听器
CacheMessageListener实现如下
public class CacheMessageListener implements MessageListener {
private static final Logger log = LoggerFactory.getLogger(CacheMessageListener.class);
private RedisTemplate redisTemplate;
private RedisCaffeineCacheManager redisCaffeineCacheManager;
public CacheMessageListener(RedisTemplate redisTemplate, RedisCaffeineCacheManager redisCaffeineCacheManager) {
super();
this.redisTemplate = redisTemplate;
this.redisCaffeineCacheManager = redisCaffeineCacheManager;
}
@Override
public void onMessage(Message message, byte[] pattern) {
//处理收到的消息,去清除其他多级缓存的一级缓存
CacheMessage cacheMessage = (CacheMessage) redisTemplate.getValueSerializer().deserialize(message.getBody());
log.debug("receive a redis topic message, clear local cache, the cacheName is {}, the key is {},the uniqueKey is {}", cacheMessage.getName(), cacheMessage.getKey(), cacheMessage.getRedisCaffeineCacheUniqueKey());
redisCaffeineCacheManager.clearLocal(cacheMessage.getName(), cacheMessage.getKey(), cacheMessage.getRedisCaffeineCacheUniqueKey());
}
}
实现CacheManager接口
主要是对多级缓存的管理,核心方法就是getCache() 。
public class RedisCaffeineCacheManager implements CacheManager {
private ConcurrentMap cacheMap = new ConcurrentHashMap();
private RedisTemplate stringKeyRedisTemplate;
private RedisCaffeineCacheProperties redisCaffeineCacheProperties;
private boolean dynamic = true;
private Set cacheNames;
private Set firstCacheNames;
private Map expires;
public RedisCaffeineCacheManager(RedisTemplate redisTemplate, RedisCaffeineCacheProperties redisCaffeineCacheProperties) {
super();
this.stringKeyRedisTemplate = redisTemplate;
this.redisCaffeineCacheProperties = redisCaffeineCacheProperties;
this.cacheNames = redisCaffeineCacheProperties.getCacheNames();
this.firstCacheNames = redisCaffeineCacheProperties.getFirstCacheNames();
this.expires = redisCaffeineCacheProperties.getCaffeine().getExpires();
}
@Override
public Cache getCache(String name) {
Cache cache = cacheMap.get(name);
if (cache != null) {
return cache;
}
//如果不动态根据cacheName创建Cache的实现,且没有配置静态缓存 则返回空
if (!dynamic && cacheNames.isEmpty()) {
return null;
}
//如果开启一级缓存
if (firstCacheNames.contains(name)) {
//开启一级缓存
cache = new RedisCaffeineCache(name, stringKeyRedisTemplate, caffeineCache(name), redisCaffeineCacheProperties);
}else {
// 只开启二级缓存 即只开启redis缓存
cache = new RedisCaffeineCache(name, stringKeyRedisTemplate, null, redisCaffeineCacheProperties);
}
Cache oldCache = cacheMap.putIfAbsent(name, cache);
return oldCache == null ? cache : oldCache;
}
private com.github.benmanes.caffeine.cache.Cache caffeineCache(String name) {
Caffeine caffeineBuilder = Caffeine.newBuilder();
if(redisCaffeineCacheProperties.getCaffeine().getExpireAfterAccess() > 0) {
caffeineBuilder.expireAfterAccess(redisCaffeineCacheProperties.getCaffeine().getExpireAfterAccess(), TimeUnit.MILLISECONDS);
}
Long expire = expires.get(name);
if (expire != null && expire > 0) {
caffeineBuilder.expireAfterWrite(expire, TimeUnit.MILLISECONDS);
}else if (redisCaffeineCacheProperties.getCaffeine().getExpireAfterWrite() > 0){
caffeineBuilder.expireAfterWrite(redisCaffeineCacheProperties.getCaffeine().getExpireAfterAccess(), TimeUnit.MILLISECONDS);
}
if (redisCaffeineCacheProperties.getCaffeine().getRefreshAfterWrite() > 0) {
caffeineBuilder.refreshAfterWrite(redisCaffeineCacheProperties.getCaffeine().getRefreshAfterWrite(), TimeUnit.MILLISECONDS);
}
if (redisCaffeineCacheProperties.getCaffeine().getInitialCapacity() > 0) {
caffeineBuilder.initialCapacity(redisCaffeineCacheProperties.getCaffeine().getInitialCapacity());
}
if (redisCaffeineCacheProperties.getCaffeine().getMaximumSize() > 0) {
caffeineBuilder.maximumSize(redisCaffeineCacheProperties.getCaffeine().getMaximumSize());
}
return caffeineBuilder.build();
}
@Override
public Collection getCacheNames() {
return this.cacheNames;
}
public void clearLocal(String name, Object key,String redisCaffeineCacheUniqueKey) {
Cache cache = cacheMap.get(name);
if(cache == null) {
return ;
}
RedisCaffeineCache redisCaffeineCache = (RedisCaffeineCache) cache;
//判断是否是自己发送的消息 如果是自己发送的就不需要清楚缓存了
if (StringUtils.isNoneEmpty(redisCaffeineCacheUniqueKey) && redisCaffeineCache.getUniqueKey().equals(redisCaffeineCacheUniqueKey)) {
return;
}
redisCaffeineCache.clearLocal(key);
}
}
增加spring boot配置类
只是我自己的一种实现,具体根据自己的项目来进行配置
@Configuration
@EnableConfigurationProperties(RedisCaffeineCacheProperties.class)
@EnableCaching
public class RedisCaffeineCacheAutoConfiguration {
@Autowired
private RedisCaffeineCacheProperties redisCaffeineCacheProperties;
@Bean
public RedisCaffeineCacheManager cacheManager(RedisTemplate redisTemplate) {
return new RedisCaffeineCacheManager(redisTemplate, redisCaffeineCacheProperties);
}
@Bean
@ConditionalOnBean(name = {"redisTemplate", "cacheManager"})
public RedisMessageListenerContainer redisMessageListenerContainer(RedisTemplate redisTemplate,RedisCaffeineCacheManager redisCaffeineCacheManager) {
RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
redisMessageListenerContainer.setConnectionFactory(redisTemplate.getConnectionFactory());
CacheMessageListener cacheMessageListener = new CacheMessageListener(redisTemplate, redisCaffeineCacheManager);
redisMessageListenerContainer.addMessageListener(cacheMessageListener, new ChannelTopic(redisCaffeineCacheProperties.getRedis().getTopic()));
return redisMessageListenerContainer;
}
}
以上就是利用spring cache 实现多级缓存的核心,仅供参考。
总结这是我第一次接触了解这种缓存实现,不足之处请见谅。
关于设置锁的地方,建议使用Redisson操作会更方便。



