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. python中json与pickle的简要说明

    import json    ======> 注意:不同语言之间通用但不能传输对象类型 该模块中最重要的方法: 1.json.dump(‘python数据’,‘json文件’)   # 将pyt ...

  2. 关于String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";

    关于 String path = request.getContextPath(); String basePath = request.getScheme()+"://"+req ...

  3. java获取当前年份、月份和日期字符串等

    Java获取当前年份.月份和日期是通过Calendar类的实例对象来获取的. 首先创建一个Calendar类的实例对象,Calendar类属于java.util包. Calendar calendar ...

  4. 基于Spring Boot+Spring Security+JWT+Vue前后端分离的开源项目

    一.前言 最近整合Spring Boot+Spring Security+JWT+Vue 完成了一套前后端分离的基础项目,这里把它开源出来分享给有需要的小伙伴们 功能很简单,单点登录,前后端动态权限配 ...

  5. 读后感:数据结构与算法JavaScript描述

    本书看完,对常见的数据结构与算法从概念上有了更深入的理解. 书中关于数组.栈和队列.链表.字典.散列.集合.二叉树.图.排序.检索.动态规划.贪心算法都有详细的介绍.算是一本不错的学习书籍. 栈和队列 ...

  6. abp去掉AbpUser中的Name,Surname,去掉姓和名分离

    abp是国外的框架,默认的框架中的AbpUser表中的Name和Surname是分开的,这不符合国情:可以先去掉 1. 在User类中重写Name和Surname,并设置为私有 2. 在DbConte ...

  7. C# -- is和as运算符

    一.is运算符用于判断一个对象是否可以转换为指定的类型,不会抛出异常,返回bool值用来表示是否转换成功: if (myObj is MyClass) { //do... } 当满足以下任意一种情况时 ...

  8. url编码本质

    url编码本质 其实url本质就是将中文字符串进行utf8编码,然后得到编码后的对象转换字符串去掉开头的b'以及末尾的',然后再将\x转换成%,再将里面内容x变成e最后将字符串小写变成大写 举例 #拿 ...

  9. 松软科技web课堂:SQLServer之HAVING 子句

    HAVING 子句 在 SQL 中增加 HAVING 子句原因是,WHERE 关键字无法与合计函数一起使用. SQL HAVING 语法 SELECT column_name, aggregate_f ...

  10. docker tomcat8 mysql8部署常见错误

    出现docker: out of memory bug ,内存溢出 解决方法: free -mh 应该看一下内存 内存优化/买内存 出现java.util.zip.ZipException: erro ...