前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >Redis的淘汰策略与持久化:数据保障与性能兼顾的独特之道

Redis的淘汰策略与持久化:数据保障与性能兼顾的独特之道

原创
作者头像
Lion Long
发布2024-11-19 21:33:56
发布2024-11-19 21:33:56
13300
代码可运行
举报
运行总次数:0
代码可运行

“好事”发生

这里推荐一篇实用的文章:《图灵测试到底是什么?怎么才能测试通过》,作者:【用户10024547】。

https://cloud.tencent.com/developer/article/2467241

文章简述了图灵测试的定义、目的以及发展历程,特别介绍了深度学习模型通过图灵测试意味着什么,以及如何让深度学习模型通过图图灵测试的方案,非常好的一篇实操文章。

接下来开始我们的正文。

一、redis 淘汰策略

淘汰策略可以通过maxmemory-policy参数来选择。默认是禁止淘汰,如果数据达到了最大内存限制,在向redis中写入数据时会报错。

1.1、背景

redis是内存数据库,当数据量足够多时,如果不加以限制,会把当前系统的内存空间耗尽;所以,redis可以设置最大限定内存。当数据达到最大限定内存时,如何处理写数据命令?这就涉及到淘汰策略。

(1)键值过期:expire / pexpire。当key的生存周期达到时,将对应的key-value删除。 (2)对象空转时长。redis支持丰富的数据结构,每次操作value时,redis记录操作的时间和统计对key-value操作次数(8位统计次数,16位记录时间)。

代码语言:javascript
代码运行次数:0
复制
// server.h
typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    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). */
    int refcount;
    void *ptr;
} robj;

lru字段就是用于记录操作value的时间,也会统计对key-value操作了多少次。可以使用object idletime key查询key的空转时长(单位是秒)。

代码语言:javascript
代码运行次数:0
复制
127.0.0.1:6379> set fly 100
OK
127.0.0.1:6379> OBJECT idletime fly
(integer) 37

(3)配置。redis有两个参数配置淘汰策略,maxmemory和maxmemory-policy。maxmemory限定redis可以使用的最大内存(单位是字节 ),一般设置为当前系统可用内存的一半;maxmemory-policy用于制定淘汰策略。

代码语言:javascript
代码运行次数:0
复制
# redis.conf
maxmemory <bytes>
maxmemory-policy noeviction

1.2、过期key

key设置了expire / pexpire的key。 针对过期key,有如下策略可以使用: (1)volatile-lru,最近最少使用(最长时间没有使用)就把它删除。这种模式下 lru整个字段都用于记录时间。 (2)volatile-lfu,最少次数使用就把它删除。这种模式下 记录操作的时间和统计对key-value操作次数(8位统计次数,16位记录时间)。 (3)volatil-ttl,最近要过期就把它删除。TTL指令可以查询key还有多长时间到期。 示例:

代码语言:javascript
代码运行次数:0
复制
127.0.0.1:6379> EXPIRE fly 100
(integer) 1
127.0.0.1:6379> TTL fly
(integer) 93
127.0.0.1:6379> TTL fly
(integer) 23

(4)volatile-random,从已过期的key中随机淘汰。

1.3、所有key

如果没有使用expire / pexpire设置key过期属性,那么就从所有key中进行淘汰。

(1)allkeys-lru,所有key中最近最少使用(最长时间没有使用)的就把它删除。这种模式下 lru整个字段都用于记录时间。 (2)allkeys-lfu,所有key中最少次数使用的就把它删除。这种模式下 记录操作的时间和统计对key-value操作次数(8位统计次数,16位记录时间)。 (3)allkeys-random,所有key中随机淘汰。

二、redis 持久化

redis持久化技术有两种:aof和rdb。

2.1、背景

(1)redis是内存数据库,不可能让redis一直运行下去,可能需要将redis重启或者部署到其他机器。一旦redis关闭,内存的所有数据都会丢失,所以需要将内存的数据持久化到磁盘中,方便从下一次启动从磁盘中将数据加载到内存当中,恢复到原来状态。 程序宕机也需要通过持久化将数据恢复回来。

(2)redis有四种持久化技术,其中有三种需要fork进程。这就涉及到内核fork进程写时复制机制。进程是通过页表操作内存的,fork复制的是页表而不是物理内存,它和父进程指向相同的内存块。

图片
图片

fork的写时复制是这样的过程: fork主要目的是克隆出和父进程一模一样的子进程,包括内存的数据;最开始为了fork的效率,复制的只是页表(页表主要起到映射作用),然后页表会被设置为只读状态。当下一次要操作redis,往redis写入或修改内存数据时,此时发现页表是一个只读状态,触发缺页中断,缺页中断处理函数中将物理内存复制一份,然后子进程页表的页会指向新的复制内存块;缺页中断之后,会把对应页表设置为可读可写状态。这就是整个写时复制的原理。

图片
图片

(3)大key;大key是指key-value中value占用大量内存。比如value是hash或者zset,里面存储大量的元素。

2.2、aof 和 aof-rewrite

aof的全称是append of file,采用追加文件的方式进行持久化。顺序写(也就是追加)的方式操作磁盘的速度最快。aof存储的是命令协议,也就是增删改操作附加到aof上;如果redis重启了,要恢复数据就通过读取aof文件,把命令协议读到内存中进行重放(replay),也就是重新执行命令,达到恢复数据的目的。 aof有三种策略:always、every_sec、no。这三种策略主要差异是fsync()的调用时机。 (1)always:write()之后立即调用fsync()将数据刷到磁盘文件中。 (2)every_sec:每秒去调用fsync()将数据刷到磁盘文件中;在bio_aof_fsync线程中执行。 (3)no:不自己调用fsync(),由系统决定什么时候调用fsync()将数据刷到磁盘文件中。

always方式的数据稳定性是最高的,确保每个写命令都能落盘;但是,它的代价也是最高的,因为它是在主线程进行的。every_sec代价是比较低的,因为它在bio_aof_fsync线程中执行的。no方式丢失数据的可能性是最高的。redis一般使用aof的every_sec方式,这个方式可能会丢1s~2s的数据风险。

aof的缺点: aof会把所有写命令都写到文件中,只会包含一些冗余数据(比如一个key设置了多次value,其实真正需要的是最后一次的命令),aof会使文件非常大以及数据恢复非常的满。 举例:

代码语言:javascript
代码运行次数:0
复制
127.0.0.1:6379> set fly 100
(integer) 1
127.0.0.1:6379> set fly 101
(integer) 1
127.0.0.1:6379> set fly 102
(integer) 1

aof会把三个命令到保存到文件中,其实真正需要的是最后一次的命令;恢复数据是也是执行了三个命令,其实真正有效的是最后一次的命令。

aof-rewrite: aof文件过大,aof数据恢复速度过慢,为解决aof的缺点,只记录最后一个状态,因此有了aof-rewrite的优化方案。 aof-rewrite通过fork进程,根据内存数据生成命令协议写到aof文件,避免同一个key历史冗余数据,就可以将aof文件降到小一些;在重写aof期间,对redis的写操作会记录到重写缓冲区,当重写aof结束后,子进程通过信号告诉父进程,再把重写缓冲区附加到aof文件。

图片
图片

这种方式虽然有了优化,但是效率还是较低,因为数据恢复还是通过重放(replay)方式,即重新执行命令的方式,需要消耗CPU,需要走命令处理流程。

2.3、rdb和rdb-aof

快照的持久化方式;直接拿内存数据直接持久化到磁盘中。直接将对象编码(ziplist、quiklist、intset、skiplist、int、embstr、raw等)落盘;它也是通过fork一个子进程,根据对象编码(二进制数据)直接落盘。由于是二进制数据,不管是落盘速度还是恢复数据速度都是最快的。 值得注意的是,skiplist是多层级链表,恢复数据时只会拿最底层进行填充,然后通过少量的CPU运算恢复内存中的数据。

redis默认的持久化方式是rdb。rdb持久化过程中,通常是5分钟一次。 rdb的持久化方式是以内存为单位,而aof持久化是以单条命令为单位,所以如果主机宕机,rdb方式丢失数据是最严重的。因此,选择持久化方式时,需要在可靠性和效率直接做平衡。

rdb-aof: 考虑到在rdb过程中,redis还会处理命令,因此,在rdb持久化期间,对redis的写操作会记录到重写缓冲区,当rdb持久化结束后,再将重写缓冲区的aof附加到rdb文件末尾。

图片
图片

这可以弥补rdb的不足。

三、redis 持久化方案的优缺点

(1)aof。 优点:数据可靠,数据丢失较少;持久化过程中代价较低。 缺点:文件过大,数据恢复慢。 (2)rdb。 优点:rdb文件小,数据恢复快。 缺点:数据丢失较多,持久化过程代价较高(因为是根据内存数据全部持久化,占用CPU运算)。

四、大key对持久化的影响

(1)fsync()压力大。比如aof的always,它是在主线程中做的持久化,如果value非常的大,会长时间占用主线程,这就是一个耗时的操作,会影响redis的响应性能;大key对aof的every_sec的影响较小,因为它在另外的线程进行持久化的;aof的no对redis的影响也较小,因为fsync()由系统决定,因此压力在系统上。

(2)fork时间比较长。redis中有一个object_info,会记录fork的时间,如果主线程fork超过1s,那么它的效率是非常低的,阻塞住的主线程。同时,写时复制造成持久化时间过长。

五、总结

(1)fork进程写时复制机制。

(2)redis的四种持久化方式。

图片
图片

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • “好事”发生
  • 一、redis 淘汰策略
    • 1.1、背景
    • 1.2、过期key
    • 1.3、所有key
  • 二、redis 持久化
    • 2.1、背景
    • 2.2、aof 和 aof-rewrite
    • 2.3、rdb和rdb-aof
  • 三、redis 持久化方案的优缺点
  • 四、大key对持久化的影响
  • 五、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档