DoubleCache
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;
}
}
}
DoubleCache的更多相关文章
- android 双缓存机制
废话不多说,直接贴代码! 所谓的双缓存,第一就是缓存在内存里面,第二就是缓存在SD卡里面,当你需要加载数据时,先去内存缓存中查找,如果没有再去SD卡中查找,并且用户可以自选使用哪种缓存! 缓存内存和缓 ...
- jetty 最后版本类库树, 基本上大多数应用都够了
d:\jetty-distribution-8.1.17.v20150415\lib\annotations\javax.annotation-1.1.0.v201108011116.jarjavax ...
- 《Android源码设计模式》学习笔记之ImageLoader
微信公众号:CodingAndroid cnblog:http://www.cnblogs.com/angel88/ CSDN:http://blog.csdn.net/xinpengfei521 需 ...
- Asp.Net Core微服务初体验
ASP.Net Core的基本配置 .在VS中调试的时候有很多修改Web应用运行端口的方法.但是在开发.调试微服务应用的时候可能需要同时在不同端口上开启多个服务器的实例,因此下面主要看看如何通过命令行 ...
- Android为TV端助力 双缓存机制
废话不多说,直接贴代码! 所谓的双缓存,第一就是缓存在内存里面,第二就是缓存在SD卡里面,当你需要加载数据时,先去内存缓存中查找,如果没有再去SD卡中查找,并且用户可以自选使用哪种缓存! 缓存内存和缓 ...
- hbase源码系列(十三)缓存机制MemStore与Block Cache
这一章讲hbase的缓存机制,这里面涉及的内容也是比较多,呵呵,我理解中的缓存是保存在内存中的特定的便于检索的数据结构就是缓存. 之前在讲put的时候,put是被添加到Store里面,这个Store是 ...
- Android图片二级缓存
点击下载源代码 想起刚開始写代码的时候,领导叫我写一个头像下载的方法,当时屁颠屁颠就写了一个图片下载的,每次都要去网络上请求,最后直接被pass掉了 当时的思路是这种 后来渐渐地就知道了有二级缓存这东 ...
- 基本的数据类型分析----java.lang.Number类及其子类分析
本文转自http://blog.csdn.net/springcsc1982/article/details/8788345 感谢作者 编写了一个测试程序,如下: int a = 1000, b= 1 ...
- 13 hbase源码系列(十三)缓存机制MemStore与Block Cache
这一章讲hbase的缓存机制,这里面涉及的内容也是比较多,呵呵,我理解中的缓存是保存在内存中的特定的便于检索的数据结构就是缓存. 之前在讲put的时候,put是被添加到Store里面,这个Store是 ...
随机推荐
- 解决div和img之间的空隙
div盒子和img之间有空隙之前也遇到过几次这问题,今天又遇到了特地来总结下. 先上代码和效果图: <!doctype html><html lang="en"& ...
- 【记录】Mybatis Generator生成数据对象Date/TimeStamp 查询时间格式化
Mybatis Generator是很好的工具帮助我们生成表映射关联代码,最近博主遇到一个问题,找了很久才解决, 就是用Mybatis Generator生成实体类的时候,Date 时间无法格式化输出 ...
- 后台执行循环(done &)
- 59th python下graphviz安装 砖
原文 摘录 感谢分享: https://www.cnblogs.com/liusx0303/p/9155305.html 参考链接:https://blog.csdn.net/u01325041 ...
- js console对象
js调试 根据信息的不同性质,console对象显示信息的方法,分别是一般信息console.log(),console.info().除错信息console.debug().警告提示console. ...
- 【leetcode】835. Image Overlap
题目如下: 解题思路:抛开移动的过程只看移动完成的结果,记图片左上角为顶点,正方形边长为board,要使得两个图片要有重叠,那么一定其中一张图片的顶点和另外一张图片的某一个点重合.假设图片A的顶点A( ...
- Axure 部件的交互样式
选中的 时 需要设置事件
- 重磅发布!阿里云推PostgreSQL 10 高可用版
摘要: 近日,阿里云重磅发布PostgreSQL 10 高可用本地SSD盘版,相比原 9.4 版本又新增了JSONB.BRIN索引.GROUPING SETS/CUBE/ROLLUP.UPSERT等多 ...
- WebView中shouldOverrideUrlLoading和onPageStarted方法的区别
WebView中的shouldOverrideUrlLoading和onPageStarted这两个方法就是可以捕获到跳转的url,然后进行一系列的操作,但是他们两到底有什么区别呢? 当点击页面中的链 ...
- 洛谷 P2522 [HAOI2011]Problem b (莫比乌斯反演+简单容斥)
题目描述 对于给出的n个询问,每次求有多少个数对(x,y),满足a≤x≤b,c≤y≤d,且gcd(x,y) = k,gcd(x,y)函数为x和y的最大公约数. 输入输出格式 输入格式: 第一行一个整数 ...