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

一、redis 淘汰策略

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

1.1、背景

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

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

// 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的空转时长(单位是秒)。

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用于制定淘汰策略。

# 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还有多长时间到期。
示例:

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写入或修改内存数据时,此时发现页表是一个只读状态,触发缺页中断,缺页中断处理函数中将物理内存复制一份,然后子进程页表的页会指向新的复制内存块;缺页中断之后,会把对应页表设置为可读可写状态。这就是整个写时复制的原理。

fork
主进程
页表
内存块
子进程
页表2
缺页中断
fork
主进程
页表
内存块
新的内存块
子进程
页表2

(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会使文件非常大以及数据恢复非常的满。
举例:

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文件。

追加
fork
根据内存数据
写入
主进程
页表
内存块
重写缓冲区
aof文件
子进程
页表2
内存块
生成命令协议

这种方式虽然有了优化,但是效率还是较低,因为数据恢复还是通过重放(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-aof文件
rdb的二进制数据
aof的命令协议数据

这可以弥补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,那么它的效率是非常低的,阻塞住的主线程。同时,写时复制造成持久化时间过长。

总结

fork进程写时复制机制。

fork进程写时复制机制
避免物理内存的复制时间过长导致父进程长时间阻塞
fork页表复制,共用同一块物理内存
在发生写操作时,系统才会去复制物理内存
父进程对数据修改时,触发缺页中断,从而进行物理内存的复制
子进程的页表将指向新的物理内存
父进程的页表指向原来的物理内存

redis的四种持久化方式。

持久化技术
aof
直接刷盘,根据不同调用fsync的时机分为always、every_sec、no
aof-rewrite
解决aof历史冗余,fork进程方式根据内存数据生成命令协议
rdb
因为重放的方式恢复数据效率较低,rdb直接根据对象编码将二进制数据落盘,恢复数据的速度非常的快
rdb-aof
根据aof-rewrite原理,将rdb持久化期间的写操作数据追加到rdb持久化文件的末尾

在这里插入图片描述

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lion 莱恩呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值