面试官:如何设计和实现一个带过期时间的本地缓存?

发布时间:2025-05-17 11:33:43 作者:益华网络 来源:undefined 浏览量(1) 点赞(1)
摘要:来源:JAVA日知录 在日常开发中有很多这样的场景:有一些业务系统的配置信息,数据量不大,修改频率不高,但是访问很频繁。如果每次程序都从数据库或集中式缓存中获取,受限于硬盘 I/O性能、远程网络访问限制等,程序的执行效率不高。在这样的业务场景中,

来源:JAVA日知录

在日常开发中有很多这样的场景:有一些业务系统的配置信息,数据量不大,修改频率不高,但是访问很频繁。如果每次程序都从数据库或集中式缓存中获取,受限于硬盘 I/O性能、远程网络访问限制等,程序的执行效率不高。在这样的业务场景中,我们可以通过本地缓存来提升数据访问的效率。

今天我们来基于ConcurrentHashMap与ScheduledThreadPoolExecutor来实现一个线程安全的本地缓存:LocalCache。在LocalCache中支持永久缓存与临时缓存,永久缓存的数据一直有效,临时缓存的数据在指定时间到期之后会自动从缓存中移出。

LocalCache提供了数据安全的增、删、改、查功能,具体方法如下所示:

方法名称方法说明put(String key , V value)向缓存中插入数据,数据永久有效put(String key , V value , int seconds)向缓存中插入数据,数据根据设定的时间生效,时间到期会从缓存中移出containKey(String key)判断缓存中是否包含对应的keyget(String key)根据key从缓存中获取数据remove(String key)移出缓存中对应key的数据shutdownNow()关闭缓存池

1. 设计原理

LocalCache主要由3个部分组成:数据缓存、数据超时时间、数据清理任务。数据缓存和数据超时时间都采用ConcurrentHashMap来存储数据,数据超时时间中Key为数据存储的键,value是数据的时间戳。数据清理任务采用ScheduledThreadPoolExecutor实现任务调度,默认的任务线程数为1,这样可以避免多线程带来的并发修改问题,同时线程都是内存操作,这样单线程同样具备高性能。

本地缓存的设计如下图所示:

每次项缓存中插入数据时,LocalCache首先会将数据插入到ConcurrentHashMap中。然后判断有没有设置超时时间,如果有超时时间,LocalCache会将失效时间插入到ConcurrentHashMap中,并创建数据清理任务,之后任务提交到ScheduledThreadPoolExecutor线程池中。

每次从缓存中查询数据,LocalCache会直接从ConcurrentHashMap中读取数据。

定时任务线程池会按照超时时间来触发数据清理任务,数据清理任务会从数据时长的缓存池中获取Key对应的时间,判断当前Key对应的数据是否已经到期了。如果数据已经到期了,LocalCache会调用remove方法将数据从缓存池中移除。

2. 实现方案

LocalCache作为本地缓存的接口,定义了数据插入、数据删除、数据查询的相关接口方法。DefaultLocalCache 定义了两个ConcurrentHashMap变量:dataMap和timeOutMap。dataMap用来缓存数据信息,timeOutMap用来存储数据失效的时间戳,同时还定义了数据清理任务ClearTask,ClearTask负责将过期的数据从dataMap中移除。UML图如下所示:

3. 代码展示

3.1 接口定义public interface LocalCache<V

{

    

/**

     * 插入数据,数据永久有效

     */
    boolean put(String key, V value)

;

    

/**

     * 插入数据,在指定时间内生效

     */
    boolean put(String key, V value, int seconds)

;

    

/**

     * 是否包含指定的key

     */
    boolean containKey(String key)

;

    

/**

     * 获取指定Key的值

     */
    get(String key)

;

    

/**

     * 从缓存中移除key对应的数据

     */
    void remove(String key)

;

    void shutdownNow()

;

}

在接口LocalCache中定义了两个数据插入的put接口:一个没有到期时间,另一个有到期时间。没有到期时间表示数据永久有效,有到期时间的数据会在到期后从缓存中移除。

接口实现

在接口实现DefaultLocalCache内部定义了三个常量:缓存的默认大小DEFAULT_CAPACITY、最大容量MAX_CAPACITY、定时线程池的大小DEFAULT_THREAD_SIZE。核心代码如下:

public class DefaultLocalCache<Vimplements LocalCache<V

{

    // 默认容量    private static final int DEFAULT_CAPACITY = 1024

;

    private static final int MAX_CAPACITY = 100000

;

    private static final int DEFAULT_THREAD_SIZE = 1

;

    private final int

 maxSize;

    //数据map    private volatile

 ConcurrentHashMap dataMap;

    //过期时间    private final

 ConcurrentHashMap timeOutMap;

    //定时任务    private final

 ScheduledExecutorService executorService;

    public DefaultLocalCache() 

{

        maxSize = MAX_CAPACITY;

        dataMap = new

 ConcurrentHashMap<>(DEFAULT_CAPACITY);

        timeOutMap = new

 ConcurrentHashMap<>(DEFAULT_CAPACITY);

        executorService = new

 ScheduledThreadPoolExecutor(DEFAULT_THREAD_SIZE) ;

    }

    public DefaultLocalCache(int size) 

{

        maxSize = size;

        dataMap = new

 ConcurrentHashMap<>(DEFAULT_CAPACITY);

        timeOutMap = new

 ConcurrentHashMap<>(DEFAULT_CAPACITY);

        executorService = new

 ScheduledThreadPoolExecutor(DEFAULT_THREAD_SIZE) ;

    }

    @Override    public boolean put(String key, V value) 

{

        //检查容量        if

(checkCapacity()){

            dataMap.put(key,value);

            return true

;

        }

        return false

;

    }

    @Override    public boolean put(String key, V value, int seconds) 

{

        if

(checkCapacity()){

            dataMap.put(key,value);

            if(seconds >= 0

){

                timeOutMap.put(key,getTimeOut(seconds));

                ClearTask task = new

 ClearTask(key);

                executorService.schedule(task, seconds, TimeUnit.SECONDS);

            }

        }

        return false

;

    }

 ......

    class ClearTask implements Runnable

{

        private

 String key;

        public ClearTask(String key)

{

            this

.key = key;

        }

        @Override        public void run() 

{

            //判断缓存中是否有key            if

(timeOutMap.contains(key)){

                //获取失效时间

                Long expire = timeOutMap.get(key);

                //如果失效时间大于0,并且比当前时间小,则删除缓存                if(expire > 0

){

                    long

 now = System.currentTimeMillis();

                    if

(now >= expire){

                        remove(key);

                    }

                }

            }

        }

    }

}

在LocalCache的默认实现DefaultLocalCache中,基于ConcurrentHashMap与ScheduledThreadPoolExecutor结合使用,使得LocalCache支持永久缓存与临时缓存两种能力。

二维码

扫一扫,关注我们

声明:本文由【益华网络】编辑上传发布,转载此文章须经作者同意,并请附上出处【益华网络】及本页链接。如内容、图片有任何版权问题,请联系我们进行处理。

感兴趣吗?

欢迎联系我们,我们愿意为您解答任何有关网站疑难问题!

您身边的【网站建设专家】

搜索千万次不如咨询1次

主营项目:网站建设,手机网站,响应式网站,SEO优化,小程序开发,公众号系统,软件开发等

立即咨询 15368564009
在线客服
嘿,我来帮您!