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的更多相关文章

  1. android 双缓存机制

    废话不多说,直接贴代码! 所谓的双缓存,第一就是缓存在内存里面,第二就是缓存在SD卡里面,当你需要加载数据时,先去内存缓存中查找,如果没有再去SD卡中查找,并且用户可以自选使用哪种缓存! 缓存内存和缓 ...

  2. jetty 最后版本类库树, 基本上大多数应用都够了

    d:\jetty-distribution-8.1.17.v20150415\lib\annotations\javax.annotation-1.1.0.v201108011116.jarjavax ...

  3. 《Android源码设计模式》学习笔记之ImageLoader

    微信公众号:CodingAndroid cnblog:http://www.cnblogs.com/angel88/ CSDN:http://blog.csdn.net/xinpengfei521 需 ...

  4. Asp.Net Core微服务初体验

    ASP.Net Core的基本配置 .在VS中调试的时候有很多修改Web应用运行端口的方法.但是在开发.调试微服务应用的时候可能需要同时在不同端口上开启多个服务器的实例,因此下面主要看看如何通过命令行 ...

  5. Android为TV端助力 双缓存机制

    废话不多说,直接贴代码! 所谓的双缓存,第一就是缓存在内存里面,第二就是缓存在SD卡里面,当你需要加载数据时,先去内存缓存中查找,如果没有再去SD卡中查找,并且用户可以自选使用哪种缓存! 缓存内存和缓 ...

  6. hbase源码系列(十三)缓存机制MemStore与Block Cache

    这一章讲hbase的缓存机制,这里面涉及的内容也是比较多,呵呵,我理解中的缓存是保存在内存中的特定的便于检索的数据结构就是缓存. 之前在讲put的时候,put是被添加到Store里面,这个Store是 ...

  7. Android图片二级缓存

    点击下载源代码 想起刚開始写代码的时候,领导叫我写一个头像下载的方法,当时屁颠屁颠就写了一个图片下载的,每次都要去网络上请求,最后直接被pass掉了 当时的思路是这种 后来渐渐地就知道了有二级缓存这东 ...

  8. 基本的数据类型分析----java.lang.Number类及其子类分析

    本文转自http://blog.csdn.net/springcsc1982/article/details/8788345 感谢作者 编写了一个测试程序,如下: int a = 1000, b= 1 ...

  9. 13 hbase源码系列(十三)缓存机制MemStore与Block Cache

    这一章讲hbase的缓存机制,这里面涉及的内容也是比较多,呵呵,我理解中的缓存是保存在内存中的特定的便于检索的数据结构就是缓存. 之前在讲put的时候,put是被添加到Store里面,这个Store是 ...

随机推荐

  1. windows修改docker的默认存放位置

    docker默认存储到c盘,我需要移动到其他盘. 参考了网上很多资料,结果要么移动不了,要么重启docker就回到c盘了. 最后参考docker的官方论坛,找到了解决方案.https://forums ...

  2. linux的CentOS、Ubuntu、Debian三个比较异同

    Linux有非常多的发行版本,从性质上划分,大体分为由商业公司维护的商业版本与由开源社区维护的免费发行版本.商业版本以Redhat为代表,开源社区版本则以debian为代表.这些版本各有不同的特点,在 ...

  3. keepalive+nginx

    1Nginx+keepAlived负载均衡高可用1.1Nginx+keepAlive架构图 1.1.1主机宕机 1.1.2主机恢复 1.1.3高可用环境 两台nginx,一主一备:192.168.10 ...

  4. 微信浏览器 video - android适配

    阶段一: 直接裸用 video 标签, 安卓 - 会重新弹一个播放层, 和之前video的父盒子错位, 要多丑有多丑, 体验要多烂有多烂. 阶段二: video添加以下属性, 安卓可实现内联播放, 但 ...

  5. input file 上传文件类型控制

    文件类型 accept *.3gpp audio/3gpp, video/3gpp.ac3 audio/ac3.asf allpication/vnd.ms-asf.au audio/basic.cs ...

  6. 【串线篇】SpringMvc框架乱码

    提交的数据可能有乱码: * 请求乱码: *      GET请求:改server.xml:在8080端口处URIEncoding="UTF-8" *      POST请求: * ...

  7. JavaSE---main方法解读

    1.概述 1.1 java程序入口:main方法 public static void main(String[] args){} a,public:java类由JVM调用,为了让JVM自由调用mai ...

  8. iSkysoft iMedia Converter Deluxe Mac如何制作视频?视频格式转换工具制作动图的方法

    使用iSkysoft iMedia Converter Deluxe Mac如何制作视频?使用视频格式转换工具,你可以轻松进行动图或视频的制作,也可以把你喜欢的视频的某一段提取出来,制作成你自己风格的 ...

  9. Quartz.Net 任务调度之传递参数(2)

    1.jobDetail //添加 //Key:Value jobDetail.JobDataMap.Add("张翼德", "张翼德"); jobDetail.J ...

  10. 【leetcode】756. Pyramid Transition Matrix

    题目如下: We are stacking blocks to form a pyramid. Each block has a color which is a one letter string, ...