使用Google Guava Cache开发本地LRU缓存

举报
pchatcq 发表于 2021/08/31 10:25:44 2021/08/31
【摘要】 使用Google Guava Cache开发本地缓存 需求有些频繁访问,更新不太频繁的io密集型业务数据可以存在缓存中,减少业务数据库io压力,由于项目开发中没有redis可用,需要自己开发一个本地cache。缓存远端请求行为定义命中N/A返回缓存中的值未命中正常响应量刷新缓存并返回该值未命中正常响应量刷新缓存并返回该值未命中异常返回错误或者抛出异常过期规定时间内响应刷新缓存并返回该值过期...

使用Google Guava Cache开发本地缓存

需求

有些频繁访问,更新不太频繁的io密集型业务数据可以存在缓存中,减少业务数据库io压力,由于项目开发中没有redis可用,需要自己开发一个本地cache。

缓存 远端请求 行为定义
命中 N/A 返回缓存中的值
未命中 正常响应量 刷新缓存并返回该值
未命中 正常响应量 刷新缓存并返回该值
未命中 异常 返回错误或者抛出异常
过期 规定时间内响应 刷新缓存并返回该值
过期 规定时间内未响应 返回缓存中的过期值,后台仍需等待远端请求

实现代码

@Slf4j
public class LocalCache<K, V> {

    private final Cache<K, TimeValue<V>> lruCache;

    private final ExecutorService threadPool;

    /**
     * 当缓存失效,多个线程并发更新缓存时,用于存储更新当前K并发更新任务,防止缓存击穿问题
     */
    private final Map<K, Future<TimeValue<V>>> runningTasks = Maps.newConcurrentMap();

    public LocalCache(int initialCapacity, long maximumSize, Duration expireTime, ExecutorService threadPool) {
        this.threadPool = threadPool;
        lruCache =
                CacheBuilder.newBuilder()
                        .expireAfterWrite(expireTime)
                        .initialCapacity(initialCapacity)
                        .maximumSize(maximumSize)
                        .build();
    }

    /**
     * 获取缓存,过期时间为时间点LocalDateTime
     * @param key  缓存key
     * @param expireTime  过期时间点
     * @param maxWait  最大等待时间
     * @param function  获取缓存的函数
     * @return V
     */
    public V get(K key, LocalDateTime expireTime, Duration maxWait,
                 Function<? super K, ? extends V> function) {

        Objects.requireNonNull(expireTime);
        Objects.requireNonNull(maxWait);
        Objects.requireNonNull(function);

        return Optional.ofNullable(key)
                .map(ThrowingFunction.handleException(
                    k -> {
                        TimeValue<V> cacheValue = lruCache.get(k, () -> refresh(k, function).get());

                        Duration existDuration =
                                Duration.between(cacheValue.getCreateTime(), LocalDateTime.now());
                        Duration expireDuration =
                                Duration.between(cacheValue.getCreateTime(), expireTime);

                        return getV(maxWait, function, k, cacheValue, existDuration, expireDuration);
                    },
                    ex -> {
                        log.info("Get key error {}", key, ex);
                        return null;
                    }))
                .orElse(null);
    }

    /**
     * 获取缓存,过期时间为时间段Duration
     * @param key  缓存key
     * @param expireDuration  过期时间段
     * @param maxWait  最大等待时间
     * @param function  获取缓存的函数
     * @return V
     */
    public V get(K key, Duration expireDuration, Duration maxWait,
                 Function<? super K, ? extends V> function) {

        Objects.requireNonNull(expireDuration);
        Objects.requireNonNull(maxWait);
        Objects.requireNonNull(function);

        return Optional.ofNullable(key)
                .map(
                        ThrowingFunction.handleException(
                            k -> {
                                TimeValue<V> cacheValue = lruCache.get(k, () -> refresh(k, function).get());

                                Duration existDuration =
                                        Duration.between(cacheValue.getCreateTime(), LocalDateTime.now());

                                return getV(maxWait, function, k, cacheValue, existDuration, expireDuration);
                            },
                            ex -> {
                                log.info("Get key error {}", key, ex);
                                return null;
                            }))
                .orElse(null);
    }

    private V getV(Duration maxWait, Function<? super K, ? extends V> function,
                   K k, TimeValue<V> cacheValue, Duration existDuration, Duration expireDuration) {
        if (existDuration.toMillis() < expireDuration.toMillis()) {
            log.debug("Key get result {}", k);
            return cacheValue.getValue();
        }

        try {
            TimeValue<V> newValue =
                    loadWithTimeout(k, maxWait.toMillis(), function);
            log.info(
                    "Key expired, get from db {} {} {}",
                    k,
                    cacheValue.getCreateTime(),
                    existDuration);
            return newValue.getValue();
        } catch (TimeoutException e) {
            log.info("Query timeout ,return old value {}", k);
            return cacheValue.getValue();
        }
    }

    /**
     * 获取缓存创建时间
     * @param key
     * @return LocalDateTime or Null
     */
    public LocalDateTime getCreateTimeIfPresent(K key) {
        return Optional
                .ofNullable(lruCache.getIfPresent(key))
                .map(TimeValue::getCreateTime)
                .orElse(null);
    }

    /**
     * 创建一个刷新key的任务,最多等待maxWait时间,超时之后抛出TimeoutException,
     * 但是刷新任务后台仍继续跑
     */
    protected TimeValue<V> loadWithTimeout(K key, long maxWait, Function<? super K, ? extends V> function)
            throws TimeoutException {
        Future<TimeValue<V>> future = refresh(key, function);
        try {
            return future.get(maxWait, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            log.info("Key get from db InterruptedException {} {}", key, maxWait, e);
            throw new TimeoutException();
        } catch (ExecutionException e) {
            log.info("Key get from db ExecutionException {} {}", key, maxWait, e);
            throw new TimeoutException();
        } catch (TimeoutException e) {
            log.info("Key get from db timeout {} {}", key, maxWait, e);
            throw new TimeoutException();
        }
    }

    /**
     * 创建刷新key的任务,立即返回Future
     * 在请求后台结束之后,会进行两个操作, 1,更新LRU Cache, 2. 清除RunningTasks
     * 避免大量并发请求导致的缓存击穿问题
     */
    protected Future<TimeValue<V>> refresh(K key, Function<? super K, ? extends V> function) {
        return runningTasks.computeIfAbsent(key, k ->
                threadPool.submit(
                    () -> {
                        try {
                            V result = function.apply(k);
                            TimeValue<V> value = new TimeValue<>(result);
                            lruCache.put(k, value);
                            return value;
                        } finally {
                            runningTasks.remove(k);
                        }
                    }));
    }
}
public class TimeValue<V> {

    private LocalDateTime createTime;
    private V value;

    public TimeValue(V value) {
        this.createTime = LocalDateTime.now();
        this.value = value;
    }

    public LocalDateTime getCreateTime() {
        return createTime;
    }

    public V getValue() {
        return value;
    }
}
@FunctionalInterface
public interface ThrowingFunction<T, R, E extends Exception> {

    /**
     *
     * @param t
     * @return
     * @throws E
     */
    R apply(T t) throws E;

    /**
     * 包装Function函数,在lambda表达式中处理异常
     * @param function
     * @param handler
     * @param <T>
     * @param <R>
     * @param <E>
     * @return Function
     */
    static <T, R, E extends Exception> Function<T, R> handleException(ThrowingFunction<T, R, E> function,
                                                                             Function<Exception, R> handler) {
        Objects.requireNonNull(function);

        return t -> {
            try {
                return function.apply(t);
            } catch (Exception e) {
                return handler.apply(e);
            }
        };
    }
}
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。