上一篇Spring Boot Cache + redis 设置有效时间和自动刷新缓存,时间支持在配置文件中配置,说了一种时间方式,直接扩展注解的Value值,如:java
@Override @Cacheable(value = "people#${select.cache.timeout:1800}#${select.cache.refresh:600}", key = "#person.id", sync = true) public Person findOne(Person person, String a, String[] b, List<Long> c) { Person p = personRepository.findOne(person.getId()); System.out.println("为id、key为:" + p.getId() + "数据作了缓存"); System.out.println(redisTemplate); return p; }
可是这种方式有一个弊端就是破坏了原有Spring Cache架构,致使若是后期想换缓存就会去改不少代码。git
RedisCacheManager能够在配置CacheManager的Bean的时候指定过时时间,如:github
@Bean public RedisCacheManager cacheManager(RedisTemplate<String, Object> redisTemplate) { RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate); // 开启使用缓存名称最为key前缀 redisCacheManager.setUsePrefix(true); //这里能够设置一个默认的过时时间 单位是秒 redisCacheManager.setDefaultExpiration(redisDefaultExpiration); // 设置缓存的过时时间 Map<String, Long> expires = new HashMap<>(); expires.put("people", 1000); redisCacheManager.setExpires(expires); return redisCacheManager; }
咱们借鉴一下redisCacheManager.setExpires(expires)思路,进行扩展。直接新建一个CacheTime类,来存过时时间和自动刷新时间。redis
在RedisCacheManager调用getCache(name)获取缓存的时候,当没有找到缓存的时候会调用getMissingCache(String cacheName)来新建缓存。在新建缓存的时候咱们能够在扩展的Map<String, CacheTime> cacheTimes里面根据key获取CacheTime进而拿到有效时间和自动刷新时间。spring
CacheTime:缓存
/** * @author yuhao.wang */ public class CacheTime { public CacheTime(long preloadSecondTime, long expirationSecondTime) { this.preloadSecondTime = preloadSecondTime; this.expirationSecondTime = expirationSecondTime; } /** * 缓存主动在失效前强制刷新缓存的时间 * 单位:秒 */ private long preloadSecondTime = 0; /** * 缓存有效时间 */ private long expirationSecondTime; public long getPreloadSecondTime() { return preloadSecondTime; } public long getExpirationSecondTime() { return expirationSecondTime; } }
和上一篇的CustomizedRedisCache类同样,主要解决:架构
CustomizedRedisCache:并发
/** * 自定义的redis缓存 * * @author yuhao.wang */ public class CustomizedRedisCache extends RedisCache { private static final Logger logger = LoggerFactory.getLogger(CustomizedRedisCache.class); private CacheSupport getCacheSupport() { return SpringContextUtils.getBean(CacheSupport.class); } private final RedisOperations redisOperations; private final byte[] prefix; /** * 缓存主动在失效前强制刷新缓存的时间 * 单位:秒 */ private long preloadSecondTime = 0; /** * 缓存有效时间 */ private long expirationSecondTime; public CustomizedRedisCache(String name, byte[] prefix, RedisOperations<? extends Object, ? extends Object> redisOperations, long expiration, long preloadSecondTime) { super(name, prefix, redisOperations, expiration); this.redisOperations = redisOperations; // 指定有效时间 this.expirationSecondTime = expiration; // 指定自动刷新时间 this.preloadSecondTime = preloadSecondTime; this.prefix = prefix; } public CustomizedRedisCache(String name, byte[] prefix, RedisOperations<? extends Object, ? extends Object> redisOperations, long expiration, long preloadSecondTime, boolean allowNullValues) { super(name, prefix, redisOperations, expiration, allowNullValues); this.redisOperations = redisOperations; // 指定有效时间 this.expirationSecondTime = expiration; // 指定自动刷新时间 this.preloadSecondTime = preloadSecondTime; this.prefix = prefix; } /** * 重写get方法,获取到缓存后再次取缓存剩余的时间,若是时间小余咱们配置的刷新时间就手动刷新缓存。 * 为了避免影响get的性能,启用后台线程去完成缓存的刷。 * 而且只放一个线程去刷新数据。 * * @param key * @return */ @Override public ValueWrapper get(final Object key) { RedisCacheKey cacheKey = getRedisCacheKey(key); String cacheKeyStr = getCacheKey(key); // 调用重写后的get方法 ValueWrapper valueWrapper = this.get(cacheKey); if (null != valueWrapper) { // 刷新缓存数据 refreshCache(key, cacheKeyStr); } return valueWrapper; } /** * 重写父类的get函数。 * 父类的get方法,是先使用exists判断key是否存在,不存在返回null,存在再到redis缓存中去取值。这样会致使并发问题, * 假若有一个请求调用了exists函数判断key存在,可是在下一时刻这个缓存过时了,或者被删掉了。 * 这时候再去缓存中获取值的时候返回的就是null了。 * 能够先获取缓存的值,再去判断key是否存在。 * * @param cacheKey * @return */ @Override public RedisCacheElement get(final RedisCacheKey cacheKey) { Assert.notNull(cacheKey, "CacheKey must not be null!"); // 根据key获取缓存值 RedisCacheElement redisCacheElement = new RedisCacheElement(cacheKey, fromStoreValue(lookup(cacheKey))); // 判断key是否存在 Boolean exists = (Boolean) redisOperations.execute(new RedisCallback<Boolean>() { @Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException { return connection.exists(cacheKey.getKeyBytes()); } }); if (!exists.booleanValue()) { return null; } return redisCacheElement; } /** * 刷新缓存数据 */ private void refreshCache(Object key, String cacheKeyStr) { Long ttl = this.redisOperations.getExpire(cacheKeyStr); if (null != ttl && ttl <= CustomizedRedisCache.this.preloadSecondTime) { // 尽可能少的去开启线程,由于线程池是有限的 ThreadTaskUtils.run(new Runnable() { @Override public void run() { // 加一个分布式锁,只放一个请求去刷新缓存 RedisLock redisLock = new RedisLock((RedisTemplate) redisOperations, cacheKeyStr + "_lock"); try { if (redisLock.lock()) { // 获取锁以后再判断一下过时时间,看是否须要加载数据 Long ttl = CustomizedRedisCache.this.redisOperations.getExpire(cacheKeyStr); if (null != ttl && ttl <= CustomizedRedisCache.this.preloadSecondTime) { // 经过获取代理方法信息从新加载缓存数据 CustomizedRedisCache.this.getCacheSupport().refreshCacheByKey(CustomizedRedisCache.super.getName(), cacheKeyStr); } } } catch (Exception e) { logger.info(e.getMessage(), e); } finally { redisLock.unlock(); } } }); } } public long getExpirationSecondTime() { return expirationSecondTime; } /** * 获取RedisCacheKey * * @param key * @return */ public RedisCacheKey getRedisCacheKey(Object key) { return new RedisCacheKey(key).usePrefix(this.prefix) .withKeySerializer(redisOperations.getKeySerializer()); } /** * 获取RedisCacheKey * * @param key * @return */ public String getCacheKey(Object key) { return new String(getRedisCacheKey(key).getKeyBytes()); } }
主要扩展经过getCache(String name)方法获取缓存的时候,当没有找到缓存回去调用getMissingCache(String cacheName)来新建缓存。app
CustomizedRedisCacheManager:框架
/** * 自定义的redis缓存管理器 * 支持方法上配置过时时间 * 支持热加载缓存:缓存即将过时时主动刷新缓存 * * @author yuhao.wang */ public class CustomizedRedisCacheManager extends RedisCacheManager { private static final Logger logger = LoggerFactory.getLogger(CustomizedRedisCacheManager.class); /** * 父类dynamic字段 */ private static final String SUPER_FIELD_DYNAMIC = "dynamic"; /** * 父类cacheNullValues字段 */ private static final String SUPER_FIELD_CACHENULLVALUES = "cacheNullValues"; RedisCacheManager redisCacheManager = null; // 0 - never expire private long defaultExpiration = 0; private Map<String, CacheTime> cacheTimes = null; public CustomizedRedisCacheManager(RedisOperations redisOperations) { super(redisOperations); } public CustomizedRedisCacheManager(RedisOperations redisOperations, Collection<String> cacheNames) { super(redisOperations, cacheNames); } public RedisCacheManager getInstance() { if (redisCacheManager == null) { redisCacheManager = SpringContextUtils.getBean(RedisCacheManager.class); } return redisCacheManager; } /** * 获取过时时间 * * @return */ public long getExpirationSecondTime(String name) { if (StringUtils.isEmpty(name)) { return 0; } CacheTime cacheTime = null; if (!CollectionUtils.isEmpty(cacheTimes)) { cacheTime = cacheTimes.get(name); } Long expiration = cacheTime != null ? cacheTime.getExpirationSecondTime() : defaultExpiration; return expiration < 0 ? 0 : expiration; } /** * 获取自动刷新时间 * * @return */ private long getPreloadSecondTime(String name) { // 自动刷新时间,默认是0 CacheTime cacheTime = null; if (!CollectionUtils.isEmpty(cacheTimes)) { cacheTime = cacheTimes.get(name); } Long preloadSecondTime = cacheTime != null ? cacheTime.getPreloadSecondTime() : 0; return preloadSecondTime < 0 ? 0 : preloadSecondTime; } /** * 建立缓存 * * @param cacheName 缓存名称 * @return */ public CustomizedRedisCache getMissingCache(String cacheName) { // 有效时间,初始化获取默认的有效时间 Long expirationSecondTime = getExpirationSecondTime(cacheName); // 自动刷新时间,默认是0 Long preloadSecondTime = getPreloadSecondTime(cacheName); logger.info("缓存 cacheName:{},过时时间:{}, 自动刷新时间:{}", cacheName, expirationSecondTime, preloadSecondTime); // 是否在运行时建立Cache Boolean dynamic = (Boolean) ReflectionUtils.getFieldValue(getInstance(), SUPER_FIELD_DYNAMIC); // 是否容许存放NULL Boolean cacheNullValues = (Boolean) ReflectionUtils.getFieldValue(getInstance(), SUPER_FIELD_CACHENULLVALUES); return dynamic ? new CustomizedRedisCache(cacheName, (this.isUsePrefix() ? this.getCachePrefix().prefix(cacheName) : null), this.getRedisOperations(), expirationSecondTime, preloadSecondTime, cacheNullValues) : null; } /** * 根据缓存名称设置缓存的有效时间和刷新时间,单位秒 * * @param cacheTimes */ public void setCacheTimess(Map<String, CacheTime> cacheTimes) { this.cacheTimes = (cacheTimes != null ? new ConcurrentHashMap<String, CacheTime>(cacheTimes) : null); } /** * 设置默认的过去时间, 单位:秒 * * @param defaultExpireTime */ @Override public void setDefaultExpiration(long defaultExpireTime) { super.setDefaultExpiration(defaultExpireTime); this.defaultExpiration = defaultExpireTime; } @Deprecated @Override public void setExpires(Map<String, Long> expires) { } }
@Bean public RedisCacheManager cacheManager(RedisTemplate<String, Object> redisTemplate) { CustomizedRedisCacheManager redisCacheManager = new CustomizedRedisCacheManager(redisTemplate); // 开启使用缓存名称最为key前缀 redisCacheManager.setUsePrefix(true); //这里能够设置一个默认的过时时间 单位是秒 redisCacheManager.setDefaultExpiration(redisDefaultExpiration); // 设置缓存的过时时间和自动刷新时间 Map<String, CacheTime> cacheTimes = new HashMap<>(); cacheTimes.put("people", new CacheTime(selectCacheTimeout, selectCacheRefresh)); cacheTimes.put("people1", new CacheTime(120, 115)); cacheTimes.put("people2", new CacheTime(120, 115)); redisCacheManager.setCacheTimess(cacheTimes); return redisCacheManager; }
剩余的建立切面来缓存方法信息请看上篇
源码地址: https://github.com/wyh-spring-ecosystem-student/spring-boot-student/tree/releases
spring-boot-student-cache-redis-2 工程
为监控而生的多级缓存框架 layering-cache这是我开源的一个多级缓存框架的实现,若是有兴趣能够看一下