Redis 系列(04-2)Redis原理 - 内存回收

目录

  • Redis 系列(04-2)Redis原理 - 内存回收

    • Redis 系列目录
    • 1. 过期策略
      • 1.1 定时过期(主动淘汰)
      • 1.2 惰性过期(被动淘汰)
      • 1.3 定期过期
    • 2. 淘汰策略
      • 2.1 最大内存设置
      • 2.2 淘汰策略
      • 2.4 LFU

Redis 系列(04-2)Redis原理 - 内存回收

Redis 系列目录

相关文档推荐:

  1. Redis - LRU

Reids 所有的数据都是存储在内存中的,在某些情况下需要对占用的内存空间进行回收。内存回收主要分为两类,一类是 key 过期,一类是内存使用达到上限(max_memory)触发内存淘汰。

Redis 设置 key 的过期时间:

expire k1 1         # 设置过期时间s
expireat k1 1       # 设置过期时间,时间戳s
pexpire k1 1000     # 设置过期时间ms
pexpireat k1 1      # 设置过期时间,时间戳ms

ttl k1
persist k1          # 取消过期时间设置

1. 过期策略

要实现 key 过期,我们有几种思路。

  1. 定期过期(主动淘汰)
  2. 惰性过期(被动淘汰)
  3. 定期过期

1.1 定时过期(主动淘汰)

每个设置过期时间的 key 都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的 CPU 资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。

1.2 惰性过期(被动淘汰)

只有当访问一个 key 时,才会判断该 key 是否已过期,过期则清除。该策略可以最大化地节省 CPU 资源,却对内存非常不友好。极端情况可能出现大量的过期 key 没有再次被访问,从而不会被清除,占用大量内存。

  1. 获取 key 时判断是否过期。调用 server.c 中 expireIfNeeded(redisDb db, robj key) 判断 key 是否过期。
  2. 写入 key 时,发现内存不够,调用 expire.c 中 activeExpireCycle 释放一部分内存。

1.3 定期过期

每隔一定的时间,会扫描一定数量的数据库的 expires 字典中一定数量的key,并清除其中已过期的 key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得 CPU 和内存资源达到最优的平衡效果。

typedef struct redisDb {
    dict *dict;                 // *redisDb中保存的数据
    dict *expires;              // *设置expire过期的key
    ...
} redisDb;

Redis 中同时使用了惰性过期和定期过期两种过期策略。

问题:如果都不过期,Redis 内存满了怎么办?

2. 淘汰策略

Redis 的内存淘汰策略,是指当内存使用达到最大内存极限时,需要使用淘汰算法来决定清理掉哪些数据,以保证新数据的存入。

2.1 最大内存设置

redis.conf 参数配置:

maxmemory <bytes>

如果不设置 maxmemory 或者设置为 0,64 位系统不限制内存,32 位系统最多使用 3GB 内存。

动态配置:

127.0.0.1:6379> config set maxmemory 2GB

2.2 淘汰策略

redis.conf 中提供了 8 种缓存淘汰策略,默认是 noeviction,即当内存到达上限时不删除 key 而直接抛出异常。

# maxmemory-policy noeviction

# volatile-lru -> Evict using approximated LRU among the keys with an expire set.
# allkeys-lru -> Evict any key using approximated LRU.
# volatile-lfu -> Evict using approximated LFU among the keys with an expire set.
# allkeys-lfu -> Evict any key using approximated LFU.
# volatile-random -> Remove a random key among the ones with an expire set.
# allkeys-random -> Remove a random key, any key.
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
# noeviction -> Don't evict anything, just return an error on write operations.

动态配置:

127.0.0.1:6379> config set maxmemory-policy volatile-lru

表1 Redis缓存淘汰策略

淘汰策略 说明
volatile-lru 根据LRU 算法删除设置了超时属性(expire)的键,直到腾出足够内存为止。
如果没有可删除的键对象,回退到noeviction 策略。
allkeys-lru 根据LRU 算法删除键,不管数据有没有设置超时属性,直到腾出足够内存为止。
volatile-lfu 在带有过期时间的键中选择最不常用的。
allkeys-lfu 在所有的键中选择最不常用的,不管数据有没有设置超时属性。
volatile-random 在带有过期时间的键中随机选择。
allkeys-random 随机删除所有键,直到腾出足够内存为止。
volatile-ttl 根据键值对象的ttl 属性,删除最近将要过期数据。如果没有,回退到noeviction 策略。
noeviction 默认策略,不会删除任何数据,拒绝所有写入操作并返回客户端错误信息(error)OOM,
此时Redis 只响应读操作。

如果没有符合前提条件的key 被淘汰,那么 volatile-lru、volatile-random 、volatile-ttl 相当于 noeviction(不做内存回收)。建议使用 volatile-lru,在保证正常服务的情况下,优先删除最近最少使用的 key。

Redis 中内存淘汰有三种算法:

  • LRU(Least Recently Used):最近最少使用。判断最近被使用的时间,目前最远的
    数据优先被淘汰。
  • LFU(Least Frequently Used):最不常用,4.0 版本新增。
  • TTL(Time To Live):存活时间。
  • random:随机删除。

### 2.3 LRU

Redis LRU 对传统的 LRU 算法进行了改良,通过随机采样来调整算法的精度。如果淘汰策略是 LRU,则根据配置的采样值 maxmemory-samples(默认是5 个),随机从数据库中选择 m 个 key, 淘汰其中热度最低的 key 对应的缓存数据。所以采样参数 m 配置的数值越大,就越能精确的查找到待淘汰的缓存数据,但是也消耗更多的 CPU 计算,执行效率降低。

redis.conf 参数配置:

maxmemory-samples 5

问题1:如何找出热度最低的数据?

Redis 中所有对象结构都有一个 lru 字段,且使用了 unsigned 的低 24 位,这个字段用来记录对象的热度。对象被创建时会记录 lru 值。在被访问的时候也会更新 lru 的值。但是不是获取系统当前的时间戳,而是设置为全局变量server.lruclock 的值。

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS;  /* 记录最后一次的访问时间,与 LRU、LFU 垃圾回收算法有关
                             * LRU time (relative to global lru_clock) or
                             * LFU data (least significant 8 bits frequency
                             * and most significant 16 bits access time). */
    int refcount;
    void *ptr;
} robj;

在 server.c 中有一个定时任务 serverCron, 默认每100 毫秒调用函数 updateCachedTime 更新一次全局变量的server.lruclock 的值,它记录的是当前unix时间戳。这样 lru 获取时间时不用每次都直接调用系统函数,提高效率。

在 alibaba sentinel 限流中也需要频繁获取时间戳,因此也采取了类似的方案,在 TimeUtils 中也有一个定时任务,每 1ms 更新一次系统时间戳。

当对象里面已经有了 LRU 字段的值,就可以评估对象的热度了。函数 evict.c/estimateObjectIdleTime 评估指定对象的 lru 热度,思想就是对象的 lru 值和全局的 server.lruclock 的差值越大(越久没有得到更新),该对象热度越低。

2.4 LFU

最不常用数据淘汰策略。当采用 LFU 淘汰算法时,redisObject 对象 lru 字段存储的数据结构不同。

typedef struct redisObject {
    unsigned lru:LRU_BITS;  /* LRU time (relative to global lru_clock) or
                             * LFU data (least significant 8 bits frequency
                             * and most significant 16 bits access time). */
    ...
} robj;

当这 24 bits 用作 LFU 时,其被分为两部分:

  • 高 16 位用来记录访问时间(单位为分钟,ldt,last decrement time)
  • 低8 位用来记录访问频率,简称 counter(logc,logistic counter)

counter 是用基于概率的对数计数器实现的,8 位可以表示百万次的访问频率。对象被读写的时候,lfu 的值会被更新。

在 db.c 中调用 updateLFU 更新 lru 的值:

void updateLFU(robj *val) {
    unsigned long counter = LFUDecrAndReturn(val);
    counter = LFULogIncr(counter);
    val->lru = (LFUGetTimeInMinutes()<<8) | counter;
}

总结: 可以看到高 16 位记录访问时间 LFUGetTimeInMinutes(),低 8 位记录访问频率 counter。

在 Redis 中并不是简单的每访问一次,统计数就 +1,lfu 增加或减少都有一个影响因子。在 redis.conf 中配置如下:

# +--------+------------+------------+------------+------------+------------+
# | factor | 100 hits   | 1000 hits  | 100K hits  | 1M hits    | 10M hits   |
# +--------+------------+------------+------------+------------+------------+
# | 0      | 104        | 255        | 255        | 255        | 255        |
# +--------+------------+------------+------------+------------+------------+
# | 1      | 18         | 49         | 255        | 255        | 255        |
# +--------+------------+------------+------------+------------+------------+
# | 10     | 10         | 18         | 142        | 255        | 255        |
# +--------+------------+------------+------------+------------+------------+
# | 100    | 8          | 11         | 49         | 143        | 255        |
# +--------+------------+------------+------------+------------+------------+

# lfu-log-factor 10
# lfu-decay-time 1
  • lfu-log-factor:增长因子。值越大,counter 增长的越慢。默认为 10,表示每 100 hits 次访问增加 10。
  • lfu-decay-time:减少因子(分钟)来控制。默认为 1,表示 N 分钟没有访问就要减少 N。


每天用心记录一点点。内容也许不重要,但习惯很重要!

原文地址:https://www.cnblogs.com/binarylei/p/11717604.html

时间: 2024-11-05 23:38:20

Redis 系列(04-2)Redis原理 - 内存回收的相关文章

Redis系列三:Redis常用设置

一.redis的配置文件redis.conf位置 centos:默认在/etc/redis.conf ubuntu:可以从解压缩后的目录里将配置文件复制到该目录 二.启动redis时指定配置文件 $redis-server /etc/redis.conf 三.常用配置说明 daemonize yes #设置后台运行,默认redis不在后台运行 logfile "/var/log/redis.log" #设置log文件地址,默认使用标准输出,即直接打印在命令行终端的窗口上 require

Redis系列(二):Redis的5种数据结构及其常用命令

上一篇博客,我们讲解了什么是Redis以及在Windows和Linux环境下安装Redis的方法, 没看过的同学可以点击以下链接查看: Redis系列(一):Redis简介及环境安装. 本篇博客我们来讲解下Redis的5种数据结构及其常用命令,5种数据结构分别为: 字符串String 列表List 集合Set 散列Hash 有序集合ZSet 注意事项:Redis可以存储键(key)值(value)对的映射,其中键(key)一直是String,而值可以是上面提到的5种数据结构中的一种. 1. 字符

Redis系列--2、Redis配置

1.Redis配置 在Redis有配置文件(redis.conf)可在Redis的根目录下找到.可以通过Redis的CONFIG命令设置所有Redis的配置. 2.配置文件说明: 1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程 daemonize no 2. 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定 pidfile /var/run/redis.pid 3. 指定

Redis系列七:redis持久化

redis支持RDB和AOF两种持久化机制,持久化可以避免因进程退出而造成数据丢失 一.RDB持久化 RDB持久化把当前进程数据生成快照(.rdb)文件保存到硬盘的过程,有手动触发和自动触发 手动触发有save和bgsave两命令 save命令:阻塞当前Redis,直到RDB持久化过程完成为止,若内存实例比较大会造成长时间阻塞,线上环境不建议用它 bgsave命令:redis进程执行fork操作创建子线程,由子线程完成持久化,阻塞时间很短(微秒级),是save的优化,在执行redis-cli s

初识Redis系列之三:Redis支持的数据类型及使用

支持的数据类型有五种: string(字符串).hash(哈希).list(列表).set(集合)及zset(sorted set:有序集合): 下面分别对这几种类型进行简单的Redis存取操作 1:string(字符串) 几种里面最常用,也是最简单的类型,使用方式如下: redis 127.0.0.1:6379> SET name "runoob" OK redis 127.0.0.1:6379> GET name "runoob" 注意:一个键最大能

Redis系列-远程连接redis并给redis加锁

假设两台redis服务器,ip分别为:192.168.1.101和192.168.1.103,如何在101上通过redis-cli访问103上的redis呢?在远程连接103之前,先讲下redis-cli的几个关键参数: 用法:redis-cli [OPTIONS] [cmd [arg [arg ...]]] -h <主机ip>,默认是127.0.0.1 -p <端口>,默认是6379 -a <密码>,如果redis加锁,需要传递密码 --help,显示帮助信息 通过对

Redis系列--5、Redis事务

Redis事务让一组命令在单个步骤中执行.事务中有两个属性,这说明如下: 在一个事务中所有命令按顺序执行作为一个单一独立的操作.这是不可能的,到另一个客户端发出的请求被担任过Redis事务的执行过程中. Redis事务也是原子的.原子就意味着要么所有命令都执行,要么都不进行处理. 例子 Redis事务由指令 MULTI 启动,然后需要传递事务,而且整个事务是通过执行命令执行后,执行命令的列表. redis 127.0.0.1:6379> MULTI OK List of commands her

Redis系列--6、Redis Java连接操作

安装 要在Java程序中使用使用操作Redis,需要确保有Redis的Java驱动程序和Java设置在机器上.可以检查看Java教程-学习如何在机器上安装Java.现在,让我们来看看如何设置Redis的Java驱动程序. 需要下载jedis.jar.请一定要下载它的最新版本. 需要包括jedis.jar到你的类路径中. 连接到Redis服务器 import redis.clients.jedis.Jedis; public class RedisJava {    public static v

Redis系列一(Redis环境的搭建)

最近工作中需要Redis缓存,由于也是第一次弄,在网上找了很多资料,在此记录一下. 安装Redis 我使用的系统是CentOS 6.6,安装步骤如下: 1.下载源码,解压后编译源码.(如果没有 wget命令 先从yum上下载 yum install wget)  $ wget http://download.redis.io/releases/redis-2.8.3.tar.gz  $ tar xzf redis-2.8.3.tar.gz  $ cd redis-2.8.3  $ make 2.