首页 技术 正文
技术 2022年11月14日
0 收藏 601 点赞 2,545 浏览 4503 个字

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;
}
}
}
相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:8,999
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,511
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,357
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,140
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,770
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,848