Redis的回收策略

  • noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
  • allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
  • volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
  • allkeys-random: 回收随机的键使得新添加的数据有空间存放。
  • volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
  • volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。

注意:

如果没有键满足回收的前提条件的话,策略volatile-lru, volatile-random以及volatile-ttl就和noeviction差不多了。

回收策略选择

选择正确的回收策略是非常重要的,这取决于你的应用的访问模式,不过你可以在运行时进行相关的策略调整,并且监控缓存命中率和没命中的次数,通过RedisINFO命令输出以便调优。

一般的经验规则:

  • 使用allkeys-lru策略:当你希望你的请求符合一个幂定律分布,也就是说,你希望部分的子集元素将比其它其它元素被访问的更多。如果你不确定选择什么,这是个很好的选择。.
  • 使用allkeys-random:如果你是循环访问,所有的键被连续的扫描,或者你希望请求分布正常(所有元素被访问的概率都差不多)。
  • 使用volatile-ttl:如果你想要通过创建缓存对象时设置TTL值,来决定哪些对象应该被过期。

注意:

  • allkeys-lruvolatile-random策略对于当你想要单一的实例实现缓存及持久化一些键时很有用。不过一般运行两个实例是解决这个问题的更好方法。

  • 为了键设置过期时间也是需要消耗内存的,所以使用allkeys-lru这种策略更加高效,因为当内存有压力时,没有必要为键去设置过期时间。

回收进程如何工作

理解回收进程如何工作是非常重要的:

  • 一个客户端运行了新的命令,添加了新的数据。
  • Redis检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。
  • 一个新的命令被执行,等等。
  • 所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。

说明:

Maxmemory:maxmemory配置指令用于配置Redis存储数据时指定限制的内存大小。通过redis.conf可以设置该指令,或者之后使用CONFIG SET命令来进行运行时配置。

如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。

LRU算法

Redis使用的是近似的LRU算法,通过少量keys进行取样,然后回收其中一个最好的key(被访问时间较早的)。从redis3.0以后,redis改进了回收键的候选池,也就是改善了算法的性能,使得更加近似真的LRU算法。

Redis LRU有个很重要的点,你通过调整每次回收时检查的采样数量,以实现调整算法的精度。这个参数可以通过以下的配置指令调整:maxmemory-samples

Redis为什么不使用真实的LRU实现是因为这需要太多的内存。不过近似的LRU算法对于应用而言应该是等价的。

java的LRU算法实现

LRU是Least Recently Used的缩写,即最少使用,常用于页面置换算法,为虚拟页式存储管理服务。LRU算法也经常被用作缓存淘汰策略。

方式一:

基于JDK提供的LinkedHashMap的实现。

LinkedHashMap说明:

  • LinkedHasnMap内部维护的是双向链表,用来维护插入顺序或LRU顺序。
  • 它继承HashMap,具有快速查询的特性。
  • 头节点head是最老的节点,尾节点是最近一次被访问的节点,维护LRU顺序时,容量用完后,再执行插入新的数据时,会将头节点移除。
  • accessOrder表示维护那种顺序,默认为false。true:代表LRU顺序,false:代表插入顺序。

示例

public class LRUCache<k, v> extends LinkedHashMap<k, v> {

  //最多能缓存的数据
  private final int CACHE_SIZE;

  public LRUCache(int cacheSize) {
    //true 表示让linkedHashMap按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部
    super((int)Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
    CACHE_SIZE = cacheSize;
  }

  @Override
  protected boolean removeEldestEntry(Map.Entry<k, v> eldest) {
    //当map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据
    return size() > CACHE_SIZE;
  }
}

方式二:

原理:其实根据方式一我们可以推到出,可以使用一个双向链表来缓存数据。

详细逻辑:

  • 新的数据插入到链表的头
  • 每当链表中的数据被访问,就将该数据移动到链表头
  • 当链表容量大于定义容量时,删除链表的尾部数据

示例

/**
 * 双向链表
 */
public class Node {
  private int key;
  private int val;
  private Node prev;
  private Node next;
}

public class LRUCache {
  //容量
  private int capacity;
    //头节点
  private Node head;
  //尾节点
  private Node last;

  private Map<Integer, Node> map;

  public LRUCache(int capacity) {
    this.capacity = capacity;
    map = new HashMap<>(capacity);
  }

  public int get(int key) {
    Node node = map.get(key);
    if (node == null) {
      return -1;
    }
    removeHead(node);
    return node.val;
  }

  private void removeHead(Node node) {
    if (node == head) {
      return;
    }
    if (node == last) {
      last.prev.next = null;
      last = last.prev;
    } else {
      node.prev.next = node.next;
      node.next.prev = node.prev;
    }
    node.prev = head.prev;
    node.next = head;

    head.prev = node;
    head = node;
  }

  public void put(int key, int value) {
    Node node = map.get(key);
    if (node == null) {
      node = new Node();
      node.key = key;
      node.val = value;

      if (map.size() == capacity) {
        removeLast();
      }
      addHead(node);
      map.put(key, node);
    } else {
      node.val = value;
      removeHead(node);
    }
  }

  private void addHead(Node node) {
    if (map.isEmpty()) {
      head = node;
      last = node;
    } else {
      node.next = head;
      head.prev = node;
      head = node;
    }
  }

  private void removeLast() {
    map.remove(last.key);
    Node prev = last.prev;
    if (prev != null) {
      prev.next = null;
      last = prev;
    }
  }

  @Override
  public String toString() {
    return map.keySet().toString();
  }
}

优点:实现逻辑简单

缺点:每次访问都需要更新链表数据


参考redis官网lru算法

Redis 笔记整理:回收策略与 LRU 算法的更多相关文章

  1. Redis的内存回收策略和内存上限(阿里)

    还有一篇文章 讲解guava如何删除过期数据的,与redis不同,guava没有维护线程删除过期key,只是在设置 key 或者 读取key的时候,顺带删除参考:GuavaCache简介(一)是轻量级 ...

  2. Redis备份及回收策略

    Redis备份(持久化) Redis备份存在两种方式: 1.一种是"RDB".是快照(snapshotting),它是备份当前瞬间Redis在内存中的数据记录; 2.另一种是&qu ...

  3. Redis笔记整理

    Redis 遵守BSD协议.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库.数据结构服务器. 特点:     1.Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时 ...

  4. 面试题之redis的内存回收策略

    1.maxmemory-policy noeviction(默认):内存空间不足会报错 2.allkeys-lru:最少使用的数据去淘汰 3.allkeys-random:随机淘汰一些key 4.vo ...

  5. Redis内存回收:LRU算法

    Redis技术交流群481804090 Redis:https://github.com/zwjlpeng/Redis_Deep_Read Redis中采用两种算法进行内存回收,引用计数算法以及LRU ...

  6. Redis笔记4-持久化方案

    一:快照模式 默认redis是会以快照的形式将数据持久化到磁盘的(一个二进制文件,dump.rdb,这个文件名字可以指定),在配置文件中的格式是:save N M表示在N秒之内,redis至少发生M次 ...

  7. 动手实现 LRU 算法,以及 Caffeine 和 Redis 中的缓存淘汰策略

    我是风筝,公众号「古时的风筝」. 文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在里面. 那天我在 LeetCode 上刷到一道 LRU 缓存机制的问题, ...

  8. Redis内存回收策略

    如果使用Redis的时候,不合理使用内存,把什么东西都放在内存里面,又不设置过期时间,就会导致内存的堆积越来越大.根据28法则,除了20%的热点数据之外,剩余的80%的非热点或不怎么重要的数据都在占用 ...

  9. redis 数据删除策略和逐出算法

    数据存储和有效期 在 redis 工作流程中,过期的数据并不需要马上就要执行删除操作.因为这些删不删除只是一种状态表示,可以异步的去处理,在不忙的时候去把这些不紧急的删除操作做了,从而保证 redis ...

随机推荐

  1. C#&.Net干货分享- 构建PrinterHelper直接调用打印机相关操作

    namespace Frame.Printer{    /// <summary>    ///     /// </summary>    public class Prin ...

  2. Python如何动态的为对象添加方法或属性,__slots__用法

    代码示例如下: import types    #使用MethodType方法需要导入包 class test(object):  #定义 一个test类,包含name属性和f()方法 def __i ...

  3. 菜鸟刷面试题(一、Java基础篇)

    目录: JDK 和 JRE 有什么区别? == 和 equals 的区别是什么? 两个对象的 hashCode()相同,则 equals()也一定为 true,对吗? final 在 java 中有什 ...

  4. 字典树(Trie)详解

    详解字典树(Trie) 本篇随笔简单讲解一下信息学奥林匹克竞赛中的较为常用的数据结构--字典树.字典树也叫Trie树.前缀树.顾名思义,它是一种针对字符串进行维护的数据结构.并且,它的用途超级广泛.建 ...

  5. Swoole 启动一个服务,开启了哪些进程和线程?

    目录 概述 代码 小结 概述 Swoole 启动一个服务,开启了哪些进程和线程? 为了解决这个问题,咱们启动一个最简单的服务,一起看看究竟启动了哪些进程和线程? 然后结合官网运行流程图,对每个进程和线 ...

  6. go语言之面向对象

    Go 语言结构体 Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型. 结构体是由一系列具有相同类型或不同类型的数据构成的数据集合. 结构体表示一项记录,比如保存图 ...

  7. 2019年百度最新Java工程师面试题

    一.单选题(共27题,每题5分) 1若下列所用变量均已经正确定义,以下表达式中不合法的是?   A.x>>3 B.+++j C.a=x>y?x:y D.x%=4 参考答案:B 答案解 ...

  8. C# 异步转同步 PushFrame

    异步转同步-PushFrame 本文通过PushFrame,实现异步转同步 首先有一个异步方法,如下异步任务延时2秒后,返回一个结果 private static async Task<stri ...

  9. SVN 回滚提交的代码

    有的时候,代码提交错了,我们可以通过SVN回滚到指定的版本,然后在提交回滚后的代码,即为撤销提交. 回滚代码 重新提交刚才回滚后的代码

  10. SSM框架之Mybatis(5)数据库连接池及事务

    Mybatis(5)数据库连接池及事务 1.Mybatis连接池 ​ Mybatis 中也有连接池技术,但是它采用的是自己的连接池技术.在 Mybatis 的 SqlMapConfig.xml 配置文 ...