DoubleCache 指的是本地+redis两份缓存模式
本地缓存过期之后从redis读取新数据
redis缓存过期时,从业务里读取新数据.
设计原理: 利用 loadingCache的过期刷新来实现异步线程自动刷新,而不阻塞当前数据返回
后期优化: 远程刷新时,增加锁机制来避免多次调用业务数据.
import com.google.common.base.Strings; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import com.fasterxml.jackson.databind.JavaType; import com.ppmoney.ppmon.rotom.utils.text.JsonMapper; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.util.Assert; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.function.Function; import lombok.extern.slf4j.Slf4j; @Slf4j public class DoubleCache<V> { private static ExecutorService executorService = Executors.newFixedThreadPool(5); private static ListeningExecutorService service = MoreExecutors.listeningDecorator(executorService); private final int remoteExpireSeconds; private final int localExpireSeconds; private final LoadingCache<String, V> remoteCache; private final LoadingCache<String, V> localCache; private final V defaultValue; private final Function<String, V> function; private final StringRedisTemplate redisTemplate; private final String business; private final Class<V> clazz; private final JavaType javaType; private final CacheLoader<String, V> remoteCacheLoader = new CacheLoader<String, V>() { @Override public V load(String key) throws Exception { V result = function.apply(key); String redisKey = getRedisKey(key); redisTemplate.opsForValue().set(redisKey, JsonMapper.INSTANCE.toJson(result), remoteExpireSeconds, TimeUnit.SECONDS); // 本地不存数据,减少内存占用 return defaultValue; } @Override public ListenableFuture<V> reload(String key, V oldValue) throws Exception { log.info("redis缓存刷新.key:{}", key); ListenableFuture<V> result = service.submit(() -> function.apply(key)); String redisKey = getRedisKey(key); redisTemplate.opsForValue().set(redisKey, JsonMapper.INSTANCE.toJson(result.get()), remoteExpireSeconds, TimeUnit.SECONDS); // 本地不存数据,减少内存占用 return service.submit(() -> defaultValue); } }; private final CacheLoader<String, V> localCacheLoader = new CacheLoader<String, V>() { @Override public V load(String key) throws Exception { String redisKey = getRedisKey(key); String val = redisTemplate.opsForValue().get(redisKey); if (Strings.isNullOrEmpty(val)) { remoteCache.get(key); val = redisTemplate.opsForValue().get(redisKey); } if (Strings.isNullOrEmpty(val)) { return defaultValue; } return clazz != null ? JsonMapper.INSTANCE.fromJson(val, clazz) : JsonMapper.INSTANCE.fromJson(val, javaType); } @Override public ListenableFuture<V> reload(String key, V oldValue) throws Exception { log.info("本地缓存刷新.key:{}", key); String redisKey = getRedisKey(key); String val = redisTemplate.opsForValue().get(redisKey); if (Strings.isNullOrEmpty(val)) { remoteCache.get(key); val = redisTemplate.opsForValue().get(redisKey); } if (Strings.isNullOrEmpty(val)) { return service.submit(() -> defaultValue); } final V result = clazz != null ? JsonMapper.INSTANCE.fromJson(val, clazz) : JsonMapper.INSTANCE.fromJson(val, javaType); return service.submit(() -> result); } }; private String getRedisKey(String key) { return "g2:doubleCache:" + business + ":" + key; } public DoubleCache(String business, int localExpireSeconds, int remoteExpireSeconds, Function<String, V> function, StringRedisTemplate redisTemplate, V defaultV, Class<V> clazz, JavaType javaType) { Assert.isTrue(1 < remoteExpireSeconds, "远程缓存过期时间必须大于1"); Assert.isTrue(0 < localExpireSeconds, "本地缓存过期时间必须大于0"); Assert.isTrue(localExpireSeconds < remoteExpireSeconds, "远程缓存过期时间必须大于本地缓存过期时间"); Assert.isTrue(javaType != null || clazz != null, "clazz与javaType不能同时为空"); Assert.isTrue(defaultV != null, "defaulV不能为空"); Assert.isTrue(function != null, "function不能为空"); Assert.isTrue(redisTemplate != null, "redisTemplate不能为空"); Assert.isTrue(!Strings.isNullOrEmpty(business), "business不能为空"); this.clazz = clazz; this.javaType = javaType; this.localExpireSeconds = localExpireSeconds; this.remoteExpireSeconds = remoteExpireSeconds; this.business = business; this.function = function; this.defaultValue = defaultV; remoteCache = CacheBuilder.newBuilder() .maximumSize(10000) .initialCapacity(100) .refreshAfterWrite(remoteExpireSeconds - 1, TimeUnit.SECONDS) .softValues() .build(remoteCacheLoader); localCache = CacheBuilder.newBuilder() .maximumSize(10000) .initialCapacity(100) .refreshAfterWrite(localExpireSeconds, TimeUnit.SECONDS) .softValues() .build(localCacheLoader); this.redisTemplate = redisTemplate; } public V get(String key) { try { return localCache.get(key); } catch (Exception ex) { log.error("获取缓存异常!", ex); return null; } } }