項目簡介
大家好,我是老馬。
Cache 用于實現一個可拓展的高性能本地緩存。
有人的地方,就有江湖。有高性能的地方,就有 cache。
v1.0.0 版本
以前的 FIFO 實現比較簡單,但是 queue 循環一遍刪除的話,性能實在是太差。
于是想到引入一個 Set 存儲有哪些 key,改成下面的方式:
package com.github.houbb.cache.core.support.evict.impl;import com.github.houbb.cache.api.ICacheContext;
import com.github.houbb.cache.core.model.CacheEntry;
import com.github.houbb.cache.core.support.evict.AbstractCacheEvict;import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;/*** 丟棄策略-先進先出* @author binbin.hou* @since 0.0.2*/
public class CacheEvictFifo<K,V> extends AbstractCacheEvict<K,V> {/*** queue 信息* @since 0.0.2*/private final Queue<K> queue = new LinkedList<>();/*** 避免數據重復加入問題* @since 1.0.1*/private final Set<K> keySet = new HashSet<>();@Overridepublic CacheEntry<K,V> doEvict(ICacheContext<K, V> context, final K newKey) {CacheEntry<K,V> result = null;// 超過限制,執行移除if(isNeedEvict(context)) {K evictKey = queue.remove();keySet.remove(evictKey);// 移除最開始的元素V evictValue = doEvictRemove(context, evictKey);result = new CacheEntry<>(evictKey, evictValue);}return result;}@Overridepublic void updateKey(ICacheContext<K, V> context, K key) {if (!keySet.contains(key)) {queue.add(key);keySet.add(key);}}}
這里雖然可以解決 fifo 的刪除問題,但是內存有點浪費。
而且這樣其實順序也太對,每次還是需要更新 queue 的位置。
我們把結構繼續調整一下,用其他的數據結構來替代。
v1.0.1 實現
其他的方式
方案 | 數據結構 | 內存開銷 | 實現難度 | 是否推薦 |
---|---|---|---|---|
Queue + Set | 兩個結構 | 較大 | 簡單 | ? |
LinkedHashSet | 單結構 | 小 | 簡單 | ? 推薦 |
LinkedHashMap | Map+鏈表 | 中等 | 中等 | ? 可選 |
實現
簡單起見,我們使用 LinkedHashSet 來實現。
package com.github.houbb.cache.core.support.evict.impl;import com.github.houbb.cache.api.ICacheContext;
import com.github.houbb.cache.core.model.CacheEntry;
import com.github.houbb.cache.core.support.evict.AbstractCacheEvict;import java.util.*;/*** 丟棄策略-先進先出* @author binbin.hou* @since 0.0.2*/
public class CacheEvictFifo<K,V> extends AbstractCacheEvict<K,V> {/*** queue 信息* @since 0.0.2*/private final Set<K> accessOrder = new LinkedHashSet<>();;@Overridepublic CacheEntry<K,V> doEvict(ICacheContext<K, V> context, final K newKey) {CacheEntry<K,V> result = null;// 超過限制,執行移除if(isNeedEvict(context)) {Iterator<K> iterator = accessOrder.iterator();K evictKey = iterator.next();V evictValue = doEvictRemove(context, evictKey);iterator.remove();// 移除最開始的元素result = new CacheEntry<>(evictKey, evictValue);}return result;}@Overridepublic void updateKey(ICacheContext<K, V> context, K key) {accessOrder.remove(key);accessOrder.add(key);}}
這樣我們的目標算是達成了,實現了內存和性能的平衡。
拓展信息
開源矩陣
下面是一些緩存系列的開源矩陣規劃。
名稱 | 介紹 | 狀態 |
---|---|---|
resubmit | 防止重復提交核心庫 | 已開源 |
rate-limit | 限流核心庫 | 已開源 |
cache | 手寫漸進式 redis | 已開源 |
lock | 開箱即用的分布式鎖 | 已開源 |
common-cache | 通用緩存標準定義 | 已開源 |
redis-config | 兼容各種常見的 redis 配置模式 | 已開源 |
quota-server | 限額限次核心服務 | 待開始 |
quota-admin | 限額限次控臺 | 待開始 |
flow-control-server | 流控核心服務 | 待開始 |
flow-control-admin | 流控控臺 | 待開始 |
手寫 Redis 系列
java從零手寫實現redis(一)如何實現固定大小的緩存?
java從零手寫實現redis(三)redis expire 過期原理
java從零手寫實現redis(三)內存數據如何重啟不丟失?
java從零手寫實現redis(四)添加監聽器
java從零手寫實現redis(五)過期策略的另一種實現思路
java從零手寫實現redis(六)AOF 持久化原理詳解及實現
java從零手寫實現redis(七)LRU 緩存淘汰策略詳解
java從零開始手寫redis(八)樸素 LRU 淘汰算法性能優化
java從零開始手寫redis(九)LRU 緩存淘汰算法如何避免緩存污染
java從零開始手寫redis(十)緩存淘汰算法 LFU 最少使用頻次
java從零開始手寫redis(十一)緩存淘汰算法 COLOK 算法
java從零開始手寫redis(十二)過期策略如何實現隨機 keys 淘汰
java從零開始手寫redis(十三)redis漸進式rehash詳解
java從零開始手寫redis(十四)JDK HashMap 源碼解析
java從零開始手寫redis(十四)JDK ConcurrentHashMap 源碼解析
java從零開始手寫redis(十五)實現自己的 HashMap
java從零開始手寫redis(十六)實現漸進式 rehash map
java從零開始手寫redis(十七)v1.0.0 代碼重構+拓展性增強
java從零開始手寫redis(十八)緩存淘汰算法 FIFO 優化