用Java写一个分布式缓存——缓存淘汰算法

分布式缓存寻址算法 使⽤相同的hash算法计算数据的hash值,映射到圆环,顺时针寻找,找到的第⼀个服务器就是 数据存储的服务器。hash slot:将数据与服务器隔离开,数据与slot映射,slot与服务器映射,数据进⾏hash决定存放的slot,新增及删除节点时,将slot进⾏迁移即可。hash算法:根据key进⾏hash函数运算、结果对分⽚数取模,确定分⽚ 适合固定分⽚数的场景, 扩展分⽚或者减少分⽚时,所有数据都需要重新计算分⽚、存储。:节点数变化时(如从10→11),90%的缓存需要重新分配。 阅读详情

前言

之前也用过一些缓存中间件,框架,也想着自己是不是也能用Java写一个出来,于是就有了这个想法,打算在写的过程中同步进行总结。

源码:weloe/Java-Distributed-Cache (github.com)

本篇代码:

Java-Distributed-Cache/src/main/java/com/weloe/cache/outstrategy at master · weloe/Java-Distributed-Cache (github.com)

Java-Distributed-Cache/src/test/java/com/weloe/cache/outstrategy at master · weloe/Java-Distributed-Cache (github.com)

我们可以想想几个问题,什么是缓存?为什么需要缓存?

什么是缓存?将之前请求的数据暂存,遇到同样的请求/状况直接返回,这就是缓存。

为什么需要?同样的情况下,直接返回数据,无需其他操作,能加快服务器反应速度,减轻服务器压力。

那么缓存怎么存?简单的缓存为键值对,可以用Map存储。这就完了吗?如果我们一直往Map中存储数据,占用的内存会越来越大,这时候怎么办?

这就是本篇需要解决的问题。

要使用缓存,就必然会面临到缓存使用空间达到上限的问题,这个时候就需要从已有的缓存数据中淘汰一部分去维持缓存的可用性。

LRU

力扣上的相关题 https://leetcode.cn/problems/lru-cache/

LRU,缓存淘汰算法,最近最少使用(Least Recently Used),就是一种选择淘汰数据的策略

原理:为最近被访问的数据进行缓存,淘汰不常被访问的数据。

也就是说我们认为最近使用过的数据应该是有用的,很久都没用过的数据应该是无用的,内存满了就优先删那些很久没用过的数据。

举一个我们最常见的例子,手机可用把软件放到后台运行,比如我们先后打开了日历,设置,闹钟。后台的顺序是 日历->设置->闹钟,如果这台手机只能打开三个应用,再打开 应用商城 ,后台的顺序会变成 应用商城->日历->设置。

从这个案例可以知道LRU的主要两个操作的具体思路,一个数据结构存值,一个数据结构存储后台顺序。

缓存一般以key,value形式存储,因此选择map存储,而存储顺序的数据结构由于要不断改动节点顺序,选择双向链表

public class LRUCache<K, V> implements CacheStrategy<K, V> {
    private Map<K, V> map;
    private int capacity;
    private Deque<K> queue;
    private Callback callback;


    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.map = new HashMap();
        this.queue = new LinkedList();
    }
}

put(key,value)

如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

   	public void put(int key, int value) {
        if (map.containsKey(key)) {
            queue.remove(key);
        }
        queue.addFirst(key);
        map.put(key, value);

        // 缓存达到上限
        if (queue.size() > capacity) {

            // 移除
            K last = queue.removeLast();
            V removeValue = map.remove(last);

            // 回调
            if (callback != null) {
                callback.callback(last, removeValue);
            }
        }
        return value;

    }

get(key)

如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1

    public V get(K key) {
        // 如果已经缓存过该数据
        if (map.containsKey(key)) {
            queue.remove(key);
            queue.addFirst(key);
            return map.get(key);
        }
        return null;
    }

弊端,容易出现缓存污染问题

(k1,v1) (k2,v2),(k3,v3),(k4,v4)

(k2,v2),(k4,v4),(k1,v1),(k3,v3)

LRU-K

LRU-K算法是对LRU算法的改进,将原先进入缓存队列的评判标准从访问一次改为访问K次。

LRU-K算法有两个队列,一个是缓存队列,一个是数据访问历史队列。当访问一个数据时,首先先在访问历史队列中累加访问次数,当历史访问记录超过K次后,才将数据缓存至缓存队列,从而避免缓存队列被污染。同时访问历史队列中的数据可以按照LRU的规则进行淘汰。具体如下:

public class LRUKCache<K, V> extends LRUCache<K, V> {

    // 进入缓存队列的评判标准
    private int putStandard;

    // 访问数据历史记录
    private LRUCache<Object, Integer> historyList;

    public LRUKCache(int cacheSize, int historyCapacity, int putStandard) {
        super(cacheSize);
        this.putStandard = putStandard;
        this.historyList = new LRUCache(historyCapacity);
    }


    @Override
    public V get(K key) {
        // 记录数据访问次数
        Integer historyCount = historyList.get(key);
        historyCount = historyCount == null ? 0 : historyCount;
        historyList.put(key, ++historyCount);
        return super.get(key);
    }

    @Override
    public V put(K key, V value) {
        if (value == null) {
            return null;
        }
        // 如果已经在缓存里则直接返回
        if (super.get(key) != null) {
            return super.put(key, value);
        }
        // 如果数据历史访问次数达到上限,则加入缓存
        Integer historyCount = historyList.get(key);
        historyCount = (historyCount == null) ? 0 : historyCount;
        if (removeCache(historyCount)) {
            // 移除历史访问记录,加入缓存
            historyList.remove(key);
            return super.put(key, value);
        }

        return value;
    }

    private boolean removeCache(Integer historyCount) {
        return historyCount >= putStandard;
    }

    public void setPutStandard(int putStandard) {
        this.putStandard = putStandard;
    }

    @Override
    public void setCallback(Callback<K, V> callback) {
        super.setCallback(callback);
    }

    public void setHistoryListCallback(Callback<K, V> callback) {
        historyList.setCallback((Callback<Object, Integer>) callback);
    }

}

LRU-K能降低缓存污染发生的概率,但是需要额外记录对象访问次数,内存消耗较大。

测试

class LRUCacheTest {

    @Test
    void lru(){
        CacheStrategy<Integer, Integer> lruCache = new LRUCache<>(5);
        lruCache.setCallback((integer, integer2) -> System.out.println("淘汰"+integer+"="+integer2));
        lruCache.put(1,1);
        lruCache.put(2,2);
        lruCache.put(3,3);
        lruCache.put(4,4);
        lruCache.put(5,5);
        lruCache.put(6,6);
        List list = lruCache.list();
        System.out.println(list);
    }

}
淘汰1=1
[2=2, 3=3, 4=4, 5=5, 6=6]
class LRUKCacheTest {

    @Test
    void lrukCacheTest() {
        LRUKCache<Integer, Integer> lrukCache = new LRUKCache<>(2,3,1);
        lrukCache.setHistoryListCallback((integer, integer2) -> System.out.println("记录队列淘汰"+integer+"="+integer2));
        lrukCache.setCallback((integer, integer2) -> System.out.println("缓存淘汰"+integer+"="+integer2));
        lrukCache.get(1);
        lrukCache.get(1);
        lrukCache.get(1);
        lrukCache.get(2);
        lrukCache.get(2);
        lrukCache.get(2);
        lrukCache.get(3);
        lrukCache.get(3);
        lrukCache.get(3);
        lrukCache.get(4);
        lrukCache.get(4);
        lrukCache.get(4);
        lrukCache.put(1,2);
        lrukCache.put(2,2);
        lrukCache.put(3,2);
        lrukCache.put(4,2);
        List list = lrukCache.list();
        System.out.println(list);
    }
}
记录队列淘汰1=3
缓存淘汰2=2
[3=2, 4=2]
Java-EE在Java领域的分布式缓存应用 在当今的Java开发领域,随着应用程序规模的不断扩大和用户访问量的急剧增加,传统的单节点缓存已经难以足系统的性能需求。Java-EE(Java Enterprise Edition)作为企业级Java开发的标准平台,为分布式缓存的应用提供了强大的支持。本文章的目的在于深入探讨Java-EE环境下分布式缓存的应用,涵盖分布式缓存的原理、实现算法、实际应用场景以及相关工具和资源等方面。通过对这些内容的详细分析,帮助开发者更好地理解和运用分布式缓存技术,提升Java应用程序的性能和可扩展性。 阅读详情

相关推荐

分布式缓存:基于Java缓存淘汰算法实现

在上述示例中,我们创建了一个容量为3的缓存实例,然后依次将键值对(1, 100)、(2, 200)和(3, 300)存储到缓存中。最后,我们再次使用put方法存储一个新的键值对(4, 400),这时由于缓存,将会触发缓存淘汰策略,淘汰最近最少使用的数据(键为1的数据)。然而,由于分布式系统的特性,设计一个高效的分布式缓存系统需要考虑到数据一致性、分布式缓存同步、缓存淘汰等方面的问题。然而,要实现一个真正的分布式缓存系统,还需要考虑数据分片、缓存同步、故障恢复等更多复杂的问题。

CodeShiftZ的博客 99

京东一面:你怎么实现一个缓存了自动淘汰最近最少使用缓存

想要在Java中设计一个缓存,当缓存了的时候自动淘汰最近使用最少的那个。也就是LRU缓存机制。首先,我得回忆一下LRU的原理。LRU是Least Recently Used的缩,即最近最少使用,当缓存容量达到上限时,删除最久未使用的数据。那Java中怎么实现呢?通常可以用哈希表加双向链表的结构。哈希表用来快速查找缓存项,而双向链表用来维护访问顺序。每次访问一个节点,就把它移到链表头部,这样链表尾部就是最近最少使用的节点,当需要淘汰时,删除链表尾部的节点。

sjkflw121150的博客 505

如何使用Java实现一个缓存淘汰机制LRU??即最近最少使用

🏆本文收录于 《全栈Bug调优(实战版)》 专栏,该专栏专注于分享我在真实项目开发中遇到的各类疑难Bug及其深层成因,并系统提供高效、可复现的解决思路和实操方案。无论你是刚入行的新手开发者,还是拥有多年项目经验的资深工程师,本专栏都将为你提供一条系统化、高质量的问题排查与优化路径,助力你加速成长,攻克技术壁垒,迈向技术价值最大化与职业发展的更高峰🚀!

**My Coding Family** 1162

算法缓存淘汰策略 及Java实现

1. LRU(Least Recently Used)最近最少使用 2. LFU(Least Frequently Used)最近最不经常使用 3. 近似LRU算法 4. TTL(Time To Live)超时时间 5. FIFO(First In First Out)先进先出 6. Random随机淘汰

dreambyday 758

Java分布式环境下并发编程实践

随着分布式系统越来越普及,分布式系统中的并发编程成为了一个重要的话题。Java作为一种高级编程语言,其并发编程能力得到了广泛的认可。但在分布式系统中,Java并发编程面临着一些新的挑战。本文将介绍在分布式系统下Java并发编程的一些技术和实际案例。

笑小枫 1290

java mom,概述-分布式 Java(Distributed Java)

概述面向消息的中间件(MOM)或者消息队列(MQ),提供了以松散耦合的灵活方式集成应用程序的一种机制。它们提供了基于存储和转发的应用程序之间的异步数据发送,即应用程序彼此不直接通信,而是与作为中介的 MOM 通信。MOM 提供了有保证的消息发送,应用程序开发人员无需了解远程过程调用(PRC)和网络/通信协议的细节。消息中间件简介远程过程调用有助于隐藏分布式系统中的通信细节,也就是说增强了访问透明性...

weixin_42622428的博客 4700

JavaDS-优先级队列(堆)

优先级队列(堆)基础知识

YeZh1Yong 的博客 1377

缓存之Lru缓存淘汰算法

随着近些年大型电商应用(例如京东、阿里巴巴等)的迅速发展,本地缓存分布式缓存相关技术被大量的应用。那么这些缓存的核心算法都有哪些呢,上次给大家手了FIFO先进先出算法的实现过程,感兴趣的可以通过手缓存之FIFO先进先出算法来进行学习和回顾。那么今天给大家介绍的是Lru淘汰缓存算法。手缓存之FIFO先进先出算法 Lru算法是近些年来一些大型电商平台比较推荐的一种算法,其原理是在我们的缓存了以后按照访问数量来进行淘汰,优先淘汰那些访问次数比较低的缓存数据,大家来看下面的示例图来加深理解。 大家可以从图

m0_72106802的博客 1076

SpringCache + Caffeine + Redis整合本地缓存分布式缓存

文章目录背景Caffeine 简介相对于Guava Cache优化点整合缓存配置缓存使用自定义缓存删除注解reids 事件监听删除缓存测试测试类测试结果总结源码下载 背景 为什么我们明明有了分布式缓存redis,还要将本地缓存多此一举整合为分布式缓存呢。原因很简单,性能。不管redis多块,都需要网络请求,io耗时,如果使用本地缓存基本没有耗时。 Caffeine 简介 官方文档 Caffeine 是基于 JAVA 8 的高性能缓存库。并且在 spring5 (springboo 2.x) 后,spring

念念不忘,必有回响 7838

缓存淘汰缓存穿透、缓存击穿、缓存雪崩、数据库缓存一致性

缓存淘汰 为什么需要缓存淘汰?你需要缓存30G的数据,但是Redis本身只能使用10G的内存,那你就得做个取舍了,毕竟鱼与熊掌不可兼得。为了利益最大化肯定要保留最重要的10个G。 Redis本身提供了6中缓存淘汰策略,以下属性表示允许使用的最大内存 1 server.maxmemory 当使用的内存超过限制内存时,Redis会根据配置的以下6...

Java学习录 329

Java中Spring Cloud的分布式缓存方案

在当今的分布式系统中,缓存是提高系统性能和响应速度的关键技术之一。Spring Cloud作为Java领域中广泛使用分布式系统开发框架,提供了多种分布式缓存的集成方案。本文的目的是深入探讨Spring Cloud中可用的分布式缓存方案,包括其原理、实现方式、应用场景等。范围涵盖了常见的分布式缓存技术,如Redis、Caffeine等,以及如何在Spring Cloud项目中集成和使用这些缓存技术。

AI开发架构师 985

Spring Boot缓存过期策略优化:TTL与淘汰算法深度解析

Spring Boot缓存技术深度解析与优化实践 本文系统介绍了Spring Boot缓存技术的核心概念与实践方案。主要内容包括: 缓存基础架构:详细解析Spring Cache抽象层,包括Cache接口、CacheManager及常用注解(@Cacheable、@CachePut等)的工作原理与使用场景。 自动配置机制:阐述Spring Boot如何通过条件化配置智能选择缓存实现(JCache/Redis/Caffeine等)及其优先级策略。 关键技术细节:深入讲解缓存键生成策略的设计原则与实现方法,避免

本博客聚焦 YOLOv11 全流程落地,涵盖架构优化、数据集处理、训练技巧与多场景部署,兼及国产数据库、Java 开发与 AI 模型应用,内容兼顾理论与工程实践,为开发者提供系统干货,助力高效提升技术能力。 1117

Java 架构 19:缓存架构设计(本地缓存 + 分布式缓存

本文探讨了Java应用中本地缓存分布式缓存的协同架构设计。通过Caffeine实现高性能本地缓存(微秒级响应),结合Redis构建分布式共享缓存,形成多级缓存防御体系。文章详细介绍了: 缓存价值:降低数据库压力,提升系统吞吐(案例显示性能提升25倍) 架构设计:本地缓存(L1)处理80%请求,Redis(L2)处理19%,数据库仅1% 技术实现: Caffeine集成与智能淘汰策略 Redis部署与Spring Boot整合 完整商品缓存示例代码 监控优化:缓存命中率统计与性能调优建议 适用于中高级开发者

千淘万漉虽辛苦,吹尽狂沙始到金 2万+

缓存分类以及常见的缓存淘汰策略】

缓存分类:本地缓存分布式缓存、多级缓存; 常见的缓存淘汰策略:最近最少使用策略、先进先出策略、最不经常使用策略、随即替换策略、基于缓存大小的淘汰、基于缓存项的生命周期的淘汰

独上高楼,望尽天涯路。 979

如何设计一个分布式缓存系统?

如何设计一个分布式缓存系统?

weixin_46619605的博客 1308

技术交流:分布式缓存的原理及应用

分布式缓存的原理及应用 缓存(进程级缓存分布式缓存缓存指将需要频繁访问的数据存放在内存中以加快用户访问速度的一种技术。缓存分进程级缓存分布式缓存,进程级缓存指将数据缓存在服务内部,通过Map、List等结构实现缓存分布式缓存指将数据单独存放在分布式系统中,以便于缓存的统一管理和存取。常用的分布式缓存系统有Ehcache、Redis和Memcached。 分布式缓存 对于传统单点的web系统一般使用进程式缓存即可,而在微服务架构下往往需要一个分布式缓存来实现跨服务的缓存系统。 如图所示,用户访问的数

FrankCai的博客 2361
上一篇: PDF,Word文件转换工具
下一篇: 用Java写一个分布式缓存——缓存管理
秋玻
博客等级 码龄5年 1粉丝 · 20原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值