从零开始手写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 优化

相关推荐
fatfishccc31 分钟前
Spring MVC 全解析:从核心原理到 SSM 整合实战 (附完整源码)
java·spring·ajax·mvc·ssm·过滤器·拦截器interceptor
没有bug.的程序员39 分钟前
MyBatis 初识:框架定位与核心原理——SQL 自由掌控的艺术
java·数据库·sql·mybatis
执键行天涯1 小时前
从双重检查锁定的设计意图、锁的作用、第一次检查提升性能的原理三个角度,详细拆解单例模式的逻辑
java·前端·github
程序员江鸟1 小时前
Java面试实战系列【JVM篇】- JVM内存结构与运行时数据区详解(私有区域)
java·jvm·面试
架构师沉默1 小时前
Java 状态机设计:替代 if-else 的优雅架构
java·程序员·架构
java亮小白19971 小时前
Spring Cloud 快速通关之Sentinel
java·spring cloud·sentinel
atwednesday1 小时前
大规模文档预览的架构设计与实现策略
java
Dioass2 小时前
Java面向对象中你大概率会踩的五大隐形陷阱
java
一叶飘零_sweeeet2 小时前
在分布式环境下正确使用MyBatis二级缓存
java·分布式·mybatis
肥仔哥哥19302 小时前
基于OpenCv做照片分析(Java)
java·人工智能·opencv·图像原理