本地缓存小计

1.概念

      本地缓存,在我们日常开发中是必不可少的一种解决数据读取性能问题的方法。简单的说,cache 就是为了提升系统性能而开辟的一块内存空间,SpringBoot 1.x版本中的默认本地cache是Guava Cache。在2.x(Spring Boot 2.0(spring 5) )版本中已经用Caffine Cache取代了Guava Cache。毕竟有了更优的缓存淘汰策略。这2个都是线程安全的,可以指定容量,多种过期策略,主要特性是将数据写入缓存时是原子操作。当缓存的数据达到最大规模时,会使用“最近最少使用(LRU)”算法来清除缓存数据。
每一条数据还可以基于时间回收,未使用时间超过一定时间后,数据会被回收。当缓存被清除时,会发送通知告知。还提供访问统计功能。

2.缓存常见淘汰算法

淘汰算法其实就是当缓存被用满时清理数据的优先顺序

2.1 FIFO(First In First out)

算法原理按照“先进先出(First In,First Out)”的原理淘汰数据。

实现步骤原理如下:

1. 新访问的数据插入FIFO队列尾部,数据在FIFO队列中顺序移动;

2. 淘汰FIFO队列头部的数据;

局限性:在这种淘汰算法中,先进入缓存的会先被淘汰,会导致命中率很低。

2.2 LRU(Least recently used)Guava Cache利用的就是此算法

算法的原理根据数据的历史访问记录来进行数据淘汰。核心思想“如果数据最近被访问过,那么将来被访问的几率也更高”。实现步骤原理如下:

1. 新数据插入到链表头部;

2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;

3. 当链表满的时候,将链表尾部的数据丢弃。

优点和局限性:如果有个数据在 1 分钟访问了 1000次,再后 1 分钟没有访问这个数据,但是有其他的数据访问,就导致了我们这个热点数据被淘汰。LRU可以很好的应对突发流量的情况,因为他不需要累计数据频率。但LRU通过历史数据来预测未来是局限的,它会认为最后到来的数据是最可能被再次访问的,从而给与它最高的优先级。

2.3 LFU(Least frequently used)

算法的原理根据数据的历史访问频率来进行数据淘汰。其核心思想“如果数据过去被访问多次,那么将来被访问的频率也更高”。LFU的每个数据块都有一个引用计数,所有数据块按照引用计数排序,具有相同引用计数的数据块则按照时间排序。
,通过利用额外的空间记录每个数据的使用频率,然后选出频率最低进行淘汰。这样就避免了 LRU 不能处理时间段的问题,其具体实现步骤原理如下:

1. 新加入数据插入到队列尾部(因为引用计数为1);

2. 队列中的数据被访问后,引用计数增加,队列重新排序;

3. 当需要淘汰数据时,将已经排序的列表最后的数据块删除。

优点:在 LFU 中只要数据访问模式的概率分布随时间保持不变时,其命中率就能变得非常高。

局限性:1需要给每个记录项维护频率信息,每次访问都需要更新,这是个巨大的开销 2如果数据访问模式随时间有变,LFU的频率信息无法随之变化,因此早先频繁访问的记录可能会占据缓存,而后期访问较多的记录则无法被命中

2.4 TinyLFU Caffine Cache利用的就是此算法的变种算法

背景: 前三种策略各有利弊,实现的成本也是一个比一个高,同时命中率也是一个比一个好。Guava Cache虽然有这么多的功能,但是本质上还是对LRU的封装,大多数的缓存设计都是基于LRU或者其变种来进行的。相比之下,LRU并不需要维护昂贵的缓存记录元信  息,同时也能够反应随时间变化的数据访问模式。然而,在许多负载之下,LRU依然需要更多的空间才能做到跟LFU一致的缓存命中率。而TinyLFU缓存有综合两者的长处。

1维护记录项频率信息:TinyLFU借助了数据流Sketching技术,Count-Min Sketch(布隆过滤器的一种变种)可以用小得多的空间存放频率信息

2反应随时间变化的访问模式:TinyLFU采用了一种基于滑动窗口的时间衰减设计机制,借助于一种简易的reset操作:每次添加一条记录到Sketch的时候,都会给一个计数器上加1,当计数器达到一个尺寸W的时候,把所有记录的Sketch数值都除以2,该reset操作可以起到衰减的作用

2.5 JetCache 综合框架 

背景: JetCache 是一个基于 Java 的缓存系统封装,提供统一的 API 和注解来简化缓存的使用。JetCache 提供了比 SpringCache 更加强大的注解,可以原生的支持 TTL、两级缓存、分布式自动刷新,还提供了 Cache 接口用于手工缓存操作。当前有四个实现,RedisCache、TairCache(此部分未在 github 开源)、CaffeineCache(in memory)和一个简易的 LinkedHashMapCache(in memory),要添加新的实现也是非常简单的。

具体示例参考官网文档

3.使用

3.1  缓存填充策略:一般是手动加载,同步加载,异步加载

3.2 回收策略:
基于大小回收:maximumWeight,maximumSize 两者不可以同时使用。
基于时间回收,expireAfterAccess expireAfterWrite 
基于引用回收:weakKeys():使用弱引用存储键。weakValues():使用弱引用存储值。softValues():使用软引用存储值。

4代码示例

4.1基于LRU算法 ,LinkedHashMap已经很好的实现了,这里在造个轮子

public class LRUCache {
    /**
     * 头结点
     */
    private Node head;
    /**
     * 结束结点
     */
    private Node end;
    /**
     * 缓存存储上线
     */
    private int limit;

    private HashMap<String,Node> hashMap;

    public LRUCache(int limit){
        this.limit=limit;
        hashMap=new HashMap<String,Node>();
    }

    public String get(String key){
        Node node=hashMap.get(key);
        if(node ==null){
            return null;
        }
        refreshNode(node);
        return node.value;
    }

    public void put(String key,String value){
        Node node=hashMap.get(key);
        if(node ==null){
            //如果key不存在,则插入key-value
            if(hashMap.size()>=limit){
                String oldKey=removeNode(head);
                hashMap.remove(oldKey);
            }
            node =new Node(key,value);
            addNode(node);
            hashMap.put(key,node);
        }else{
            //如果key存在。则刷新key-value
            node.value=value;
            refreshNode(node);
        }
    }

    /**
     * 删除节点
     * @param key
     */
    public void remove(String key){
        Node node =hashMap.get(key);
        removeNode(node);
        hashMap.remove(node);
    }

    /**
     * 尾部插入结点
     * @param node
     */
    private void addNode(Node node){
        if(end !=null){
            end.next=node;
            node.pre=end;
            node.next=null;
        }
        end=node;
        if(head ==null){
            head=node;
        }
    }

    /**
     * 要删除的节点
     * @param node
     * @return
     */
    private String removeNode(Node node){
        if(node == head && node ==end){
            //移除唯一的节点
            head =null;
            end=null;
        }else if(node ==end){
            //移除尾节点
            end=end.pre;
            end.next=null;
        }else if(node ==head){
            head=head.next;
            head.pre=null;
        }else{
            node.pre.next=node.next;
            node.next.pre=node.pre;
        }
        return node.key;
    }

    /**
     * 刷新被访问的节点位置
     * @param node
     */
    private void refreshNode(Node node){
        //如果访问的节点是尾节点,则无须移动节点
        if(node ==end){
            return;
        }
        //移除节点
        removeNode(node);
        //重新插入节点
        addNode(node);
    }

    class Node{
        public Node pre;
        public Node next;
        public String key;
        public String value;
        Node(String key,String value){
            this.key=key;
            this.value=value;
        }
    }


    public static void main(String[] args) {
        LRUCache lruCache=new LRUCache(5);
        lruCache.put("001","用户1的信息");
        lruCache.put("002","用户1的信息");
        lruCache.put("003","用户1的信息");
        lruCache.put("004","用户1的信息");
        lruCache.put("005","用户1的信息");
        lruCache.get("002");
        lruCache.put("004","用户2的信息更新");
        lruCache.put("006","用户6的信息");
        System.out.println(lruCache.get("001"));
        System.out.println(lruCache.get("006"));

    }

 

5参考链接

Guava:

https://blog.csdn.net/jiangzhexi/article/details/55209887

https://github.com/CoderLyd/myblog/issues/5#issue-577736339

Caffine Cache:

https://www.cnblogs.com/rickiyang/p/11074158.html
 

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值