本文共 16853 字,大约阅读时间需要 56 分钟。
本文参考了。
与一致:
空值的包装
和缓存值的包装,所以就不用实现Cache接口了,直接实现AbstractValueAdaptingCache抽象类1.@EnableCaching
:启用spring cache缓存,在spring boot的启动类或配置类上需要加上此注解才会生效
2.yml
# redis-starter的配置spring: cache: cache-names: cache1,cache2,cache3 redis: timeout: 10000 pool: max-idle: 10 min-idle: 2 max-active: 10 max-wait: 3000#自定义配置。expire统一单位为毫秒cache: multi: cacheNames: cache1,cache2,cache3 ehcache: expireAfterWrite: 5000 maxEntry: 1000 redis: defaultExpiration: 60000 expires: cache1: 50000 cache2: 70000 cache3: 70000
3.POM
依赖项org.springframework.boot spring-boot-starter-redis 1.4.3.RELEASE org.ehcache ehcache 3.5.2
定义properties配置属性类
@ConfigurationProperties(prefix = "cache.multi")@Datapublic class RedisEhcacheProperties { private SetcacheNames = new HashSet<>(); /** 是否存储空值,默认true,防止缓存穿透*/ private boolean cacheNullValues = true; /** 是否动态根据cacheName创建Cache的实现,默认true*/ private boolean dynamic = true; /** 缓存key的前缀*/ private String cachePrefix; private Redis redis = new Redis(); private Ehcache ehcache = new Ehcache(); public boolean isCacheNullValues() { return cacheNullValues; } @Data public class Redis { /** 全局过期时间,单位毫秒,默认不过期*/ private long defaultExpiration = 0; /** 每个cacheName的过期时间,单位毫秒,优先级比defaultExpiration高*/ private Map expires = new HashMap<>(); /** 缓存更新时通知其他节点的topic名称*/ private String topic = "cache:redis:ehcache:topic"; } @Data public class Ehcache { /** * 访问后过期时间,单位毫秒 */// private long expireAfterAccess; /** * 写入后过期时间,单位毫秒 */ private long expireAfterWrite; /** * 初始化大小 */// private int initialCapacity; /** * 每个ehcache最大缓存对象个数,超过此数量时按照失效策略(默认为LRU) */ private long maxEntry = 500; }}
RedisEhcacheCache
import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service;@Slf4jpublic class RedisEhcacheCache extends AbstractValueAdaptingCache { private String name; private RedisTemplate
实现CacheManager接口
import lombok.extern.slf4j.Slf4j;import org.ehcache.config.CacheConfiguration;import org.ehcache.config.builders.*;import org.springframework.cache.Cache;import org.springframework.cache.CacheManager;import org.springframework.data.redis.core.RedisTemplate;import java.time.Duration;import java.util.Collection;import java.util.Set;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.ConcurrentMap;@Slf4jpublic class RedisEhcacheCacheManager implements CacheManager { private ConcurrentMapcacheMap = new ConcurrentHashMap (); private RedisEhcacheProperties redisEhcacheProperties; private RedisTemplate redisTemplate; private boolean dynamic = true; private Set cacheNames; private org.ehcache.CacheManager ehCacheManager; private CacheConfiguration configuration; public RedisEhcacheCacheManager(RedisEhcacheProperties redisEhcacheProperties, RedisTemplate redisTemplate) { super(); this.redisEhcacheProperties = redisEhcacheProperties; this.redisTemplate = redisTemplate; this.dynamic = redisEhcacheProperties.isDynamic(); this.cacheNames = redisEhcacheProperties.getCacheNames(); setAboutEhCache(); } private void setAboutEhCache(){ long ehcacheExpire = redisEhcacheProperties.getEhcache().getExpireAfterWrite(); this.configuration = CacheConfigurationBuilder .newCacheConfigurationBuilder(Object.class, Object.class, ResourcePoolsBuilder.heap(redisEhcacheProperties.getEhcache().getMaxEntry())) .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(ehcacheExpire))) .build(); this.ehCacheManager = CacheManagerBuilder .newCacheManagerBuilder() .build(); this.ehCacheManager.init(); } @Override public Cache getCache(String name) { Cache cache = cacheMap.get(name); if(cache != null) { return cache; } if(!dynamic && !cacheNames.contains(name)) { return cache; } cache = new RedisEhcacheCache(name, redisTemplate, getEhcache(name), redisEhcacheProperties); Cache oldCache = cacheMap.putIfAbsent(name, cache); log.debug("create cache instance, the cache name is : {}", name); return oldCache == null ? cache : oldCache; } public org.ehcache.Cache getEhcache(String name){ org.ehcache.Cache res = ehCacheManager.getCache(name, Object.class, Object.class); if(res != null){ return res; } return ehCacheManager.createCache(name, configuration); } @Override public Collection getCacheNames() { return this.cacheNames; } public void clearLocal(String cacheName, Object key, Integer sender) { Cache cache = cacheMap.get(cacheName); if(cache == null) { return ; } RedisEhcacheCache redisEhcacheCache = (RedisEhcacheCache) cache; //如果是发送者本身发送的消息,就不进行key的清除 if(redisEhcacheCache.getLocalCache().hashCode() != sender) { redisEhcacheCache.clearLocal(key); } }}
redis消息发布/订阅,传输的消息类
@Datapublic class CacheMessage implements Serializable { private static final long serialVersionUID = 5987219310442078193L; private String cacheName; private Object key; private Integer sender; public CacheMessage(String cacheName, Object key) { super(); this.cacheName = cacheName; this.key = key; } public CacheMessage(String cacheName, Object key, Integer sender) { super(); this.cacheName = cacheName; this.key = key; this.sender = sender; }}
监听redis消息需要实现MessageListener接口
import lombok.extern.slf4j.Slf4j;import org.springframework.data.redis.connection.Message;import org.springframework.data.redis.connection.MessageListener;import org.springframework.data.redis.core.RedisTemplate;/** * 监听redis消息需要实现MessageListener接口 */@Slf4jpublic class CacheMessageListener implements MessageListener { private RedisTemplateredisTemplate; private RedisEhcacheCacheManager redisEhcacheCacheManager; public CacheMessageListener(RedisTemplate redisTemplate, RedisEhcacheCacheManager redisEhcacheCacheManager) { super(); this.redisTemplate = redisTemplate; this.redisEhcacheCacheManager = redisEhcacheCacheManager; } @Override public void onMessage(Message message, byte[] pattern) { CacheMessage cacheMessage = (CacheMessage) redisTemplate.getValueSerializer().deserialize(message.getBody()); log.debug("recevice a redis topic message, clear local cache, the cacheName is {}, the key is {}", cacheMessage.getCacheName(), cacheMessage.getKey()); redisEhcacheCacheManager.clearLocal(cacheMessage.getCacheName(), cacheMessage.getKey(), cacheMessage.getSender()); }}
增加spring boot配置类
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.autoconfigure.AutoConfigureAfter;import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;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(RedisEhcacheProperties.class)public class CacheRedisEhcacheAutoConfiguration { @Autowired private RedisEhcacheProperties redisEhcacheProperties; @Bean public RedisEhcacheCacheManager cacheManager(RedisTemplateredisTemplate) { return new RedisEhcacheCacheManager(redisEhcacheProperties, redisTemplate); } @Bean @ConditionalOnBean(RedisEhcacheCacheManager.class) public RedisMessageListenerContainer redisMessageListenerContainer(RedisTemplate redisTemplate, RedisEhcacheCacheManager redisEhcacheCacheManager) { RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer(); redisMessageListenerContainer.setConnectionFactory(redisTemplate.getConnectionFactory()); CacheMessageListener cacheMessageListener = new CacheMessageListener(redisTemplate, redisEhcacheCacheManager); redisMessageListenerContainer.addMessageListener(cacheMessageListener, new ChannelTopic(redisEhcacheProperties.getRedis().getTopic())); return redisMessageListenerContainer; }}
缓存使用
//cacheManager = "cacheManager"可以不指定 @Cacheable(value = "gerritCache", key = "#projectName + '_' + #from + '_' + #to"/*, cacheManager = "cacheManager"*/) public UserVO get(long id) { logger.info("get by id from db"); UserVO user = new UserVO(); user.setId(id); user.setName("name" + id); user.setCreateTime(TimestampUtil.current()); return user; }
RedisCacheConfiguration
和我们自定义的CacheRedisEhcacheAutoConfiguration
都有注解:
@AutoConfigureAfter(RedisAutoConfiguration.class)
不过由于RedisCacheConfiguration
有:
@ConditionalOnMissingBean(CacheManager.class)
保证了唯一性:如果CacheRedisEhcacheAutoConfiguration
被执行了,那么RedisCacheConfiguration
就不会被执行。
我们可以基于这一点做一个二级缓存开关。在yml加入
cache: use2L: true #开启二级缓存
CacheRedisEhcacheAutoConfiguration加上(yml没有配置或者配置为false,二级缓存都不起作用):
@ConditionalOnProperty(name = "cache.use2L", havingValue = "true", matchIfMissing = false)
加上CacheConfig
对单独一级redis缓存进行配置:
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.cache.CacheManager;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.cache.RedisCacheManager;import org.springframework.data.redis.core.RedisTemplate;import java.util.HashMap;import java.util.Map;@Configuration@ConditionalOnProperty(name = "cache.use2L", havingValue = "false", matchIfMissing = true)@EnableConfigurationProperties(RedisEhcacheProperties.class)public class CacheConfig { @Autowired private RedisEhcacheProperties redisEhcacheProperties; @Bean public CacheManager cacheManager(RedisTemplate redisTemplate) { RedisCacheManager rcm = new RedisCacheManager(redisTemplate); //设置各个cache的缓存过期时间 Mapexpires = new HashMap<>(redisEhcacheProperties.getRedis().getExpires()); //毫秒->秒 expires.forEach((k, v) -> expires.put(k, v/1000)); rcm.setExpires(expires); rcm.setDefaultExpiration(redisEhcacheProperties.getRedis().getDefaultExpiration());//默认过期时间 return rcm; }}
ref:
转载地址:http://siztb.baihongyu.com/