如何实现一个Key可以过期的HashMap

背景

在项目中,我有大量的接口是只读的,只是从数据库发布为服务接口供其他项目使用,为了提高服务接口的响应速度(数据接口的特点是只读,所以做缓存会极大提升接口访问性能)。本文只介绍本地缓存存储可过期HashMap的实现。

可选的缓存中间件有:

缓存类型

  • 本地缓存
    • caffeine 一个优秀的进程缓存框架(据说是本地缓存性能最高)
    • guava google的Java类库
  • 中间件缓存
    • redis (优秀的分布式缓存中间件)
    • memcached (缓存中间件,相对redis功能来说较少)

项目在实际落地中可能是单机版本的,也可能是分布式版本的,所以通用缓存接口的逻辑默认支持两种,即本地缓存和分布式缓存。对于本地缓存的使用,虽然上述的本地缓存框架比较成熟,但是根据项目的特性,还是考虑自己实现一个适用项目的简单的本地缓存存储容器。主要考虑到以下几个方面:

  1. caffeine 和guava cache 都支持可过期的缓存key,但是对于每个key设置过期时间的场景下,就显得有些乏力,需要每次在存储缓存时,创建对应的cache对象,随着key的增多,资源消耗会直线成长,redis自带key单独设置过期时间,所以不用考虑。
  2. 对于外部的依赖的类库,会增加一部分的学习成本,且会与第三方类库进行强绑定。
  3. 抱着学习的态度,尝试利用已知原理动手实现解决现有问题

设计思考

  1. 使用的缓存存储器需要支持Key-Value存储,HashMap是个比较好的选择,HashMap的代码是JDK自带的,如何给HashMap添加新特性?
  2. 需要让单个Key过期,且每个Key的过期时间都是可以自定义的,怎么让HashMap中的Key自动过期是一个问题?
  3. HashMap多线程读写情况下会出现数据不一致甚至可能报错的情况,怎么避免?

设计实现

  1. 使用HashMap做Key-Value的存储容器,可以使用继承的方式来为HashMap添加支持单个Key过期的特性
  2. 缓存的过期时间由另外一个HashMap来控制,即一个HashMap存储Key-Value,另一个存储Key-ExpireTime.
  3. 单独的Key的过期时间的控制在get(K key)的时候判断,即如果key 超过了过期时间就返回null,没有过期则返回其Value
  4. 线程安全问题要通HashMap一致,由调用逻辑控制,容器本身不考虑线程安全问题

具体代码

/**
 * 可对单个Key进行过期的HashMap
 * 实现Map接口,具体存储的逻辑委派给内部的HashMap
 * @since 1.0
 */
public class ExpireTimeHashMap<K,V> implements Map<K,V> {

    /**
     * 具体存储缓存数据的容器
     */
    private Map<K,V> map = new HashMap<>();
    /**
     * 过期时间记录
     */
    private final Map<String, Date>  expireRecord ;

    
    public ExpireTimeHashMap(Map<String, Date> expireRecord) {
        this.expireRecord = expireRecord;
    }
....
  @Override
    public V get(Object key) {
        Date date = expireRecord.get(key);
        // 未记录过期时间就返回map.getKey
        if(date == null){
            return map.get(key);
        }
        // 命中缓存后 返回缓存数据
        if (new Date().before(date)){
            System.out.printf("key:%s,命中缓存",key);
            return map.get(key);
        }else {
        //  数据过期移除数据存储和过期记录存储
            expireRecord.remove(key);
            map.remove(key);
            return null;
        }
    }

}

使用ExpireHashMap

    /**
     * map key 过期记录
     */
    private final Map<String, Date> expireRecord = new HashMap<>();

    /**
     * 本地缓存容器
     */
    private final Map<String, Object> cacheStore = new ExpireTimeHashMap<>(expireRecord);
    
   ...
   
    protected <T> void addCache(String cacheKey, Object cacheValue, Long times, TimeUnit timeUnit) {

        // 设置key的过期时间
        expireRecord.put(cacheKey, new Date(System.currentTimeMillis() + timeUnit.toMillis(times)));
        // 添加缓存到数据中
        cacheStore.put(cacheKey, cacheValue);


    }

设计缺陷

  1. ExpireRecord 的Key是String类型的,针对于非String类型的Key未考虑。
  2. 只在get(K key)的时候获取才判断时间是否过期,如果一直没有获取该Key且过期时间较长,会在一段时间内占用大量内存,可以增加内部定时任务做检测,目前没有做。

代码看起来比较简单,在考虑问题的时候,需要考虑优点、缺点和当前适用的解决方法,简单记录一下

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值