Redis持久化

Redis 持久化

Redis 数据存储于内存中,重启后内存中的数据会丢失。可以将Redis的中的数据持久化到硬盘或者SSD上长期存储,Redis提供了一些持久化选项。

1.RDB

在指定的时间间隔,执行数据集的时间点(全量数据)快照,将快照数据写入磁盘也就是Snapshot内存快照,恢复时再将磁盘快照文件dump.rdb文件读到内存中。
RDB持久化

1.1RDB配置

    ###################### SNAPSHOTTING  ####################
    # 持久化配置
    # save 3600 1 300 100 60 10000

    # 默认yes,如果配置成no,表示你不在乎数据不一致或者有其他的手段发现和控制这种不一致,那# 么在快照写入失败时,也能确保redis继续接受新的写请求
    stop-writes-on-bgsave-error yes

    # 默认yes
    # 对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进
    # 行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能
    rdbcompression yes

    # 默认yes,在存储快照后,还可以让Redis使用CRC64算法进行数据校验,但是这样会增加大约10%的性能消耗,如果希望获得到最大的性能提升,可以关闭此功能。
    rdbchecksum yes

    # RDB文件存储路径
    dir ./

    # 文件名称
    dbfilename dump.rdb

    # 默认NO,在没有持久化的情况下删除复制中使用的RDB文件的启用
    rdb-del-sync-files no

1.2自动备份原理

1.2.1RDB触发场景

1.满足配置文件中的快照配置
2.手动save/bgsave
3.执行flushall/flushdb命令也会产生dump.rdb文件,但里面是空的,无意义
4.执行shutdown且没有设置开启AOF持久化
5.主从复制时,主节点自动触发
save
在主程序中执行会阻塞当前redis服务器,直到持久化工作完成执行save命令期间,Redis不能处理其它命令,生产环境禁止使用
bgsave(默认)
Redis在后台异步进行快照操作,不阻塞快照同时还可以响应客户端请求,该触发方式会fork一个子线程由子线程进程复制持久化过程
Redis会使用bgsave对当前内存中的所有数据做快照,这个操作是子进程在后台完成的,这就允许主进程同时可以修改数据
lastsave
可以通过lastsave命令获取最后一次成功执行快照的时间

    127.0.0.1:6379> lastsave
    1681959054

1.2.2savaparams属性

Redis会根据配置文件中设置的保存条件(或者未配置时的默认配置),设置服务器状态的redisServersaveparams属性

    struct redisServer{
        ...
        // 保存条件配置的数组
        struct saveparam *saveparams;
        ...
    }

saveparams是一个数组,数组中每个对象都是saveparam结构,saveparam结构如下所示,每个字段分别表征save选项的参数

    struct saveparam{
        // 秒数
        time_t seconds;
        // 修改次数
        int changes;
    }

已默认配置为例,redis中saveparams存储的数据结构将会如下所示
数据结构

1.2.3dirty计数器和lastsave属性

除了saveparams参数之外,redisServer还有dirtylastsave属性

    struct redisServer{
        ...
        // 修改次数的计数器
        long dirty;
        // 上一次成功执行RDB快照的时间
        time_t lastsave;
        // 保存条件配置的数组
        struct saveparam *saveparams;
        ...
    }

dirty属性保存距离上次成功执行RDB快照之后,Redis对数据进行了多少次修改操作(包括写入、更新、删除)
lastsave属性记录了Redis上一次成功执行RDB快照的时间,是一个UNIX时间戳
Redis没进行一次写命令都会对dirty计数器进行更新,批量操作按多次进行计数

    mset user zhangsan lisi wangwu

dirty计数器会增加3
数据结构
如上图,dirty计数器的值为101,表示Redis自上次成功进行RDB快照之后,对数据库一共进行了3次操作;lastsave属性记录了上次成功进行RDB快照的时间1681980894893(2023-04-20 16:54:54)

1.2.4周期性检查保存条件

serverCron函数默认每隔100ms会执行一次,该函数的其中一个作用就是检查save命令设置的保存条件是否满足,是则执行BGSAVE命令。伪代码如下

    void serverCron(){
        ...
        for (saveparam in server.saveparams){
            // 计算距离上次成功进行RDB快照多少时间
            save_interval = unixtime_now() - server.lastsave;

            // 如果距离上次快照时间超过条件设置时间 && 数据库修改次数超过条件所设置的次数,则执行快照操作
            if (save_interval > saveparam.seconds && server.dirty >= saveparam.changes){
                BGSAVE()
            }
        }
        ...
    }

举个🌰,假设Redis的状态如下
redisServer状态
当时间来到1681980895194(1681980894893 + 301),由于满足saveparams数组的第2个保存的条件--300s内至少进行了100次修改,Redis将会执行一次basave操作。
假设bgsave执行10S后完成,则此时Redis的状态会更新为:
BGSAVE后状态更新

1.3数据恢复

将备份文件(dump.rdb)移动到安装目录并启动服务即可
注意:
不可以把备份文件dump.rdb和生产redis服务器放在同一台机器,必须分开存储,以防生产物理损坏后备份文件也挂了

1.4RDB总结

优势
1.存储紧凑,节省内存空间
2.恢复速度非常快
3.适合全量备份、全量复制的场景,经常用于灾难恢复(对数据的完整性和一致性要求相对较低的场合)
劣势
1.容易丢失数据,容易丢失两次快照之间Redis服务器中变化的数据
2.RDB通过fork子进程对内存快照进行全量备份,是一个重量级操作,频繁执行成本高
3.fork子进程,虽然共享内存,但是如果备份时内存被修改(cow写时复制),最大可能膨胀到2倍大小

2AOF

以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以修改文件,Redis启动之初会读取该文件重新构建数据,换言之,Redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
默认情况下,Redis是没有开启AOF(append only file)的。开启AOF功能需要设置配置:

    # The working directory.
    # The DB will be written inside this directory, with the filename specified
    # above using the 'dbfilename' configuration directive.
    # The Append Only File will also be created inside this directory.
    # Note that you must specify a directory here, not a file name.
    dir ./
     #开启AOF持久化
     appendonly yes
     #AOF保存的文件名称
     appendfilename "appendonly.aof"
     #Redis7新版本增加的目录配置项目
     #最终路径 = dir + appendonlydir
     appenddirname "appendonlydir"
     # 三种写回策略默认每秒写回
     # appendfsync always
     appendfsync everysec
     # appendfsync no   

2.1AOF持久化流程


1.Client作为命令的来源,会有多个源头以及源源不断的请求命令
2.这些命令到达redis Server 以后并不是直接写入AOF文件,会将这些命令先放入AOF缓存中进行保存。这里的AOF缓冲区实际上是内存中的一片区域,存在的目的是当这些命令到达一定量以后再写入磁盘,避免频繁的磁盘I/O
3.AOF缓冲会根据AOF缓冲区同步文件的三种回写策略将命令写入磁盘上的AOF文件
4.随着写入AOF内容的增加为了避免文件膨胀,会根据规则进行命令的合并(又称AOF重写),从而起到AOF文件压缩的目的
5.当redis Server 服务器启动的时候会重新冲AOF文件载入数据

2.2AOF缓冲区三种写回策略

always
同步写回,每个写命令执行完立刻同步地将日志写回磁盘
everysec
每秒写回,每个命令执行完,只是先把日志写到AOF文件的内存缓冲区,每隔1秒把缓冲区中的内容写入磁盘
no
操作系统控制的写回,每个命令执行完,只是先把日志写到AOF文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘

2.3AOF文件

顾名思义,MP-AOF就是将原来的单个AOF文件拆分成多个AOF文件。在MP-AOF中,将AOF分为三种类型。分别为:
BASE:表示基础AOF,他一般由子进程通过重写产生,该文件最多只有一个
INCR:表示增量AOF,他一般会在AOFRW开始执行时被创建,该文件可能存在多个。
HISTORY:表示历史AOF,它由BASE和INCR AOF变化而来,每次AOFRW成功完成时,本次AOFRW之前对应的BASE很INCR AOF都将会变成HISTORY,HISTORY类型的AOF会被Redis自动删除。
为了管理这些AOF文件,我们引入了一个manifest(清单)文件来跟踪,管理这些AOF。同时,为了便于AOF备份和拷贝,我们将所有的AOF文件和manifest文件放入一个单独的文件目录中,目录名由appenddirname配置(Redis7新增配置)决定。

    # For example, if appendfilename is set to appendonly.aof, the following file 
    # names could be derived:
    # 基本文件
    # - appendonly.aof.1.base.rdb as a base file.
    # 增量文件
    # - appendonly.aof.1.incr.aof, appendonly.aof.2.incr.aof as incremental files.
    # 清单文件
    # - appendonly.aof.manifest as a manifest file.
    appendfilename "appendonly.aof"

2.3.1异常恢复

当AOF文件错误时,Redis将无法启动。此时可以通过异常修复命令进行修复

    redis-check-aof --fix 文件xxx 

2.4AOF重写机制

启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集。

    #默认配置
    #同时满足,且的关系才会触发
    # 根据上次重写后的aof大小,判断当前aof大小是否增长了100%
    auto-aof-rewrite-percentage 100
    # 重写是满足的文件大小
    auto-aof-rewrite-min-size 64mb

2.4.1自动触发

满足配置文件中的选项后,Redis会记录上次重写时的AOF大小,当AOF文件是上次rewrite后大小的一倍且文件大于64M时

2.4.2手动触发

客户端向服务器发送bgwriteaof命令

2.4.3重写原理

Redis AOF文件重写并不是对原文件进行重新整理,而是直接读取服务器现有的价值对,然后用一条命令去代替之前这个键值对的多个命令,生成一个新的文件后去替换原来的AOF文件

实现关键
由于写操作通常是有缓冲的,所以有可能 AOF 操作并没有写到硬盘中,一般可以通过 fsync()来强制输出到硬盘中。而 fsync()的频率可以通过配置文件中的 flush 策略来指定,可以选择每次事件循环写操作都强制 fsync 或者每秒 fsync 至少运行一次。
当 rewrite 子进程开始后,父进程接受到的命令会添加到 aof_rewrite_buf_blocks 中,使得 rewrite 成功后,将这些命令添加到新文件中。在 rewrite 过程中,原来的 AOF 也可以选择是不是继续添加,由于存在性能上的问题,在 rewrite 过程中,如果 fsync()继续执行,会导致 IO 性能受损影响 Redis 性能。所以一般情况下 rewrite 期间禁止 fsync()到旧 AOF 文件。这策略可以在配置文件中修改。
在 rewrite 结束后,在将新 rewrite 文件重命名为配置中指定的文件时,如果旧 AOF 存在,那么会 unlink 掉旧文件。这是就存在一个问题,处理 rewrite 文件迁移的是主线程,rename(oldpath, newpath)过程会覆盖旧文件,这是 rename 会 unlink(oldfd),而 unlink 操作会导致 block 主线程。这时,我们就需要类似 libeio(http://software.schmorp.de/pkg/libeio.html)这样的库去进行异步的底层 IO。作者在 bio.c 有一个类似的机制,通过创建新线程来进行异步操作。
异步重写的支持
Redis Server 收到 BGREWRITE 命令或者系统自动触发 AOF 重写时,主进程创建一个子进程并进行 AOF 重写,主进程异步等待子进程结束(信号量),此时主进程能正常接收处理用户请求,用户请求会修改数据库里数据,会使得当前数据库的数据跟重写后 AOF 里不一致,需要有种机制保证数据的一致性。当前的做法是在重写 AOF 期间系统会新开一块内存用于缓存重写期间收到的命令,在重写完成以后再将缓存中的数据追加到新的 AOF。
在处理命令时既要将命令追加到 aof_buf,也要追加到 aof_rewrite_buf_blocks。

2.5AOF总结

优点
1.数据的备份更加完整,丢失数据的概率更低,适合对数据完整性要求高的场景
2.日志文件可读,AOF可操作性更强,可通过操作日志文件进行修复
缺点
1.AOF日志记录在长期运行中逐渐庞大,恢复起来非常耗时,需要定期对AOF日志进行瘦身处理
2.恢复备份速度比较慢
3.同步写操作频繁会带来性能压力

3.RDB-AOF混合持久化

    # AOF 和 RDB 持久化可以同时开启,没有问题。如果 AOF 在启动时开启,Redis 将加载 AOF,即具有更好持久性保证的文件。
    # AOF and RDB persistence can be enabled at the same time without problems.
    # If the AOF is enabled on startup Redis will load the AOF, that is the file 
    # with the better durability guarantees.


推荐二者结合使用,既能快速加载又能避免丢失过多的数据。

    # Redis can create append-only base files in either RDB or AOF formats. Using
    # the RDB format is always faster and more efficient, and disabling it is only
    # supported for backward compatibility purposes.
    # 前置开启了AOF
    aof-use-rdb-preamble yes

混合持久化本质是通过 AOF 后台重写(bgrewriteaof 命令)完成的,不同的是当开启混合持久化时,fork 出的子进程先将当前全量数据以 RDB 方式写入新的 AOF 文件,然后再将 AOF 重写缓冲区(aof_rewrite_buf_blocks)的增量命令以 AOF 方式写入到文件,写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件

4.纯缓存模式

同时关闭RDB和AOF

    # 禁用RDB(但是仍让可以通过save、bgsave生成rdb文件 ) 
    save "" 
    # 禁用AOF(但是仍让可以通过 bgrewriteaof 生成rdb文件 )
    appendonly no
posted @ 2023-04-20 11:29  古拉加斯·浩二  阅读(75)  评论(0)    收藏  举报