从零开始手写redis(18)缓存淘汰算法 FIFO 优化

项目简介

大家好,我是老马。

Cache 用于实现一个可拓展的高性能本地缓存。

有人的地方,就有江湖。有高性能的地方,就有 cache。

v1.0.0 版本

以前的 FIFO 实现比较简单,但是 queue 循环一遍删除的话,性能实在是太差。

于是想到引入一个 Set 存储有哪些 key,改成下面的方式:

java 复制代码
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<>();

    @Override
    public 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;
    }

    @Override
    public 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 来实现。

java 复制代码
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<>();;

    @Override
    public 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;
    }

    @Override
    public 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 优化

相关推荐
xbgRS18 分钟前
RocketMQ中的broker
java·rocketmq
晓晓_za89866837 分钟前
Geo 优化源码二次开发:自定义地域规则改造实操文档
java·开发语言·搜索引擎·ci/cd·矩阵
摇滚侠38 分钟前
《SpringBoot 3:入门与应用实战》第 8 章 AOP 的进阶机制和应用 阅读笔记 16
java·spring boot·笔记
AI人工智能+电脑小能手1 小时前
大白话说Java设计模式-38-迭代器模式(业务实战篇)
java·设计模式·迭代器模式·stream·遍历封装·集合通用·分页遍历
QQ_21696290961 小时前
【源码编号:project86570】SpringBoot电影院在线选座售票系统:电影排片、在线选座、订单购票、后台管理完整实战
java·spring boot·后端
谢亮_vipxieliang1 小时前
手机号验证技术方案:号段规则与正则表达式
java·服务器·spring boot·正则表达式·hibernate
wang09072 小时前
自己动手写一个tomcat之7监听器和过滤器
java·tomcat
梅孔立2 小时前
Pi-Agent 终极极简配置文档(Java+Vue+Python爬虫 4-5千文件项目)
java·vue.js·python
Query*2 小时前
Agent 开发之项目 AI Native 化:通过大模型与 RAG 赋予产品智能能力
java·人工智能·ai
孫治AllenSun2 小时前
【LangChain4J-03】Springboot 项目搭建框架
java·spring boot·后端