Angular 6集成Spring Boot 2,Spring Security,JWT和CORS系列:八、Spring Boot实现两级cache

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基于时间的回收策略有以下几种:

  1. expireAfterAccess:访问后到期,从上次读或写发生后的过期时间
  2. expireAfterWrite:写入后到期,从上次写入发生之后的过期时间
  3. 自定义策略:到期时间由实现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 maximumSize;
		}

		public void setMaximumSize(long maximumSize) {
			this.maximumSize = maximumSize;
		}
	}
	
	public String getLocalIp() {
		return localIp;
	}

	public void setLocalIp(String localIp) {
		this.localIp = localIp;
	}

	public Set<String> getCacheNames() {
		return cacheNames;
	}

	public void setCacheNames(Set<String> cacheNames) {
		this.cacheNames = cacheNames;
	}
	
	public boolean isCacheNullValues() {
		return this.cacheNullValues;
	}

	public void setCacheNullValues(boolean cacheNullValues) {
		this.cacheNullValues = cacheNullValues;
	}

	public boolean isDynamic() {
		return dynamic;
	}

	public void setDynamic(boolean dynamic) {
		this.dynamic = dynamic;
	}

	public String getCachePrefix() {
		return cachePrefix;
	}

	public void setCachePrefix(String cachePrefix) {
		this.cachePrefix = cachePrefix;
	}

	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;
	}
	
}

localIp表示本机的ip地址,默认值为127.0.0.1。系统启动,当其值为空或者为127.0.0.1时,系统通过InetAddress获取本机的ip地址.

redis参数的expires,可以设置不同的缓存项不同的过期时间。

2、redis监控的消息体CacheMessage.java

package com.demo.example.security.core.cache;

import java.io.Serializable;

public class CacheMessage implements Serializable {

	/**序列化id.*/
	private static final long serialVersionUID = 5987219310442078193L;

	/**
	 * 发送消息的ip
	 */
	private String ip;
	/**
	 * cache组名
	 */
	private String cacheName;
	/**
	 * cache键值
	 */
	private Object key;

	
	public CacheMessage(String ip, String cacheName, Object key) {
		super();
		this.ip = ip;
		this.cacheName = cacheName;
		this.key = key;
	}

	public String getIp() {
		return ip;
	}


	public void setIp(String ip) {
		this.ip = ip;
	}


	public String getCacheName() {
		return cacheName;
	}

	public void setCacheName(String cacheName) {
		this.cacheName = cacheName;
	}

	public Object getKey() {
		return key;
	}

	public void setKey(Object key) {
		this.key = key;
	}

	@Override
	public String toString() {
		return "CacheMessage [ip=" + ip + ", cacheName=" + cacheName + ", key=" + key + "]";
	}
	
}

发送消息的ip是为了识别消息是谁发送的。

3、消息监听者。当接受消息时,判断是否本机发送的,非本机发送的调用缓存管理器的清除本地缓存方法

/**
 * 
 */
package com.demo.example.security.core.cache;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.core.RedisTemplate;

public class CacheMessageListener implements MessageListener {

	private final Logger logger = LoggerFactory.getLogger(CacheMessageListener.class);
	
	/**
	 * 缓存配置参数.
	 */
	private CacheRedisCaffeineProperties cacheRedisCaffeineProperties;
	/**
	 * redis连接模板.
	 */
	private RedisTemplate<Object, Object> redisTemplate;
	/**
	 * 缓存管理器.
	 */
	private RedisCaffeineCacheManager redisCaffeineCacheManager;

	/**
	 * 构造函数,注入
	 * @param redisTemplate redis连接模板
	 * @param redisCaffeineCacheManager 缓存管理器
	 * @param cacheRedisCaffeineProperties 缓存配置参数
	 */
	public CacheMessageListener(RedisTemplate<Object, Object> redisTemplate,
			RedisCaffeineCacheManager redisCaffeineCacheManager,
			CacheRedisCaffeineProperties cacheRedisCaffeineProperties) {
		super();
		this.redisTemplate = redisTemplate;
		this.redisCaffeineCacheManager = redisCaffeineCacheManager;
		this.cacheRedisCaffeineProperties=cacheRedisCaffeineProperties;
	}

	/**
	 * 监控消息,接受到消息后的处理
	 */
	@Override
	public void onMessage(Message message, byte[] pattern) {
		CacheMessage cacheMessage = (CacheMessage) redisTemplate.getValueSerializer().deserialize(message.getBody());
		logger.debug("recevice a redis topic message, clear local cache, {}",cacheMessage);
		if (cacheMessage.getIp().equals(cacheRedisCaffeineProperties.getLocalIp())) {
			logger.error("自己发送的,不清理,{}",cacheMessage);
		} else {
			//调用缓存管理器的清除本地缓存
			redisCaffeineCacheManager.clearLocal(cacheMessage);
		}
	}
}

4、本地缓存对象RedisCaffeineCache.java

/**
 * 
 */
package com.demo.example.security.core.cache;

import java.lang.reflect.Constructor;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.support.AbstractValueAdaptingCache;
import org.springframework.data.redis.core.RedisTemplate;

import com.github.benmanes.caffeine.cache.Cache;


public class RedisCaffeineCache extends AbstractValueAdaptingCache {

	private final Logger logger = LoggerFactory.getLogger(RedisCaffeineCache.class);
	
	private CacheRedisCaffeineProperties cacheRedisCaffeineProperties;
	private String name;
	private RedisTemplate<Object, Object> redisTemplate;
	private Cache<Object, Object> caffeineCache;
	private String cachePrefix;
	private long defaultExpiration = 0;
	private Map<String, Long> expires;
	
	private String topic = "cache:redis:caffeine:topic";

	protected RedisCaffeineCache(boolean allowNullValues) {
		super(allowNullValues);
	}

	public RedisCaffeineCache(String name, RedisTemplate<Object, Object> redisTemplate,
			Cache<Object, Object> caffeineCache, CacheRedisCaffeineProperties cacheRedisCaffeineProperties) {
		super(cacheRedisCaffeineProperties.isCacheNullValues());
		this.cacheRedisCaffeineProperties=cacheRedisCaffeineProperties;
		this.name = name;
		this.redisTemplate = redisTemplate;
		this.caffeineCache = caffeineCache;
		this.cachePrefix = cacheRedisCaffeineProperties.getCachePrefix();
		this.defaultExpiration = cacheRedisCaffeineProperties.getRedis().getDefaultExpiration();
		this.expires = cacheRedisCaffeineProperties.getRedis().getExpires();
		this.topic = cacheRedisCaffeineProperties.getRedis().getTopic();
		
		logger.info("topic={}",topic);
		logger.info("defaultExpiration={}",defaultExpiration);
	}

	@Override
	public String getName() {
		logger.debug("String getName() [name={}]",this.name); 
		return this.name;
	}

	@Override
	public Object getNativeCache() {
		logger.debug("Object getNativeCache() [name= {}'",this.name); 
		return this;
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> T get(Object key, Callable<T> valueLoader) {
		logger.debug("<T> T get(Object key, Callable<T> valueLoader), the cache'name is {},the key is : {}", this.name,key); 
		Object value = lookup(key);
		if (value != null) {
			return (T) value;
		}

		ReentrantLock lock = new ReentrantLock();
		try {
			lock.lock();
			value = lookup(key);
			if (value != null) {
				return (T) value;
			}
			value = valueLoader.call();
			Object storeValue = toStoreValue(valueLoader.call());
			put(key, storeValue);
			return (T) value;
		} catch (Exception e) {
			try {
				Class<?> c = Class.forName("org.springframework.cache.Cache$ValueRetrievalException");
				Constructor<?> constructor = c.getConstructor(Object.class, Callable.class, Throwable.class);
				RuntimeException exception = (RuntimeException) constructor.newInstance(key, valueLoader, e.getCause());
				throw exception;
			} catch (Exception e1) {
				throw new IllegalStateException(e1);
			}
		} finally {
			lock.unlock();
		}
	}

	@Override
	public void put(Object key, Object value) {
		logger.debug("put(Object key, Object value), the cache'name is {},the key is : {}", this.name,key); 
		if (!super.isAllowNullValues() && value == null) {
			this.evict(key);
			return;
		}
		long expire = getExpire();
		if (expire > 0) {
			redisTemplate.opsForValue().set(getKey(key), toStoreValue(value), expire, TimeUnit.MILLISECONDS);
		} else {
			redisTemplate.opsForValue().set(getKey(key), toStoreValue(value));
		}
		pushMessage(key);
		caffeineCache.put(key, value);
	}

	@Override
	public ValueWrapper putIfAbsent(Object key, Object value) {
		logger.debug("ValueWrapper putIfAbsent(Object key, Object value), the cache'name is {},the key is : {}", this.name,key); 
		Object cacheKey = getKey(key);
		Object prevValue = null;
		// 考虑使用分布式锁,或者将redis的setIfAbsent改为原子性操作
		synchronized (key) {
			prevValue = redisTemplate.opsForValue().get(cacheKey);
			if (prevValue == null) {
				long expire = getExpire();
				if (expire > 0) {
					redisTemplate.opsForValue().set(getKey(key), toStoreValue(value), expire, TimeUnit.MILLISECONDS);
				} else {
					redisTemplate.opsForValue().set(getKey(key), toStoreValue(value));
				}
				pushMessage(key);
				caffeineCache.put(key, toStoreValue(value));
			}
		}
		return toValueWrapper(prevValue);
	}

	@Override
	public void evict(Object key) {
		// 先清除redis中缓存数据,然后清除caffeine中的缓存,避免短时间内如果先清除caffeine缓存后其他请求会再从redis里加载到caffeine中
		logger.debug("evict(Object key), the cache'name is {},the key is : {}",this.name, key); 
		redisTemplate.delete(getKey(key));
		pushMessage(key);
		caffeineCache.invalidate(key);
	}

	@Override
	public void clear() {
		logger.debug("clear() the cache'name is {}",this.name); 
		// 先清除redis中缓存数据,然后清除caffeine中的缓存,避免短时间内如果先清除caffeine缓存后其他请求会再从redis里加载到caffeine中
		Set<Object> keys = redisTemplate.keys(this.name.concat(":"));
		for (Object key : keys) {
			redisTemplate.delete(key);
		}
		pushMessage(null);
		caffeineCache.invalidateAll();
	}

	@Override
	protected Object lookup(Object key) {
		logger.debug("Object lookup(Object key),the cache'name is {}, the key is : {}", this.name,key); 
		Object cacheKey = getKey(key);
		Object value = caffeineCache.getIfPresent(key);
		if (value != null) {
			logger.debug("get cache from caffeine, the cache'name is {},the key is : {}",this.name, cacheKey);
			return value;
		}
		value = redisTemplate.opsForValue().get(cacheKey);
		if (value != null) {
			logger.debug("get cache from redis and put in caffeine, the cache'name is {},the key is : {}", this.name,cacheKey);
			caffeineCache.put(key, value);
		}
		return value;
	}

	private Object getKey(Object key) {
		return this.name.concat(":").concat(
				StringUtils.isEmpty(cachePrefix) ? key.toString() : cachePrefix.concat(":").concat(key.toString()));
	}

	private long getExpire() {
		long expire = defaultExpiration;
		Long cacheNameExpire = expires.get(this.name);
		return cacheNameExpire == null ? expire : cacheNameExpire.longValue();
	}
	
	/**
	 * 缓存变更时通知其他节点清理本地缓存
	 * @param key 待清理的键
	 */
	private void pushMessage(Object key) {
		logger.debug("push(CacheMessage message) 缓存变更时通知其他节点清理本地缓存,the cache'name is {}, the key is : {}",this.name, key); 
		redisTemplate.convertAndSend(topic, new CacheMessage(cacheRedisCaffeineProperties.getLocalIp(),this.name,key));
	}
	/**
	 * 清理本地缓存
	 * @param key
	 */
	public void clearLocal(Object key) {
		logger.debug("clear local cache, the cache'name is {}, the key is : {}",this.name, key);
		if (key == null) {
			caffeineCache.invalidateAll();
		} else {
			caffeineCache.invalidate(key);
		}
	}

}

RedisCaffeineCache.java继承于AbstractValueAdaptingCache,实现spring的cache。可以说,我们实现了一套全新的,实现spring规范的cache。当然,在遵守spring规范下,我们同时实现了《两级缓存数据流原理》。

5、缓存管理器RedisCaffeineCacheManager.java


package com.demo.example.security.core.cache;

import java.util.Collection;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.data.redis.core.RedisTemplate;

import com.demo.example.security.core.cache.CacheRedisCaffeineProperties;
import com.github.benmanes.caffeine.cache.Caffeine;

public class RedisCaffeineCacheManager implements CacheManager {

	/**
	 * 日志.
	 */
	private final Logger logger = LoggerFactory.getLogger(RedisCaffeineCacheManager.class);

	/**
	 * 管理器中已经建立的缓存组.
	 */
	private ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>();
	/**
	 * 缓存配置参数.
	 */
	private CacheRedisCaffeineProperties cacheRedisCaffeineProperties;
	/**
	 * redis连接模板.
	 */
	private RedisTemplate<Object, Object> redisTemplate;
	/**
	 * 是否动态创建缓存组,每组独立一个缓存对象
	 */
	private boolean dynamic = true;
	/**
	 * 管理器中已经建立的缓存组的名称.
	 */
	private Set<String> cacheNames;

	/**
	 * 构造函数.
	 * 
	 * @param cacheRedisCaffeineProperties
	 *            缓存配置参数
	 * @param redisTemplate
	 *            redis连接模板
	 */
	public RedisCaffeineCacheManager(CacheRedisCaffeineProperties cacheRedisCaffeineProperties,
			RedisTemplate<Object, Object> redisTemplate) {
		super();
		this.cacheRedisCaffeineProperties = cacheRedisCaffeineProperties;
		this.redisTemplate = redisTemplate;
		this.dynamic = cacheRedisCaffeineProperties.isDynamic();
		this.cacheNames = cacheRedisCaffeineProperties.getCacheNames();
		if (this.dynamic == false) {
			logger.debug("create cache instance, the cache name is : {}", "localonly");
			Cache cache = new RedisCaffeineCache("localonly", redisTemplate, caffeineCacheBuild(),
					cacheRedisCaffeineProperties);
			cacheMap.putIfAbsent("localonly", cache);
		}
	}

	/**
	 * 获取缓存实列.
	 */
	@Override
	public Cache getCache(String name) {
		Cache cache = cacheMap.get(name);
		if (cache == null) {
			if (this.dynamic) {
				logger.debug("create cache instance, the cache name is : {}", name);
				cache = new RedisCaffeineCache(name, redisTemplate, caffeineCacheBuild(), cacheRedisCaffeineProperties);
				Cache oldCache = cacheMap.putIfAbsent(name, cache);
				if (oldCache != null) {
					cache = oldCache;
				}
			} else {
				if (cacheNames.contains(name) || cacheNames.size()==0) {
					cache = cacheMap.get("localonly");
				} else {
					//系统报错,缓存没有纳入管理
					logger.error("[缓存组={}]未纳入缓存管理,请无配置缓存注解",name);
				}
			}
		}
		return cache;
	}

	/**
	 * 构建Caffeine缓存实列
	 * 
	 * @return Caffeine缓存实列
	 */
	private com.github.benmanes.caffeine.cache.Cache<Object, Object> caffeineCacheBuild() {
		Caffeine<Object, Object> cacheBuilder = Caffeine.newBuilder();
		if (cacheRedisCaffeineProperties.getCaffeine().getExpireAfterAccess() > 0) {
			cacheBuilder.expireAfterAccess(cacheRedisCaffeineProperties.getCaffeine().getExpireAfterAccess(),
					TimeUnit.MILLISECONDS);
		}
		if (cacheRedisCaffeineProperties.getCaffeine().getExpireAfterWrite() > 0) {
			cacheBuilder.expireAfterWrite(cacheRedisCaffeineProperties.getCaffeine().getExpireAfterWrite(),
					TimeUnit.MILLISECONDS);
		}
		if (cacheRedisCaffeineProperties.getCaffeine().getInitialCapacity() > 0) {
			cacheBuilder.initialCapacity(cacheRedisCaffeineProperties.getCaffeine().getInitialCapacity());
		}
		if (cacheRedisCaffeineProperties.getCaffeine().getMaximumSize() > 0) {
			cacheBuilder.maximumSize(cacheRedisCaffeineProperties.getCaffeine().getMaximumSize());
		}
		if (cacheRedisCaffeineProperties.getCaffeine().getRefreshAfterWrite() > 0) {
			cacheBuilder.refreshAfterWrite(cacheRedisCaffeineProperties.getCaffeine().getRefreshAfterWrite(),
					TimeUnit.MILLISECONDS);
		}
		return cacheBuilder.build();
	}

	/**
	 * 获取缓存组的名称.
	 */
	@Override
	public Collection<String> getCacheNames() {
		if (this.dynamic) {
			return this.cacheMap.keySet();
		} else {
			return this.cacheNames;
		}
	}

	/**
	 * 清除本地缓存.
	 * 
	 * @param cacheMessage
	 */
	public void clearLocal(CacheMessage cacheMessage) {
		Cache cache = cacheMap.get(cacheMessage.getCacheName());
		if (cache == null) {
			return;
		}
		RedisCaffeineCache redisCaffeineCache = (RedisCaffeineCache) cache;
		redisCaffeineCache.clearLocal(cacheMessage.getKey());
	}
}

这个缓管理器主要由三个方面组成。

A、构造函数,接受参数和初始化。

B、spring规范中CacheManager接口必须要实现的两个接口:缓存实例和管理的实例的名称集合。

C、某缓存实例的缓存清除方法,以供消息监听器调用.

6、配置文件CacheRedisCaffeineConfiguration.java

package com.demo.example.security.core.cache;

import java.net.InetAddress;
import java.net.UnknownHostException;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;

@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@EnableConfigurationProperties(CacheRedisCaffeineProperties.class)
public class CacheRedisCaffeineConfiguration {
	
	private final Logger logger = LoggerFactory.getLogger(CacheRedisCaffeineConfiguration.class);
	
	@Autowired
	private CacheRedisCaffeineProperties cacheRedisCaffeineProperties;

	@Bean
	public RedisCaffeineCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) {
		if (StringUtils.isBlank(cacheRedisCaffeineProperties.getLocalIp()) || "127.0.0.1".equals(cacheRedisCaffeineProperties.getLocalIp())) {
			//获取本机的ip地址
			try {
				InetAddress address = InetAddress.getLocalHost();
				String host = address.getHostAddress();
				cacheRedisCaffeineProperties.setLocalIp(host);
			} catch (UnknownHostException e) {
				logger.error("获取ip错误");
			}
		}
		return new RedisCaffeineCacheManager(cacheRedisCaffeineProperties, redisTemplate);
	}

	/**
	 * 注册一个RedisMessageListerContainer
	 * @param redisTemplate
	 * @param redisCaffeineCacheManager
	 * @return
	 */
	@Bean
	public RedisMessageListenerContainer cacheRedisCaffeineContainer(RedisTemplate<Object, Object> redisTemplate,
			RedisCaffeineCacheManager redisCaffeineCacheManager) {
		RedisMessageListenerContainer container = new RedisMessageListenerContainer();
		container.setConnectionFactory(redisTemplate.getConnectionFactory());
		CacheMessageListener cacheMessageListener = new CacheMessageListener(redisTemplate, redisCaffeineCacheManager,cacheRedisCaffeineProperties);
		container.addMessageListener(cacheMessageListener,
				new ChannelTopic(cacheRedisCaffeineProperties.getRedis().getTopic()));
		return container;
	}
}

这个配置文件,在配置文件RedisAutoConfiguration.class和属性文件CacheRedisCaffeineProperties.class之后执行;它注册了我们建立的缓存管理器,和一个redis消息的监听容器.

四、优缺点

1、成功实现两级缓存。

2、消息通知清除缓存,不清除自身发送的。

3、缓存创建比较费时,后续继续跟进。

猜你喜欢

转载自blog.csdn.net/lxhjh/article/details/84939198