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;
        }
    }
}
原文地址:https://www.cnblogs.com/zhshlimi/p/12097674.html