>데이터 베이스 >Redis >Redis의 메모리 제거 전략과 만료된 삭제 전략의 차이점

Redis의 메모리 제거 전략과 만료된 삭제 전략의 차이점

WBOY
WBOY앞으로
2022-08-01 15:12:342937검색

추천 학습: Redis 비디오 튜토리얼

머리말

Redis는 키의 만료 시간을 설정할 수 있으므로 만료된 키-값 쌍을 삭제하는 해당 메커니즘이 필요하며 이 작업은 만료입니다. 키값 삭제 전략.

Redis의 "메모리 제거 전략"과 "만료 삭제 전략"은 많은 친구들이 쉽게 혼동합니다. 이 두 메커니즘은 모두 삭제 작업을 수행하지만 사용되는 트리거 조건과 전략은 다릅니다.

오늘은 "메모리 제거 전략"과 "만료 삭제 전략"에 대해 설명드리겠습니다.


만료된 삭제 전략

Redis는 키의 만료 시간을 설정할 수 있으므로 만료된 키-값 쌍을 삭제하는 해당 메커니즘이 필요하며 이 작업은 만료된 키-값 삭제입니다. 전략.

만료 시간을 어떻게 설정하나요?

먼저 키의 만료 시간을 설정하는 명령에 대해 이야기해 보겠습니다. 키 만료 시간을 설정하는 명령은 4가지가 있습니다.

  • expire 42538adbdb6240b2b083a000a615d5bd 751fecf49c9d13ca89ee2cbb9b75d4f6: 키가 n초 후에 만료되도록 설정합니다. 예를 들어, 만료 키 100은 키가 100초 후에 만료되도록 설정하는 것을 의미합니다.
  • pexpire 8a66db7248cea4b091ced72fb7100c95 751fecf49c9d13ca89ee2cbb9b75d4f6: n밀리초 후에 키가 만료되도록 설정합니다. 예를 들어 pexpire key2 100000은 key2가 100000밀리초(100초) 후에 만료되도록 설정한다는 의미입니다.
  • expireat key751fecf49c9d13ca89ee2cbb9b75d4f6: 특정 타임스탬프(밀리초 단위로 정확함) 후에 키가 만료되도록 설정합니다. 예를 들어 pexpireat key4 1655654400000은 키4가 타임스탬프 1655654400000(밀리초 단위로 정확함) 후에 만료됨을 의미합니다. 문자열을 설정할 때 동시에 3가지 명령을 설정할 수도 있습니다.
  • set 8487820b627113dd990f63dd2ef215f3 ex 751fecf49c9d13ca89ee2cbb9b75d4f6
set 8487820b627113dd990f63dd2ef215f3 px 751fecf49c9d13ca89ee2cbb9b75d4f6 : 키-값 쌍을 설정할 때 만료 시간(밀리초 단위)도 지정합니다. ;

setex 42538adbdb6240b2b083a000a615d5bd 8487820b627113dd990f63dd2ef215f3 : 설정 키-값 쌍을 입력할 때 만료 시간도 지정합니다(초 단위).
  • 키의 남은 생존 시간을 확인하려면 TTL 42538adbdb6240b2b083a000a615d5bd 명령을 사용하세요.
  • # 设置键值对的时候,同时指定过期时间位 60 秒
    > setex key1 60 value1
    OK
    # 查看 key1 过期时间还剩多少
    > ttl key1
    (integer) 56
    > ttl key1
    (integer) 52
  • 갑자기 후회해서 키 만료 시간을 취소했다면 PERSIST 42538adbdb6240b2b083a000a615d5bd 명령을 사용하면 됩니다.
  • # 取消 key1 的过期时间
    > persist key1
    (integer) 1
    
    # 使用完 persist 命令之后,
    # 查下 key1 的存活时间结果是 -1,表明 key1 永不过期 
    > ttl key1 
    (integer) -1
  • 키가 만료되었는지 어떻게 확인하나요?

키에 대한 만료 시간을 설정할 때마다 Redis는 만료 시간과 함께 키를 만료 사전(expires dict)에 저장합니다. 즉, "expires 사전"은 데이터베이스에 있는 모든 키의 만료 시간을 저장합니다. .

만료된 사전은 다음과 같이 redisDb 구조에 저장됩니다.

typedef struct redisDb {
    dict *dict;    /* 数据库键空间,存放着所有的键值对 */
    dict *expires; /* 键的过期时间 */
    ....
} redisDb;

만료된 사전 데이터 구조는 다음과 같습니다.

만료된 사전의 키는 특정 키 개체를 가리키는 포인터입니다.

만료된 사전의 값은 long long 유형의 정수입니다. 이 정수는 키의 만료 시간을 저장합니다.

  • 만료 사전의 데이터 구조는 아래 그림과 같습니다.
  • 사전은 사실 해시 테이블인데, 해시 테이블의 가장 큰 장점은 O(1) 시간 복잡도로 빠르게 검색할 수 있다는 점입니다. 키를 쿼리할 때 Redis는 먼저 만료 사전에 키가 있는지 확인합니다.
키가 없으면 키 값을 정상적으로 읽습니다.

키가 있으면 키의 만료 시간을 가져온 다음 현재 시스템과 비교 시간이 시스템 시간보다 크면 만료되지 않은 것으로 판단됩니다.

만료된 키 결정 프로세스는 아래 그림과 같습니다.

만료된 삭제 전략이란 무엇입니까?

Redis 만료 삭제 전략에 대해 이야기하기 전에 먼저 세 가지 일반적인 만료 삭제 전략을 소개하겠습니다.

  • 定时删除;
  • 惰性删除;
  • 定期删除;

接下来,分别分析它们的优缺点。

定时删除策略是怎么样的?

定时删除策略的做法是,在设置 key 的过期时间时,同时创建一个定时事件,当时间到达时,由事件处理器自动执行 key 的删除操作。

定时删除策略的优点:可以保证过期 key 会被尽快删除,也就是内存可以被尽快地释放。因此,定时删除对内存是最友好的。

定时删除策略的缺点:在过期 key 比较多的情况下,删除过期 key 可能会占用相当一部分 CPU 时间,在内存不紧张但 CPU 时间紧张的情况下,将 CPU 时间用于删除和当前任务无关的过期键上,无疑会对服务器的响应时间和吞吐量造成影响。所以,定时删除策略对 CPU 不友好。

惰性删除策略是怎么样的?惰性删除策略的做法是,不主动删除过期键,每次从数据库访问 key 时,都检测 key 是否过期,如果过期则删除该 key。

惰性删除策略的优点:因为每次访问时,才会检查 key 是否过期,所以此策略只会使用很少的系统资源,因此,惰性删除策略对 CPU 时间最友好。

惰性删除策略的缺点:如果一个 key 已经过期,而这个 key 又仍然保留在数据库中,那么只要这个过期 key 一直没有被访问,它所占用的内存就不会释放,造成了一定的内存空间浪费。所以,惰性删除策略对内存不友好。

定期删除策略是怎么样的?定期删除策略的做法是,每隔一段时间「随机」从数据库中取出一定数量的 key 进行检查,并删除其中的过期key。

定期删除策略的优点:通过限制删除操作执行的时长和频率,来减少删除操作对 CPU 的影响,同时也能删除一部分过期的数据减少了过期键对空间的无效占用。

定期删除策略的缺点:

  • 内存清理方面没有定时删除效果好,同时没有惰性删除使用的系统资源少。
  • 难以确定删除操作执行的时长和频率。如果执行的太频繁,定期删除策略变得和定时删除策略一样,对CPU不友好;如果执行的太少,那又和惰性删除一样了,过期 key 占用的内存不会及时得到释放。

Redis 过期删除策略是什么?

前面介绍了三种过期删除策略,每一种都有优缺点,仅使用某一个策略都不能满足实际需求。

所以, Redis 选择「惰性删除+定期删除」这两种策略配和使用,以求在合理使用 CPU 时间和避免内存浪费之间取得平衡。

Redis 是怎么实现惰性删除的?

Redis 的惰性删除策略由 db.c 文件中的 expireIfNeeded 函数实现,代码如下:

int expireIfNeeded(redisDb *db, robj *key) {
    // 判断 key 是否过期
    if (!keyIsExpired(db,key)) return 0;
    ....
    /* 删除过期键 */
    ....
    // 如果 server.lazyfree_lazy_expire 为 1 表示异步删除,反之同步删除;
    return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
                                         dbSyncDelete(db,key);
}

Redis 在访问或者修改 key 之前,都会调用 expireIfNeeded 函数对其进行检查,检查 key 是否过期:

  • 如果过期,则删除该 key,至于选择异步删除,还是选择同步删除,根据lazyfree_lazy_expire 参数配置决定(Redis 4.0版本开始提供参数),然后返回 null 给客服端;
  • 如果没有过期,不做任何处理,然后返回正常的键值对给客户端;

惰性删除的流程图如下:

Redis 是怎么实现定期删除的?

再回忆一下,定期删除策略的做法:每隔一段时间「随机」从数据库中取出一定数量的 key 进行检查,并删除其中的过期key。

1.这个间隔检查的时间是多长呢?

在 Redis 中,默认每秒进行 10 次过期检查一次数据库,此配置可通过 Redis 的配置文件 redis.conf 进行配置,配置键为 hz 它的默认值是 hz 10。

特别强调下,每次检查数据库并不是遍历过期字典中的所有 key,而是从数据库中随机抽取一定数量的 key 进行过期检查。

2.随机抽查的数量是多少呢?

我查了下源码,定期删除的实现在 expire.c 文件下的 activeExpireCycle 函数中,其中随机抽查的数量由 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 定义的,它是写死在代码中的,数值是 20。

也就是说,数据库每轮抽查时,会随机选择 20 个 key 判断是否过期。

接下来,详细说说 Redis 的定时删除的流程:

  • 从过期字典中随机抽取 20 个 key;
  • 检查这 20 个 key 是否过期,并删除已过期的 key;
  • 如果本轮检查的已过期 key 的数量,超过 5 个(20/4),也就是「已过期 key 的数量」占比「随机抽取 key 的数量」大于 25%,则继续重复步骤 1;如果已过期的 key 比例小于 25%,则停止继续删除过期 key,然后等待下一轮再检查。

可以看到,定时删除是一个循环的流程。

那 Redis 为了保证定时删除不会出现循环过度,导致线程卡死现象,为此增加了定时删除循环流程的时间上限,默认不会超过 25ms。

针对定时删除的流程,我写了个伪代码:

do {
    //已过期的数量
    expired = 0;
    //随机抽取的数量
    num = 20;
    while (num--) {
        //1. 从过期字典中随机抽取 1 个 key
        //2. 判断该 key 是否过期,如果已过期则进行删除,同时对 expired++
    }

    // 超过时间限制则退出
    if (timelimit_exit) return;

  /* 如果本轮检查的已过期 key 的数量,超过 25%,则继续随机抽查,否则退出本轮检查 */
} while (expired > 20/4);

定时删除的流程如下:


内存淘汰策略

前面说的过期删除策略,是删除已过期的 key,而当 Redis 的运行内存已经超过 Redis 设置的最大内存之后,则会使用内存淘汰策略删除符合条件的 key,以此来保障 Redis 高效的运行。

如何设置 Redis 最大运行内存?

在配置文件 redis.conf 中,可以通过参数 maxmemory 4d5e782727adce28d9e975bcb1fbc12c 来设定最大运行内存,只有在 Redis 的运行内存达到了我们设置的最大运行内存,才会触发内存淘汰策略。

不同位数的操作系统,maxmemory 的默认值是不同的:

  • 在 64 位操作系统中,maxmemory 的默认值是 0,表示没有内存大小限制,那么不管用户存放多少数据到 Redis 中,Redis 也不会对可用内存进行检查,直到 Redis 实例因内存不足而崩溃也无作为。
  • 在 32 位操作系统中,maxmemory 的默认值是 3G,因为 32 位的机器最大只支持 4GB 的内存,而系统本身就需要一定的内存资源来支持运行,所以 32 位操作系统限制最大 3 GB 的可用内存是非常合理的,这样可以避免因为内存不足而导致 Redis 实例崩溃。

Redis 内存淘汰策略有哪些?

Redis 内存淘汰策略共有八种,这八种策略大体分为「不进行数据淘汰」和「进行数据淘汰」两类策略。

1.不进行数据淘汰的策略

noeviction(Redis3.0之后,默认的内存淘汰策略) :它表示当运行内存超过最大设置内存时,不淘汰任何数据,而是不再提供服务,直接返回错误。

2.进行数据淘汰的策略

针对「进行数据淘汰」这一类策略,又可以细分为「在设置了过期时间的数据中进行淘汰」和「在所有数据范围内进行淘汰」这两类策略。

在设置了过期时间的数据中进行淘汰:

  • volatile-random:随机淘汰设置了过期时间的任意键值;
  • volatile-ttl:优先淘汰更早过期的键值。
  • volatile-lru(Redis3.0 之前,默认的内存淘汰策略):淘汰所有设置了过期时间的键值中,最久未使用的键值;
  • volatile-lfu(Redis 4.0 后新增的内存淘汰策略):淘汰所有设置了过期时间的键值中,最少使用的键值;

在所有数据范围内进行淘汰:

  • allkeys-random:随机淘汰任意键值;
  • allkeys-lru:淘汰整个键值中最久未使用的键值;
  • allkeys-lfu(Redis 4.0 后新增的内存淘汰策略):淘汰整个键值中最少使用的键值。

如何查看当前 Redis 使用的内存淘汰策略?

可以使用 config get maxmemory-policy 命令,来查看当前 Redis 的内存淘汰策略,命令如下:

127.0.0.1:6379> config get maxmemory-policy
1) "maxmemory-policy"
2) "noeviction"

可以看出,当前 Redis 使用的是 noeviction 类型的内存淘汰策略,它是 Redis 3.0 之后默认使用的内存淘汰策略,表示当运行内存超过最大设置内存时,不淘汰任何数据,但新增操作会报错。

如何修改 Redis 内存淘汰策略?

设置内存淘汰策略有两种方法:

  • 方式一:通过“config set maxmemory-policy c108bdf0b9c068e379c81772fce52805”命令设置。它的优点是设置之后立即生效,不需要重启 Redis 服务,缺点是重启 Redis 之后,设置就会失效。
  • 方式二:通过修改 Redis 配置文件修改,设置“maxmemory-policy c108bdf0b9c068e379c81772fce52805”,它的优点是重启 Redis 服务后配置不会丢失,缺点是必须重启 Redis 服务,设置才能生效。

LRU 算法和 LFU 算法有什么区别?

LFU 内存淘汰算法是 Redis  4.0 之后新增内存淘汰策略,那为什么要新增这个算法?那肯定是为了解决 LRU 算法的问题。

接下来,就看看这两个算法有什么区别?Redis 又是如何实现这两个算法的?

什么是 LRU 算法?

LRU 全称是 Least Recently Used 翻译为最近最少使用,会选择淘汰最近最少使用的数据。

传统 LRU 算法的实现是基于「链表」结构,链表中的元素按照操作顺序从前往后排列,最新操作的键会被移动到表头,当需要内存淘汰时,只需要删除链表尾部的元素即可,因为链表尾部的元素就代表最久未被使用的元素。

Redis 并没有使用这样的方式实现 LRU 算法,因为传统的 LRU 算法存在两个问题:

  • 需要用链表管理所有的缓存数据,这会带来额外的空间开销;
  • 当有数据被访问时,需要在链表上把该数据移动到头端,如果有大量数据被访问,就会带来很多链表移动操作,会很耗时,进而会降低 Redis 缓存性能。

Redis 是如何实现 LRU 算法的?

Redis 实现的是一种近似 LRU 算法,目的是为了更好的节约内存,它的实现方式是在 Redis 的对象结构体中添加一个额外的字段,用于记录此数据的最后一次访问时间。

当 Redis 进行内存淘汰时,会使用随机采样的方式来淘汰数据,它是随机取 5 个值(此值可配置),然后淘汰最久没有使用的那个。

Redis 实现的 LRU 算法的优点:

  • 不用为所有的数据维护一个大链表,节省了空间占用;
  • 不用在每次数据访问时都移动链表项,提升了缓存的性能;

但是 LRU 算法有一个问题,无法解决缓存污染问题,比如应用一次读取了大量的数据,而这些数据只会被读取这一次,那么这些数据会留存在 Redis 缓存中很长一段时间,造成缓存污染。

因此,在 Redis 4.0 之后引入了 LFU 算法来解决这个问题。

什么是 LFU 算法?

LFU 全称是 Least Frequently Used 翻译为最近最不常用的,LFU 算法是根据数据访问次数来淘汰数据的,它的核心思想是“如果数据过去被访问多次,那么将来被访问的频率也更高”。

所以, LFU 算法会记录每个数据的访问次数。当一个数据被再次访问时,就会增加该数据的访问次数。这样就解决了偶尔被访问一次之后,数据留存在缓存中很长一段时间的问题,相比于 LRU 算法也更合理一些。

Redis 是如何实现 LFU 算法的?

LFU 算法相比于  LRU 算法的实现,多记录了「数据的访问频次」的信息。Redis 对象的结构如下:

typedef struct redisObject {
    ...

    // 24 bits,用于记录对象的访问信息
    unsigned lru:24;
    ...
} robj;

Redis 对象头中的 lru 字段,在 LRU 算法下和 LFU 算法下使用方式并不相同。

在 LRU 算法中,Redis 对象头的 24 bits 的 lru 字段是用来记录 key 的访问时间戳,因此在 LRU 模式下,Redis可以根据对象头中的 lru 字段记录的值,来比较最后一次 key 的访问时间长,从而淘汰最久未被使用的 key。

在 LFU 算法中,Redis对象头的 24 bits 的 lru 字段被分成两段来存储,高 16bit 存储 ldt(Last Decrement Time),低 8bit 存储 logc(Logistic Counter)。

  • ldt 是用来记录 key 的访问时间戳;
  • logc 是用来记录 key 的访问频次,它的值越小表示使用频率越低,越容易淘汰,每个新加入的 key 的logc 初始值为 5。

注意,logc 并不是单纯的访问次数,而是访问频次(访问频率),因为 logc  会随时间推移而衰减的。

키에 액세스할 때마다 먼저 logc에서 붕괴 작업이 수행됩니다. 붕괴 값은 이전 액세스 시간과 이번 액세스 시간의 차이가 큰 경우 이렇게 구현된 LFU 알고리즘은 단순히 액세스 횟수가 아닌 액세스 빈도를 기준으로 데이터를 제거합니다. 액세스 빈도는 키 액세스가 발생하는 기간을 고려해야 합니다. 키의 이전 액세스가 현재 시간보다 길어질수록 이 키의 액세스 빈도는 그에 따라 감소하고 제거될 확률은 높아집니다.

logc에서 감쇠 연산을 완료한 후 logc에서 증가 연산을 시작합니다. 증가 연산은 단순히 +1이 아니라 확률에 따라 증가합니다. 키에 대한 logc가 클수록 증가하기가 더 어려워집니다. logc.

그래서 Redis가 키에 액세스하면 logc는 다음과 같이 변경됩니다.

  • 먼저 마지막 액세스부터 현재 시간까지의 시간에 따라 logc를 감쇠합니다.
  • 그런 다음 특정 확률에 따라 logc의 값을 늘립니다.

redis.conf는 logc의 증가와 감소를 제어하기 위해 LFU 알고리즘을 조정하기 위한 두 가지 구성 항목을 제공합니다.

  • lfu-decay-time은 분 단위 값인 logc의 감소 속도를 조정하는 데 사용됩니다. 기본값은 1입니다. lfu-decay-time 값이 클수록 붕괴 속도가 느려집니다.
  • lfu-log-factor는 logc의 증가율을 조정하는 데 사용됩니다. logc의 성장이 느려집니다.

요약

Redis에서 사용하는 만료 삭제 전략은 "지연 삭제 + 일반 삭제"이며, 삭제된 개체는 만료된 키입니다.

메모리 제거 전략은 Redis의 실행 메모리가 최대 실행 메모리를 초과하는 경우 메모리 제거 전략이 실행됩니다. Redis 4.0 이후에는 총 8가지 메모리 제거 전략이 있습니다. 구현된 8가지 전략은 다음과 같이 분류됩니다.

추천 학습:

Redis 비디오 튜토리얼

위 내용은 Redis의 메모리 제거 전략과 만료된 삭제 전략의 차이점의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 jb51.net에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제