赞
踩
类图如下:
例如:
DawnSimpleCache dawnSimpleCache = new DawnSimpleCache(
"testCache", 2, DawnSimpleCache.LRU, 1F);
// 设置缓存数据
dawnSimpleCache.setCache("test1", "xxx", 200, TimeUnit.SECONDS);
// 从缓存获取数据
dawnSimpleCache.getFromCache("test1");
/**
* 构造
*
* @param name 缓存名称
* @param maxSize 最大的缓存数量
* @param strategy 缓存策略,LRU_LFU 1, LFU 2, FIFO 3
* @param clearProportion 清除的比重,0-1之间
*/
public DawnSimpleCache(String name, int maxSize, int strategy, double clearProportion)
LRU (Least recently used) 最近最少使用,如果数据最近被访问过,那么将来被访问的几率也更高。
LFU (Least frequently used) 最不经常使用,如果一个数据在最近一段时间内使用次数很少,
那么在将来一段时间内被使用的可能性也很小。
FIFO (Fist in first out) 先进先出, 如果一个数据最先进入缓存中,则应该最早淘汰掉。
LRU_LFU 综合 LRU 和 LFU 的优点,但是计算较复杂
/** * DAWN 缓存通用接口 * * @author hengyumo * @since 2021-06-06 */ public interface DawnCache { /** * 从缓存中取值 * * @param key 键 * @return Pair<Boolean, Optional < ?>>, Boolean是标识位,标识是否缓存中存在这个Key * 该设计是为了避免空值时出现缓存穿透的可能 */ Pair<Boolean, Optional<?>> getFromCache(String key); /** * 删除某个前缀开头的键对应的那些缓存 * * @param keyPrefix 键的前缀 * @return 删除的数量 */ int deleteCache(String keyPrefix); /** * 设置缓存 * * @param key 键 * @param value 值 * @param timeToLive 有效时间,0时为永久 * @param timeUnit 有效时间的单位 */ void setCache(String key, Object value, long timeToLive, TimeUnit timeUnit); }
package cn.hengyumo.dawn.core.cache.types; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.data.util.Pair; import org.springframework.util.Assert; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.function.Function; /** * 使用 Java 内置的 MAP 实现的缓存组件,适用于一些轻量级缓存 * * @author hengyumo * @since 2021-06-06 */ @Slf4j public class DawnSimpleCache implements DawnCache { @AllArgsConstructor @Getter @Setter private static class CacheHitInfo { // 缓存启用时间 private long startTime; // 最后一次使用时间 private long lastTime; // 命中次数 private long hitCount; // 计分 private double score; // 存活次数 private int liveCount; } private static class MyPair<E1, E2> { private final Object[] objects = new Object[2]; private MyPair(){} public static <E1,E2> MyPair<E1,E2> of(E1 e1, E2 e2) { MyPair<E1, E2> pair = new MyPair<>(); pair.objects[0] = e1; pair.objects[1] = e2; return pair; } public void setFirst(E1 e1) { objects[0] = e1; } public void setSecond(E2 e2) { objects[1] = e2; } public E1 getFirst() { //noinspection unchecked return (E1) objects[0]; } public E2 getSecond() { //noinspection unchecked return (E2) objects[1]; } } /** * 最大数量的默认值,200 */ public final static int MAX_SIZE = 200; /* LRU (Least recently used) 最近最少使用,如果数据最近被访问过,那么将来被访问的几率也更高。 LFU (Least frequently used) 最不经常使用,如果一个数据在最近一段时间内使用次数很少, 那么在将来一段时间内被使用的可能性也很小。 FIFO (Fist in first out) 先进先出, 如果一个数据最先进入缓存中,则应该最早淘汰掉。 LRU_LFU 综合 LRU 和 LFU 的优点,但是计算较复杂 */ public final static int LRU = 1; public final static int LFU = 2; public final static int FIFO = 3; public final static int LRU_LFU = 4; /** * 默认清除之后剩下的比重 0.8 */ public final static float DEFAULT_CLEAR_PROPORTION = 0.8F; /** * 默认的缓存置换策略,LFU,使用次数最少的缓存将会被优先置换 */ public final static int DEFAULT_STRATEGY = LRU_LFU; /** * 保存缓存数据 */ private final Map<String, Object> cache; /** * 保存缓存对应的过期时间 */ private final Map<String, Long> cacheExpireRecord; /** * 记录缓存的最后一次命中时间,LRU支持(最近最少使用的会被优先淘汰) */ private List<MyPair<String, Long>> cacheHitListForLru; /** * 记录缓存的命中次数,LFU 支持(最近最不经常使用的会优先被淘汰) */ private List<MyPair<String, Long>> cacheHitListForLfu; /** * 一个先进先出队列,FIFO 支持(最先缓存的数据会优先被淘汰) */ private List<String> cacheHitListForFifo; /** * LRU_LFU支持 */ private List<MyPair<String, CacheHitInfo>> cacheHitListForLruLfu; /** * 缓存名称 */ private final String name; /** * 对象的最大数量 */ private final int maxSize; /** * 使用的缓存置换策略 */ private final int strategy; /** * 缓存清除所有过期数据之后,仍超出最大大小时,执行策略进行清除的清除比重 */ private final double clearProportion; /** * 默认构造函数,MAX_SIZE 1000、DEFAULT_STRATEGY LRU */ public DawnSimpleCache() { this.cache = new ConcurrentHashMap<>(); this.cacheExpireRecord = new ConcurrentHashMap<>(); this.maxSize = MAX_SIZE; this.strategy = DEFAULT_STRATEGY; this.clearProportion = DEFAULT_CLEAR_PROPORTION; this.name = this.getClass().getSimpleName() + System.currentTimeMillis(); applyStrategy(); } /** * 构造 * * @param name 缓存名称 * @param maxSize 最大的缓存数量 * @param strategy 缓存策略,LRU_LFU 1, LFU 2, FIFO 3 * @param clearProportion 清除的比重,0-1之间 */ public DawnSimpleCache(String name, int maxSize, int strategy, double clearProportion) { this.cache = new ConcurrentHashMap<>(); this.cacheExpireRecord = new ConcurrentHashMap<>(); this.name = name; this.maxSize = maxSize; this.strategy = strategy; Assert.isTrue(clearProportion > 0 && clearProportion <= 1, "清除的比重在0<x<=1之间"); this.clearProportion = clearProportion; applyStrategy(); } // 应用设置的缓存清除策略 private void applyStrategy() { switch (this.strategy) { case LRU -> cacheHitListForLru = new LinkedList<>(); case LFU -> cacheHitListForLfu = new LinkedList<>(); case FIFO -> cacheHitListForFifo = new LinkedList<>(); case LRU_LFU -> cacheHitListForLruLfu = new LinkedList<>(); } } private void collectHitInfoForLru(String key) { MyPair<String, Long> aPair = null; Iterator<MyPair<String, Long>> iterator = cacheHitListForLru.iterator(); for (int i = 0; i < cacheHitListForLru.size(); i++) { MyPair<String, Long> pair = iterator.next(); if (pair.getFirst().equals(key)) { aPair = pair; // 更新最近使用时间 pair.setSecond(System.currentTimeMillis()); break; } } if (aPair == null) { MyPair<String, Long> newPair = MyPair.of(key, System.currentTimeMillis()); cacheHitListForLru.add(newPair); } } private void collectHitInfoForLfu(String key) { MyPair<String, Long> aPair = null; Iterator<MyPair<String, Long>> iterator = cacheHitListForLfu.iterator(); for (int i = 0; i < cacheHitListForLfu.size(); i++) { MyPair<String, Long> pair = iterator.next(); if (pair.getFirst().equals(key)) { aPair = pair; // 缓存命中次数 + 1 pair.setSecond(pair.getSecond() + 1); break; } } if (aPair == null) { MyPair<String, Long> newPair = MyPair.of(key, 1L); cacheHitListForLfu.add(newPair); } } private void collectHitInfoForFifo(String key) { // 如果没找到则加到最后 boolean found = cacheHitListForFifo.stream() .anyMatch(x -> x.equals(key)); if (!found) { cacheHitListForFifo.add(key); } } private void collectHitInfoForLruLfu(String key) { MyPair<String, CacheHitInfo> aPair = null; Iterator<MyPair<String, CacheHitInfo>> iterator = cacheHitListForLruLfu.iterator(); for (int i = 0; i < cacheHitListForLruLfu.size(); i++) { MyPair<String, CacheHitInfo> pair = iterator.next(); if (pair.getFirst().equals(key)) { aPair = pair; // 更新信息 CacheHitInfo cacheHitInfo = pair.getSecond(); cacheHitInfo.setHitCount(cacheHitInfo.getHitCount() + 1); cacheHitInfo.setLastTime(System.currentTimeMillis()); break; } } if (aPair == null) { CacheHitInfo cacheHitInfo = new CacheHitInfo( System.currentTimeMillis(), System.currentTimeMillis(), 1L, 0F, 0); MyPair<String, CacheHitInfo> newPair = MyPair.of(key, cacheHitInfo); cacheHitListForLruLfu.add(newPair); } } // 执行缓存的命中信息收集 private synchronized void collectHitInfoForStrategy(String key) { switch (this.strategy) { case LRU -> collectHitInfoForLru(key); case LFU -> collectHitInfoForLfu(key); case FIFO -> collectHitInfoForFifo(key); case LRU_LFU -> collectHitInfoForLruLfu(key); } } // 清除缓存,从左到右 private synchronized <T> void clearCache(Iterator<T> iterator, Function<T, String> function, int clearSize) { for (int i = 0; i < clearSize; i++) { String key = function.apply(iterator.next()); this.removeCache(key); } } private void clearWithLru(int clearSize) { int cacheSize = cache.size(); // 针对最近使用时间从低到高进行排序 cacheHitListForLru.sort(Comparator.comparingLong(MyPair::getSecond)); // 清除对应的缓存 clearCache(cacheHitListForLru.iterator(), MyPair::getFirst, clearSize); // 最后边的数据最近使用,所以,去头即可 cacheHitListForLru = cacheHitListForLru.subList(clearSize, cacheSize); } private void clearWithLfu(int clearSize) { int cacheSize = cache.size(); // 针对使用次数从低到高进行排序 cacheHitListForLfu.sort(Comparator.comparingLong(MyPair::getSecond)); // 清除对应的缓存 clearCache(cacheHitListForLfu.iterator(), MyPair::getFirst, clearSize); // 最后边的数据使用最频繁,所以,去头即可 cacheHitListForLfu = cacheHitListForLfu.subList(clearSize, cacheSize); } private void clearWithFifo(int clearSize) { int cacheSize = cache.size(); // 清除对应的缓存 clearCache(cacheHitListForFifo.iterator(), x -> x, clearSize); // 最左边的数据最先进入,所以去头 cacheHitListForFifo = cacheHitListForFifo.subList(clearSize, cacheSize); } private void clearWithLruLfu(int clearSize) { int cacheSize = cache.size(); // 计算得分、排序 cacheHitListForLruLfu.forEach(x -> { CacheHitInfo cacheHitInfo = x.getSecond(); // 得分,命中次数 / 最后一次命中至今的毫秒数 // LRU_LFU耗时:60622 write:299 read:5000 del:100 score:16.49566164098842 // 得分,命中次数 ^ 2 / 最后一次命中至今的毫秒数 // LRU_LFU耗时:54182 write:267 read:5000 del:100 score:18.456313904986896 // LRU_LFU耗时:57845 write:285 read:5000 del:100 score:17.287578874578614 // 得分,命中次数 ^ 3 / 最后一次命中至今的毫秒数 // LRU_LFU耗时:57157 write:282 read:5000 del:100 score:17.495669821719126 // 得分,命中次数 ^ 4 / 最后一次命中至今的毫秒数 // LRU_LFU耗时:59370 write:293 read:5000 del:100 score:16.843523665150748 // 得分,命中次数 ^ 0.5 / 最后一次命中至今的毫秒数 // LRU_LFU耗时:55969 write:276 read:5000 del:100 score:17.867033536421946 // LRU_LFU耗时:58623 write:289 read:5000 del:100 score:17.05815123756887 double a = Math.pow((double) cacheHitInfo.getHitCount(), 2); cacheHitInfo.setScore((a / (double) ((System.currentTimeMillis() - cacheHitInfo.getLastTime())))); cacheHitInfo.setLiveCount(cacheHitInfo.getLiveCount() + 1); }); // 针对得分从低到高进行排序 cacheHitListForLruLfu.sort(Comparator.comparingDouble(x -> x.getSecond().getScore())); // 清除对应的缓存 clearCache(cacheHitListForLruLfu.iterator(), MyPair::getFirst, clearSize); // 最后边的数据使用最频繁,所以,去头即可 cacheHitListForLruLfu = cacheHitListForLruLfu.subList(clearSize, cacheSize); } // 执行缓存清除策略 private synchronized void clearWithStrategy(int clearSize) { switch (this.strategy) { case LRU -> clearWithLru(clearSize); case LFU -> clearWithLfu(clearSize); case FIFO -> clearWithFifo(clearSize); case LRU_LFU -> clearWithLruLfu(clearSize); } } // 清除缓存命中记录,连同缓存数据 private synchronized void removeCacheAndCacheHit(String key) { switch (this.strategy) { case LRU -> cacheHitListForLru.removeIf(x -> x.getFirst().equals(key)); case LFU -> cacheHitListForLfu.removeIf(x -> x.getFirst().equals(key)); case FIFO -> cacheHitListForFifo.removeIf(x -> x.equals(key)); case LRU_LFU -> cacheHitListForLruLfu.removeIf(x -> x.getFirst().equals(key)); } removeCache(key); } @Override public synchronized Pair<Boolean, Optional<?>> getFromCache(String key) { boolean cacheContainsKey = true; Object value = null; Long expireTime = cacheExpireRecord.get(key); // 如果存在这个Key if (expireTime != null) { // 如果数据已经过期,此处使用了延迟删除,只有查询发现过期了才会删除 // 0L是特殊的,它代表永不过期 if (expireTime != 0L && expireTime < System.currentTimeMillis()) { log.info("SIMPLE CACHE {}缓存过期:{}", name, key); removeCacheAndCacheHit(key); cacheContainsKey = false; } else { // 从缓存中获取数据 value = cache.get(key); collectHitInfoForStrategy(key); } } else { cacheContainsKey = false; } return Pair.of(cacheContainsKey, Optional.ofNullable(value)); } private synchronized void maxSizeExpireCheck() { Set<String> keys = cacheExpireRecord.keySet(); Set<String> keysDel = new HashSet<>(); for (String key : keys) { Long expireTime = cacheExpireRecord.get(key); if (expireTime != 0L && expireTime < System.currentTimeMillis()) { log.info("SIMPLE CACHE {}缓存过期:{}", name, key); keysDel.add(key); } } // 遍历这些Key,依次清除 keysDel.forEach(this::removeCacheAndCacheHit); // 清除之后还是超出最大大小,则进行清除,只留下配置的缓存大小:maxSize * clearProportion if (cache.size() > maxSize) { clearWithStrategy((cache.size() - maxSize) + (int) (maxSize * (1 - clearProportion))); } } private void removeCache(String key) { cacheExpireRecord.remove(key); cache.remove(key); log.info("SIMPLE CACHE {}缓存清除:{}", name, key); } @Override public synchronized int deleteCache(String keyPrefix) { Set<String> keys = new HashSet<>(); // 先查到以这个前缀开头的所有Key cache.forEach((k, v) -> { if (k.startsWith(keyPrefix)) { keys.add(k); } }); // 遍历这些Key,依次清除 keys.forEach(this::removeCacheAndCacheHit); return keys.size(); } @Override public synchronized void setCache(String key, Object value, long timeToLive, TimeUnit timeUnit) { cache.put(key, value); // 记录失效时间 if (timeToLive <= 0L) { cacheExpireRecord.put(key, 0L); } else { cacheExpireRecord.put(key, System.currentTimeMillis() + timeUnit.toMillis(timeToLive)); } collectHitInfoForStrategy(key); if (cacheExpireRecord.size() > maxSize) { maxSizeExpireCheck(); } } public synchronized int getCacheSize() { return cache.size(); } public String getName() { return name; } public int getMaxSize() { return maxSize; } public int getStrategy() { return strategy; } public double getClearProportion() { return clearProportion; } }
package cn.hengyumo.dawn.example; import cn.hengyumo.dawn.core.cache.types.DawnSimpleCache; import org.junit.Assert; import org.junit.Test; import org.springframework.data.util.Pair; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.Random; import java.util.concurrent.TimeUnit; public class DawnSimpleCacheTest { @Test public void simpleTest() throws InterruptedException { DawnSimpleCache dawnSimpleCache = new DawnSimpleCache(); dawnSimpleCache.setCache("test", "xxx", 2, TimeUnit.SECONDS); Pair<Boolean, Optional<?>> value = dawnSimpleCache.getFromCache("test"); Assert.assertTrue(value.getFirst()); Assert.assertTrue(value.getSecond().isPresent()); String val = (String) value.getSecond().orElse(null); Assert.assertEquals("xxx", val); Thread.sleep(2000); value = dawnSimpleCache.getFromCache("test"); Assert.assertFalse(value.getFirst()); Assert.assertFalse(value.getSecond().isPresent()); dawnSimpleCache.setCache("test", "xxx", 2, TimeUnit.SECONDS); dawnSimpleCache.deleteCache("test"); Assert.assertFalse(value.getFirst()); Assert.assertFalse(value.getSecond().isPresent()); } @Test public void testLru() throws InterruptedException { DawnSimpleCache dawnSimpleCache = new DawnSimpleCache( "testCache", 2, DawnSimpleCache.LRU, 1F); dawnSimpleCache.setCache("test1", "xxx", 200, TimeUnit.SECONDS); dawnSimpleCache.getFromCache("test1"); dawnSimpleCache.setCache("test2", "xxx", 200, TimeUnit.SECONDS); dawnSimpleCache.getFromCache("test2"); Thread.sleep(2000); dawnSimpleCache.getFromCache("test1"); dawnSimpleCache.setCache("test3", "xxx", 200, TimeUnit.SECONDS); // 这时 test2 应该被清除 Pair<Boolean, Optional<?>> value = dawnSimpleCache.getFromCache("test2"); Assert.assertFalse(value.getFirst()); Assert.assertFalse(value.getSecond().isPresent()); Assert.assertEquals(2, dawnSimpleCache.getCacheSize()); } @Test public void testLfu() { DawnSimpleCache dawnSimpleCache = new DawnSimpleCache( "testCache", 2, DawnSimpleCache.LFU, 1F); dawnSimpleCache.setCache("test1", "xxx", 200, TimeUnit.SECONDS); dawnSimpleCache.getFromCache("test1"); dawnSimpleCache.setCache("test2", "xxx", 200, TimeUnit.SECONDS); dawnSimpleCache.getFromCache("test2"); dawnSimpleCache.getFromCache("test1"); dawnSimpleCache.getFromCache("test2"); dawnSimpleCache.setCache("test3", "xxx", 200, TimeUnit.SECONDS); // 这时 test3 应该被清除 Pair<Boolean, Optional<?>> value = dawnSimpleCache.getFromCache("test3"); Assert.assertFalse(value.getFirst()); Assert.assertFalse(value.getSecond().isPresent()); Assert.assertEquals(2, dawnSimpleCache.getCacheSize()); } @Test public void testFifo() { DawnSimpleCache dawnSimpleCache = new DawnSimpleCache( "testCache", 2, DawnSimpleCache.FIFO, 1F); dawnSimpleCache.setCache("test1", "xxx", 200, TimeUnit.SECONDS); dawnSimpleCache.getFromCache("test1"); dawnSimpleCache.setCache("test2", "xxx", 200, TimeUnit.SECONDS); dawnSimpleCache.getFromCache("test2"); dawnSimpleCache.getFromCache("test1"); dawnSimpleCache.getFromCache("test2"); dawnSimpleCache.setCache("test3", "xxx", 200, TimeUnit.SECONDS); // 这时 test1 应该被清除 Pair<Boolean, Optional<?>> value = dawnSimpleCache.getFromCache("test1"); Assert.assertFalse(value.getFirst()); Assert.assertFalse(value.getSecond().isPresent()); Assert.assertEquals(2, dawnSimpleCache.getCacheSize()); } @Test public void testLruLfu() throws InterruptedException { DawnSimpleCache dawnSimpleCache = new DawnSimpleCache( "testCache", 2, DawnSimpleCache.LRU_LFU, 1F); dawnSimpleCache.setCache("test1", "xxx", 200, TimeUnit.SECONDS); dawnSimpleCache.getFromCache("test1"); dawnSimpleCache.setCache("test2", "xxx", 200, TimeUnit.SECONDS); dawnSimpleCache.getFromCache("test2"); dawnSimpleCache.getFromCache("test1"); Thread.sleep(2000); dawnSimpleCache.getFromCache("test2"); dawnSimpleCache.setCache("test3", "xxx", 200, TimeUnit.SECONDS); // test1 3 / 2, test2 3/ 0.., test3 1/0.. // 这时 test1 应该被清除 Pair<Boolean, Optional<?>> value = dawnSimpleCache.getFromCache("test1"); Assert.assertFalse(value.getFirst()); Assert.assertFalse(value.getSecond().isPresent()); Assert.assertEquals(2, dawnSimpleCache.getCacheSize()); } @Test public void performanceTest() throws InterruptedException { // 查询次数 int readSize = 5000; int dataSize = 1000; double hot = 0.05; // 50 double delProportion = 0.05; // 0.51 double clearProportion = (hot / (hot + (1 - hot) * hot)); // 二八定律 dataSize * 0.2 + dataSize * 0.8 * 0.2 = dataSize * 0.36 / 0.56 // 189 int maxSize = (int) ((int) (dataSize * (hot + (1 - hot) * hot)) / clearProportion); // 0.2 / 0.36 = 0.56 缓存中的热数据占比 int expire = 300_000; long searchTime = 200L; Random random = new Random(System.currentTimeMillis()); StringBuilder sb = new StringBuilder(); List<DawnSimpleCache> cacheList = new ArrayList<>(4); cacheList.add(new DawnSimpleCache( "LRU", maxSize, DawnSimpleCache.LRU, clearProportion)); cacheList.add(new DawnSimpleCache( "LFU", maxSize, DawnSimpleCache.LFU, clearProportion)); cacheList.add(new DawnSimpleCache( "FIFO", maxSize, DawnSimpleCache.FIFO, clearProportion)); cacheList.add(new DawnSimpleCache( "LRU_LFU", maxSize, DawnSimpleCache.LRU_LFU, clearProportion)); for (DawnSimpleCache cache : cacheList) { long start = System.currentTimeMillis(); int read = 0; int write = 0; int del = 0; for (int i = 0; i < readSize; i++) { double r = random.nextDouble(); // 删除的可能性 // if (r < delProportion) { if (i % 50 == 1) { // 1/50 cache.deleteCache("key" + random.nextInt(dataSize)); del ++; } int index; // 80 % 的 查询集中在 20%的数据 if (r < (1 - hot)) { index = random.nextInt((int) (dataSize * hot)); } else { index = (int) (dataSize * hot) + random.nextInt((int) (dataSize * (1 - hot))); } Pair<Boolean, Optional<?>> value = cache.getFromCache("key" + index); read ++; if (!value.getFirst()) { // 模拟没查到缓存时重新设置 Thread.sleep(searchTime); cache.setCache("key" + index, index, expire, TimeUnit.MILLISECONDS); write ++; } } long time = System.currentTimeMillis() - start; sb.append(cache.getName()) .append("耗时:").append(time) .append(" write:").append(write) .append(" read:").append(read) .append(" del:").append(del) // 快了多少倍 .append(" score:").append((double) (read * searchTime)/ (double) time) .append('\n'); } System.out.println("\n\n" + sb); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。