[小设计] 之 缓存过期机制

159 阅读2分钟

一、概述

前言:

需求背景: 获取实时电量(调用 GRPC

问题:电量每5分钟更新,查询频繁,接口访问经常超时。

缓存,貌似是最容易想到的。

可使用什么缓存呢? RedisGuavaCache


二、缓存过期机制

缓存过期机制有多种,列举一些我能想到的。

缓存过期机制有:

  1. 定时器定时更新
  2. 阻塞状态
  3. 触发时更新

(1)定时器定时更新

可以使用定时组件( 例如:quartzxxl-job) 来定时更新,也可以起一个线程定时更新。

例如,Nacos 内部的配置动态刷新原理是通过客户端维护长轮询的任务,定时拉取发生变更的配置,然后将最新的数据推送给客户端 Listener 的持有者。

Nacos 里有 ClientWorker 来维护长轮询任务。ClientWorker 的构造函数会构造线程池,并且每隔 10ms 会发起 LongPollingRunnable 任务,如下代码所示:

    public ClientWorker(final HttpAgent agent, final ConfigFilterChainManager configFilterChainManager,
            final Properties properties) {
        this.agent = agent;
        this.configFilterChainManager = configFilterChainManager;
        
        ......
        
        this.executor.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                try {
                    checkConfigInfo();
                } catch (Throwable e) {
                    LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", e);
                }
            }
        }, 1L, 10L, TimeUnit.MILLISECONDS);
    }

(2)阻塞状态

创建缓存时候,另开个线程sleep在那,到时间就移除这个缓存。

public class Test {

    public static volatile String cacheData = "init string";

    @Test
    public void test() throws InterruptedException {

        System.out.println("初识值 : " + cacheData);

        new Thread(() -> {

            cacheData = "set new string";

            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            cacheData = "init string";

        }).start();

        for (int i = 0; i < 10; ++i) {

            System.out.println("当前 : " + cacheData);

            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

(3)触发时更新

获取数据时候,先走缓存,并判断缓存中是否过期。

意义在于:数据库可能更新了数据,这边再获取一次即可刷新。

好处:只在获取数据时判断,减少了其他组件的接入

如下代码,是简单的代码缓存:

public abstract class ExpireLessGet<T> {

    private long lastRefresh;

    private long expiredIn;

    private T data;

    /**
     * Should set the data and expiredIn
     */
    public abstract void load();

    public long getLastRefresh() {
        return lastRefresh;
    }

    public long getExpiredIn() {
        return expiredIn;
    }

    public void setExpiredIn(long expiredIn) {
        this.expiredIn = expiredIn;
    }

    public T getData() {
        Date date = new Date();

        // 初始化或者已过期
        if (lastRefresh == 0 || date.getTime() >= lastRefresh + getExpiredIn()) {
            load();
            this.lastRefresh = date.getTime();
        }
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}