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