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是 ...
 
随机推荐
- go读写excel文件
			
首先,需要安装golang用来操作excel文档的类库: go get github.com/Luxurioust/excelize 一.excel文件创建与写入 package main impor ...
 - 目录---Asp.NETCore轻松学系列【目录】
			
随笔分类 - Asp.NETCore轻松学系列 Asp.NETCore轻松学系列阅读指引目录 摘要: 耗时两个多月,坚持写这个入门系列文章,就是想给后来者更好更快的上手体验,这个系列可以说是从入门到进 ...
 - SpringMVC学习(12):基于Hibernate+Spring+Spring MVC+Bootstrap的管理系统实现
			
到这里已经写到第12篇了,前11篇基本上把Spring MVC主要的内容都讲了,现在就直接上一个项目吧,希望能对有需要的朋友有一些帮助. 一.首先看一下项目结构: InfrastructureProj ...
 - 数组Array的十种使用方法
			
第一种方法 array toString () 将数组的元素全部转换为字符串; 第二种方法 array join ("这里是分隔符") 这种方法可以把数组的元素转换为字符串,并把分 ...
 - elasticsearch  分析器 分词器
			
参考:https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html 在全文搜索(Fu ...
 - iOS 点击按钮截屏
			
@interface CaptureViewController () @property (nonatomic, strong) UIImageView *backgrounView; //控制器背 ...
 - CentOS7 配置163 yum源(详细步骤)
			
CentOS7 配置163 yum源 1)下载repo文件 wget http://mirrors.163.com/.help/CentOS7-Base-163.repo 2)备份并替换系统的r ...
 - JavaScript值传递和引用传递
			
1、数据类型:boolean,null,undefined,String,Number,指向包含的数据,进行“值传递”: 2.非数据类型:Array,Function,Object,指向了一个内存地址 ...
 - H2数据库做单测数据库时踩到的坑
			
H2数据库用来做单测数据库,可以自定义初始化数据,不用担心数据库内容更改造成单测跑不过问题,不过H2数据库跟实际使用的Mysql还是有一定区别. 1. H2数据库不支持Mysql的批量更新功能,支持批 ...
 - 由于阿里云磁盘空间导致hadoop的yarn节点处于UNHEALTHY状态
			
最初使用的阿里云云盘只有50G 正常运行的hadoop集群突然无法正常运行了,web页面显示节点为UNHEALTHY 使用df -m命令,发现一些节点磁盘空间占用达到了99%,因此要扩容磁盘空间 1. ...