zoukankan      html  css  js  c++  java
  • 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;
            }
        }
    }
  • 相关阅读:
    node.js 的简单介绍
    vue浅析
    rest_framework的分页器组件配置与使用
    restframwork组件的权限认证
    关于and和or的运算
    restframwork组件的使用
    实现简单的子页面传值给父页面
    Django使用orm模块时想看多对对数据关系的配置
    Django更新数据库表时无法执行表修改 指定Django要使用的数据库
    图论-kruskal算法-稀疏图
  • 原文地址:https://www.cnblogs.com/zhshlimi/p/12097674.html
Copyright © 2011-2022 走看看