万隆的笔记 万隆的笔记
博文索引
笔试面试
  • 在线学站

    • 菜鸟教程 (opens new window)
    • 入门教程 (opens new window)
    • Coursera (opens new window)
  • 在线文档

    • w3school (opens new window)
    • Bootstrap (opens new window)
    • Vue (opens new window)
    • 阿里开发者藏经阁 (opens new window)
  • 在线工具

    • tool 工具集 (opens new window)
    • bejson 工具集 (opens new window)
    • 文档转换 (opens new window)
  • 更多在线资源
  • Changlog
  • Aboutme
GitHub (opens new window)
博文索引
笔试面试
  • 在线学站

    • 菜鸟教程 (opens new window)
    • 入门教程 (opens new window)
    • Coursera (opens new window)
  • 在线文档

    • w3school (opens new window)
    • Bootstrap (opens new window)
    • Vue (opens new window)
    • 阿里开发者藏经阁 (opens new window)
  • 在线工具

    • tool 工具集 (opens new window)
    • bejson 工具集 (opens new window)
    • 文档转换 (opens new window)
  • 更多在线资源
  • Changlog
  • Aboutme
GitHub (opens new window)
  • Redis

    • Redis简介
    • 缓存的认识
    • Linux安装Redis的正确方式
    • Redis数据类型
    • Redis底层数据结构
    • Redis回收策略与缓存过期
      • maxmemory配置
      • 回收策略
      • expire过期
      • 过期策略
      • 总结
    • Redis持久化
    • Redis发布与订阅
    • Redis事务
    • Redis Lua脚本
    • Redis 慢查询
    • Redis 监视器
    • Redis通讯协议
    • Redis事件处理机制与NIO演进
    • Redis 常用命令
    • Redis与MyBatis整合
    • Spring、SpringBoot整合Redis
  • 集群架构

  • Redis
  • Redis
2022-03-30
目录

Redis回收策略与缓存过期

# Redis回收策略与缓存过期

将redis当做使用LRU算法的缓存来使用 (opens new window)

内存优化 (opens new window)

在探讨Redis的缓存过期与淘汰策略之前,首先需要明确一点:你的Redis是用来干嘛的?是用做缓存还是DB数据库?

因为在配置Redis的内存,以及设置缓存过期与淘汰策略,这些都与你的Redis的用途有关。首先要有以下基本常识:

  • 缓存用途:缓存的数据不是全量数据,会随着访问的变化而变化,通常保留的是热数据。
  • DB用途:一般要求保持数据的完整性,不能宕机,不能淘汰,支持横向拓展。

基于这些常识,接下来去探讨Redis的maxmemory设置、expire过期的使用以及淘汰策略的选择。

# maxmemory配置

有两种方式可以设置Redis的内存:

  1. 在redis.conf中

    maxmemory 100mb // 64位的系统是0, 代表没有内存限制,
    
  2. 通过 CONFIG SET (opens new window) 命令设置。

    127.0.0.1:6379> config get maxmemory
    1) "maxmemory"
    2) "0"
    127.0.0.1:6379> config set maxmemory xxx
    

当我们设置了maxmemory后,Redis会分配几乎和maxmemory一样大的内存。那么我们到底改设置多大呢?

  • Redis作为DB使用,保证数据的完整性,不能淘汰,可以做集群,横向扩展。这个时候可以不设置。
  • Redis作为缓存使用,假如我们不设置,当达到物理内存后,性能可能会持续下降,甚至崩溃(内存与硬盘交换swap虚拟内存,频繁IO)。这个时候需要根据具体的业务去设置。通常情况下,在满足业务运行的情况下,剩下的内存可以都给Redis,此外,如果有slaver从节点,注意也要流出一部分的内存。

当Redis快要达到指定的内存限制大小时,则会根据配置的不同的策略对内存进行回收。

# 回收策略

收了解回收策略前,对LRU和LFU有个基本的理解:

  • LRU(Least Recently Used)算法:最近最少使用页面置换算法,淘汰最长时间未被使用的页面。
  • LFU(Least Frequently Used)算法:最近最不常用页面置换算法,淘汰一定时期内被访问次数最少的页。

区别:LRU关键是看页面最后一次被使用到发生调度的时间长短;而LFU关键是看一定时间段内页面被使用的频率。

再简单得说就是,LRU-多久没有碰它,很久没碰的淘汰;LFU-一段时间碰了多少次,碰的少的淘汰。

Redis的maxmemory-policy配置指令来进行回收策略的配置,回收策略有以下几种:

  • noeviction(默认):禁止驱逐,不淘汰。直接返回错误。
  • allkeys-lru:所有的键通过LRU驱逐,即从所有键中淘汰长时间未被使用的键。
  • volatile-lru:对在过期集合的键通过LRU驱逐,即从过期键中淘汰长时间未被使用的键。
  • allkeys-lfu:所有的键通过LFU驱逐,即从所有键中淘汰一定时间内使用次数最少的键。
  • volatile-lfu:对在过期集合的键通过LFU驱逐,即从过期键中一定时间内使用次数最少的键。
  • allkeys-random:随机回收所有键。
  • volatile-random:随机回收过期集合的键。
  • volatile-ttl:回收在过期集合的键,并优先回收存活时间(TTL)较短的键。

从上面可以知道,Redis的回收策略无非就是从两个方面考虑:

  • 收集的键的范围:所有键还是过期键
  • 使用的算法:是最近最少使用LRU还是最近最不常使用或者是随机。

除了上面的方面考虑,当然还有额外的情况比如不回收以及根据过期键存活时间(volatile-ttl)回收。

# 一般的选择

回收策略选择一般取决于你的应用的访问模式,此外,我们还可以监控缓存命中率和没命中的次数,在运行时进行相关的策略调整,一般来说有一下推荐:

  • 使用allkeys-lru策略:如果Redis中部分的数据是属于热点数据,在不确定时一般选这个。
  • 使用allkeys-random:如果Redis中所有的数据热点分布比较均衡,选择随机回收。
  • 使用volatile-ttl:如果Redis中有大量通过创建缓存对象时设置TTL值,来决定哪些对象应该被过期,那么建议选择volatile-ttl。不过,设置过期时间也是需要消耗内存的,所以有时候使用allkeys-lru这种策略反而更加高效。

# 回收进程工作流程

回收进程工作流:

  • 一个客户端运行了新的命令,添加了新的数据。
  • Redis检查内存使用情况,如果大于maxmemory的限制,则根据设定好的策略进行回收。
  • 执行新的命令等等

也就是说,每次执行一个命令之前,Redis都会对内存进行校验或者会输。如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存就会超出限制。

# expire过期

EXPIRE key seconds (opens new window)

上面我们提到volatile-ttl,也就是说我们是可以给key赋予过期时间的,主要是用expire命令设置一个键的存活时间(ttl: time to live),过了这段时间,该键就会自动被删除。

# expire的使用

expire命令的使用方法如下:expire key ttl(单位秒)

127.0.0.1:6379> set k1 zhangsan
OK
127.0.0.1:6379> ttl k1 # 没有过期时间,持久化的
(integer) -1
127.0.0.1:6379> expire k1 3
(integer) 1
127.0.0.1:6379> ttl k1
(integer) 2
127.0.0.1:6379> ttl k1 # 过期失效
(integer) -2
127.0.0.1:6379> get k1
(nil)

# 注意事项

  • 设置了expire的key,并不会随着访问而延长!
  • 如果发生了写数据,那么会剔除设置的expire时间!
127.0.0.1:6379> set k2 lisi
OK
127.0.0.1:6379> expire k2 30
(integer) 1
127.0.0.1:6379> ttl k2
(integer) 26
127.0.0.1:6379> set k2 wangwu
OK
127.0.0.1:6379> ttl k2
(integer) -1

# expire原理

redisDb的结构体源码:

typedef struct redisDb { 
  int id; // id是数据库序号,为0-15(默认Redis有16个数据库) 
  long avg_ttl; // 存储的数据库对象的平均ttl(time to live),用于统计 
  dict *dict; // 存储数据库所有的key-value(重点)
  dict *expires; // 存储key的过期时间(重点)
  dict *blocking_keys; // blpop 存储阻塞key和客户端对象 
  dict *ready_keys; // 阻塞后push 响应阻塞客户端 存储阻塞后push的key和客户端对象 
  dict *watched_keys; //存储watch监控的的key和客户端对象 
} redisDb;

这个结构体定义中除了 id 以外都是指向字典的指针,其中我们只看 dict 和 expires。

  • dict 用来维护一个 Redis 数据库中包含的所有 Key-Value 键值对
  • expires 则用于维护一个 Redis 数据库中设置了失效时间的键(即key与失效时间的映射)。

当我们使用 expire 命令设置一个key的失效时间时,Redis 首先到 dict 这个字典表中查找要设置的key是否存在,如果存在就将这个key和失效时间添加到 expires 这个字典表。

当我们使用 set 命令向系统插入数据时,Redis 首先将 Key 和 Value 添加到 dict 这个字典表中,然后将 Key 和失效时间添加到 expires 这个字典表中。

简单地总结来说就是,设置了失效时间的key和具体的失效时间全部都维护在 expires 这个字典表中。

此外,这里就知道前面回收策略中allkeys-xx(server.db[i].dict)、volatile-xx(server.db[i].expires)这些key是在那里保存了吧。

# 过期策略

现在讲讲Redis的过期策略(删除策略),包括对底层结构的结构以及LRU的更加详细解读。

等等,刚才不是讲了回收策略,怎么现在又出了个过期策略啊?什么东西??

其实这是站在不同的角度看Redis删除Key的方式而已,本质上还是对内存的回收。先前讲的回收策略是站在Redis对maxmemory兜底时候进行内存回收的策略,这里则主要是看Redis对过期键的回收处理,回收策略的设置当然也会影响到回收处理。

过期键淘汰策略分为定时删除、惰性删除、主动删除(回收策略)。

过期判定原理:

Redis目前采用惰性删除+主动删除的方式,即被动+主动。

# 定时删除

在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作。需要创建定时器,而且消耗CPU,一般不推荐使用。

# 惰性删除

在key被访问时如果发现它已经失效,那么就删除它。

底层原理是调用expireIfNeeded函数,该函数的意义是:读取数据之前先检查一下它有没有失效,如果失效了就删除它。

这里注意,如果Redis正在从RDB文件中加载数据,是暂时不会处理失效主键的。

int expireIfNeeded(redisDb *db, robj *key) { 
  // 获取主键的失效时间 get当前时间-创建时间>ttl 
  long long when = getExpire(db,key); 
  // 假如失效时间为负数,说明该主键未设置失效时间(失效时间默认为-1),直接返回0 
  if (when < 0) return 0; 
  // 假如Redis服务器正在从RDB文件中加载数据,暂时不进行失效主键的删除,直接返回0 
  if (server.loading) return 0; 
  ...
  // 如果以上条件都不满足,就将主键的失效时间与当前时间进行对比,如果发现指定的主键 
  // 还未失效就直接返回0 
  if (mstime() <= when) return 0; 
  // 如果发现主键确实已经失效了,那么首先更新关于失效主键的统计个数,然后将该主键失效的信息进行广播,
  // 最后将该主键从数据库中删除 
  server.stat_expiredkeys++; 
  propagateExpire(db,key); 
  return dbDelete(db,key); 
}

# 主动删除(回收策略)

仅仅做惰性删除是不够的,因为有些过期的keys,可能永远不会被访问。 无论如何,这些keys应该过期,所以Redis还会定时随机删除:具体就是Redis每秒10次做的事情:

  • 测试随机的20个keys进行相关过期检测。
  • 删除所有已经过期的keys。
  • 如果有多于25%的keys过期,重复步奏1.

这是一个平凡的概率算法,基本上的假设是,在不断重复过期检测中,直到过期的keys的百分百低于25%。这意味着,在任何给定的时刻,最多会清除1/4的过期keys。

使用的策略就是在redis.conf文件中配置的回收策略,上面的回收策略已经进过,这里不再赘述。

# 总结

对于内存的设置以及回收策略的选择,其实是根据我们Redis的使用场景以及业务场景来运转的,因为内存是有限制的,Redis中应该尽量保存热数据,而对冷数据进行淘汰。

对于过期的键,Redis是同构 被动访问时判定以及周期轮询增量判定的方式进行回收的。

上次更新: 5/30/2023, 11:42:20 PM
Redis持久化

Redis持久化→

最近更新
01
2025
01-15
02
Elasticsearch面试题
07-17
03
Elasticsearch进阶
07-16
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式