首页 > 编程语言 >用Java写一个分布式缓存——缓存淘汰算法

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

时间:2023-01-13 18:55:16浏览次数:61  
标签:lrukCache 缓存 Java get historyCount key put 分布式

前言

之前也用过一些缓存中间件,框架,也想着自己是不是也能用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]

标签:lrukCache,缓存,Java,get,historyCount,key,put,分布式
From: https://www.cnblogs.com/weloe/p/17050512.html

相关文章

  • Java 正则表达式详解_正则表达式
    Java正则表达式详解_正则表达式如果你不熟悉这个术语,那么“正则表达式”(RegularExpression)就是一个字符构成的串,它定义了一个用来搜索匹配字符串的模式。​​正则表达式3......
  • Java生成和操作Excel文件 - 残星
    JAVAEXCELAPI:是一开放源码项目,通过它Java开发人员可以读取Excel文件的内容、创建新的Excel文件、更新已经存在的Excel文件。使用该API非Windows操作系统也可以通过纯......
  • React中实现keepalive组件缓存效果
    背景:由于react官方并没有提供缓存组件相关的api(类似vue中的keepalive),在某些场景,会使得页面交互性变的很差,比如在有搜索条件的表格页面,点击某一条数据跳转到详情页面,再返回......
  • 顺着这份Java面试地图,国内一二线互联网公司随便进...
    原创:陶朱公Boy(微信公众号ID:taozhugongboy),欢迎分享,转载请保留出处。前言临近春节,这几天手头没什么事情,花了点时间,将自己近两年收集的面试真题,进行了一番深度归纳总结,......
  • Java 以上超大文件上传和断点续传服务器的实现
    ​ 第一点:Java代码实现文件上传FormFilefile=manform.getFile();StringnewfileName= null;Stringnewpathname= null;StringfileAddre= "/numUp";try{......
  • JavaScript学习笔记—循环
    JS三种循环语句while语句do-while语句for语句通常编写一个循环,需要有三个条件:(1)初始化表达式(2)条件表达式(3)更新表达式1.while循环语法while(condition){......
  • java中关于继承,多态及方法调用的底层细节
    java中关于继承,多态及方法调用的底层细节一、继承继承已存在的类就是复用(继承)这些类的方法和域。在此基础上,还可以添加一些新的方法和域,以满足新的需求。子类会拥有......
  • java数据类型
    Java语言支持的数据类型分为两种:基本数据类型(PrimitiveType)和引用数据类型(ReferenceType)Java基本数据类型基本数据类型包括Boolean(布尔值)、flot(单精度浮点型)、char(字......
  • 如何通过Java应用程序将Word转为Excel
    平时在工作中,很多小伙伴会习惯性地将文件保存为Word文档格式,但有时会发现某些文件如果保存成Excel表格可能会更好地呈现。例如有的文本在Word文本中不如在Excel工作表编辑计......
  • kettle java 代码调用
    使用程序调用kettlepom.xml点击查看代码<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.or......