Java实现本地缓存

浮生一境 2019年12月26日 611次浏览

java实现本地缓存

缓存

缓存,将程序或系统中重复用到的数据缓存在内存中,加快访问速度,减少系统开销,提高系统效率的方案。

数据存储方式主要分2种:

  1. 文件 数据保存在文件中,做持久化。
  2. 内存 数据保存在内存中,也就是创建一个静态内存区域,本文中数据保存在静态区域,使用MAP保存key\value数据。

开源缓存框架

  • Redis Redis是基于内存、可持久化的日志型、Key-Value数据库高性能存储系统,并提供多种语言的API.
  • memcached 是一个自由开源的,高性能,分布式内存对象缓存系统。基于内存的key-value存储,用来存储小块的任意数据(字符串、对象)。

解决场景

目前在开发的项目中,不希望引入太多外部服务,引入分布式缓存框架例如Redis,还需要部署与维护,这会大大增加对项目的维护成本。

Java实现本地缓存框架逻辑

框架基本定义

  • 数据存储格式 key-value,仅针对字符串缓存。
  • 数据有效性,增加超时时间失效逻辑
  • 失效策略:定期删除与懒惰淘汰并行

定期删除策略

定期删除策略是每隔一段时间检测已过期的缓存,并且降之删除。这个策略的优点是能够确保过期的缓存都会被删除。同时也存在着缺点,过期的缓存不一定能够及时的被删除,这跟我们设置的定时频率有关系,另一个缺点是如果缓存数据较多时,每次检测也会给 cup 带来不小的压力。

懒惰淘汰策略

懒惰淘汰策略是在使用缓存时,先判断缓存是否过期,如果过期将它删除,并且返回空。这个策略的优点是只有在查找的时候,才判断是否过期,对 CUP 影响较小。同时这种策略有致命的缺点,当存入了大量的缓存,这些缓存都没有被使用并且已过期,都将成为无效缓存,这些无效的缓存将占用你大量的内存空间,最后导致服务器内存溢出。 我们简单的了解了一下 Redis 的两种过期缓存处理策略,每种策略都存在自己的优缺点。所以我们在使用过程中,可以将两种策略组合起来,结合效果还是非常理想的。

逻辑顺序

  1. 定义缓存map, 定义定时移除时效数据的任务,在static代码块中初始化。
  2. 定义缓存对象,包含值和过期时间。
  3. 定义定时移除时效数据的异步任务。
  4. 实现俩种时效策略移除数据的逻辑 remove + removeAll.
  5. 实现put与get方法。

框架中用到的定时任务模块

private final static ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); executor.scheduleAtFixedRate(new Task(), INITIAL_DELAY_TIME, PERIOD_TIME, TimeUnit.SECONDS);

初始化线程池,启动定时执行任务scheduleAtFixedRate

  1. new Task() 定时任务执行的具体业务逻辑
  2. INITIAL_DELAY_TIME 启动后延迟时间执行(秒)
  3. PERIOD_TIME 每次执行间隔(秒)

执行结果

定时任务策略:延迟5秒执行,每隔5秒执行一次 (实际失效策略时间可以放长一点)

首先添加缓存数据

LocalCache.put("a", "10a", 7);
LocalCache.put("b", "10b", 12);
LocalCache.put("c", "10c", 30);
2019-12-25 21:00:19,914 [http-nio-9060-exec-1] INFO LocalCache 62 - 添加缓存,key=a, value=10a, expire=7秒
2019-12-25 21:00:19,915 [http-nio-9060-exec-1] INFO LocalCache 62 - 添加缓存,key=b, value=10b, expire=12秒
2019-12-25 21:00:19,915 [http-nio-9060-exec-1] INFO LocalCache 62 - 添加缓存,key=c, value=10c, expire=30秒

定时任务执行第二次 10s后,移除缓存数据a

2019-12-25 21:00:24,919 [pool-3-thread-1] INFO LocalCache 89 - 定期删除策略: 开始执行, store={a=LocalCache.Cache(value=10a, expire=1577278826915), b=LocalCache.Cache(value=10b, expire=1577278831915), c=LocalCache.Cache(value=10c, expire=1577278849915)}
2019-12-25 21:00:29,918 [pool-3-thread-1] INFO LocalCache 89 - 定期删除策略: 开始执行, store={a=LocalCache.Cache(value=10a, expire=1577278826915), b=LocalCache.Cache(value=10b, expire=1577278831915), c=LocalCache.Cache(value=10c, expire=1577278849915)}
2019-12-25 21:00:29,919 [pool-3-thread-1] INFO LocalCache 94 - 定期删除策略: 移除超时失效数据, key=a, value=10a, time=1577278826915

定时任务执行第三次 15s后,移除缓存数据b

2019-12-25 21:00:34,918 [pool-3-thread-1] INFO LocalCache 89 - 定期删除策略: 开始执行, store={b=LocalCache.Cache(value=10b, expire=1577278831915), c=LocalCache.Cache(value=10c, expire=1577278849915)}
2019-12-25 21:00:34,919 [pool-3-thread-1] INFO LocalCache 94 - 定期删除策略: 移除超时失效数据, key=b, value=10b, time=1577278831915

定时任务执行第六次 30s后,移除缓存数据c

2019-12-25 21:00:39,918 [pool-3-thread-1] INFO LocalCache 89 - 定期删除策略: 开始执行, store={c=LocalCache.Cache(value=10c, expire=1577278849915)}
2019-12-25 21:00:44,919 [pool-3-thread-1] INFO LocalCache 89 - 定期删除策略: 开始执行, store={c=LocalCache.Cache(value=10c, expire=1577278849915)}
2019-12-25 21:00:49,918 [pool-3-thread-1] INFO LocalCache 89 - 定期删除策略: 开始执行, store={c=LocalCache.Cache(value=10c, expire=1577278849915)}
2019-12-25 21:00:49,919 [pool-3-thread-1] INFO LocalCache 94 - 定期删除策略: 移除超时失效数据, key=c, value=10c, time=1577278849915
2019-12-25 21:00:54,918 [pool-3-thread-1] INFO LocalCache 89 - 定期删除策略: 开始执行, store={}

测试结果

源码

package com.core.mall.config.cache;

import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.*;

/**
 * 本地缓存服务
 * key = 字符串 | value = 字符串 | expire = 秒(有效时间)
 *
 * put 添加缓存
 * get 读取缓存值
 *
 * 失效策略:
 *  1. 定期删除策略:启动1个线程,每2分钟扫描一次,超时数据移除
 *  2. 懒惰淘汰策略:每次访问时校验有效性,如果失效移除
 */
public class LocalCache {
    private final static Logger logger = LoggerFactory.getLogger(LocalCache.class);

    /**
     * 启动开始后延迟5秒执行时效策略
     */
    private static final int INITIAL_DELAY_TIME = 5;
    /**
     * 执行时效策略间隔时间
     */
    private static final int PERIOD_TIME = 5;
    /**
     * 本地缓存map
     */
    private static ConcurrentHashMap<String, Cache> store;
    /**
     * 执行时效策略线程池
     */
    private final static ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);

    /*
     * 静态代码块
     *
     * 初始化缓存map
     * 添加时效策略定时线程任务
     */
    static {
        store = new ConcurrentHashMap<>();
        executor.scheduleAtFixedRate(new Task(), INITIAL_DELAY_TIME, PERIOD_TIME, TimeUnit.SECONDS);
    }

    public static void put(String key, String value) {
        put(key, value, 0);
    }

    /**
     * 设置缓存
     * @param key 唯一key
     * @param value 值
     * @param expire 超时时间-单位(s/秒)
     */
    public static void put(String key, String value, long expire) {
        logger.info("添加缓存,key={}, value={}, expire={}秒", key, value, expire);
        if (expire > 0) {
            store.put(key, new Cache(value, expire));
        } else {
            store.put(key, new Cache(value));
        }
    }

    public static String get(String key) {
        Cache cache = store.get(key);
        if (cache == null) {
            return null;
        }

        if (cache.getExpire() > 0 && cache.getExpire() < System.currentTimeMillis()) {
            remove(key);
            return null;
        }
        return store.get(key).getValue();
    }

    private static void remove(String key) {
        Cache cache = store.remove(key);
        logger.info("懒惰淘汰策略: 移除超时失效数据, cache={}", cache);
    }

    private static void removeAll() {
        logger.info("定期删除策略: 开始执行, store={}", store);
        for (String key : store.keySet()) {
            Cache cache = store.get(key);
            if (cache.getExpire() > 0 && cache.getExpire() < System.currentTimeMillis()) {
                store.remove(key);
                logger.info("定期删除策略: 移除超时失效数据, key={}, value={}, time={}", key, cache.getValue(), cache.getExpire());
            }
        }
    }

    /**
     * 定时移除时效数据任务
     */
    private static class Task implements Runnable {
        @Override
        public void run() {
            try {
                LocalCache.removeAll();
            } catch (Exception e) {
                logger.info("定期删除策略异常", e);
            }
        }
    }

    /**
     * 本地缓存对象
     */
    @Data
    private static class Cache {
        private String value;
        private long expire = 0;

        Cache(String value, long expire) {
            this.value = value;
            this.expire = System.currentTimeMillis() + expire * 1000;
        }

        Cache(String value) {
            this.value = value;
        }
    }
}

联系方式

如果对你有帮助,可以关注作者支持一下,每天会不定时回复留言(有任何问题都可以留言哦)。

微信公众号