• Redis持久化


    前言

      Redis是一个内存数据库,数据保存在内存中,但是内存数据库变化是非常快的,也容易发生数据丢失。而Redis为我们提供了两种持久化机制:RDB(Redis DataBases)和AOF(Append Only File)。

    一、持久化流程

    Redis持久化需要下面5个过程:

      1. 客户端向服务器端发送写操作(数据在客户端的内存中);

      2. 数据库服务器端接收到写请求的数据(数据在服务端内存中);

      3. 服务端调用write这个系统调用,将数据往磁盘上写(数据在系统内存缓冲区中);

      4. 操作系统将缓冲区中的数据转移到磁盘控制器上(数据在磁盘缓存中);

      5. 磁盘控制器将数据写入到磁盘的物理介质中(数据真正落到磁盘中)。

    这5个过程是在理想条件下一个正常的保存流程,但是在大多数情况下,我们的机器等等都会有各种各样的故障,这里划分了两种情况:

      1. Redis数据库发生故障,只要在上面的第三步执行完毕,那么就可以持久化保存,剩下的两步由操作系统帮我们完成;

      2. 操作系统故障,必须上面五步全部完成才可以持久化保存。

    这里只考虑了保存过程可能发生的故障,其实保存的数据也有可能发生损坏,需要一定的恢复机制。这里主要考虑redis来实现上面5个保存磁盘的步骤。

    二、RDB(Redis DataBases)机制

    RDB持久化方式是通过快照(snapshotting)完成的,当符合一定条件时,redis会自动将内存中的数据以二进制方式生成一份副本保存在磁盘上。当redis重启时,并且AOF持久化未开启时,redis会读取RDB持久化生成的二进制文件(默认文件名dump.rdb,可以通过设置dbfilename项进行修改)进行恢复数据,对于持久化信息可以通过命令"info persistence"查看。

    127.0.0.1:6379> info persistence # 查看持久化信息
    # Persistence
    loading:0
    rdb_changes_since_last_save:0
    rdb_bgsave_in_progress:0
    rdb_last_save_time:1609380861 #最近一次save触发持久化时间
    rdb_last_bgsave_status:ok
    rdb_last_bgsave_time_sec:-1
    rdb_current_bgsave_time_sec:-1
    rdb_last_cow_size:0
    aof_enabled:0
    aof_rewrite_in_progress:0
    aof_rewrite_scheduled:0
    aof_last_rewrite_time_sec:-1
    aof_current_rewrite_time_sec:-1
    aof_last_bgrewrite_status:ok
    aof_last_write_status:ok
    aof_last_cow_size:0
    module_fork_in_progress:0
    module_fork_last_cow_size:0
    127.0.0.1:6379> config get dbfilename # 查看RDB持久化数据保存的文件名,可以通过dbfilename配置项进行修改
    1) "dbfilename"
    2) "dump.rdb"
    127.0.0.1:6379> config get dir # 查看RDB持久化数据文件保存的目录,可以通过dir配置项进行修改
    1) "dir"
    2) "/usr/local/bin/redis_dump"  

    快照的触发条件

    RDB生成快照可自动触发,也可以使用命令手动触发,以下是redis触发执行快照的条件:

      1. 客户端执行save和bgsave命令会生成快照

      2. 根据配置文件中的save m n 规则进行自动快照

      3. 主从复制时,从库全量复制同步主库数据,此时主库会执行bgsave命令进行快照

      4. 客户端执行清空全部数据库flushall命令时,触发快照

      5. 客户端执行shutdown关闭redis时,触发快照

    快照触发方式

    1、save命令触发

      客户端执行save命令时,该命令强制redis执行快照,这时redis处于阻塞状态,不会响应任何其他客户端发来的请求,知道RDB快照文件执行完成,所以请慎用。来看下具体流程:

     执行完成时候如果存在旧的RDB文件,新生成的RDB文件将替换掉旧的RDB文件。

    2、bgsave命令触发

    bgsave命令可以理解为background save即:后台保存。当执行bgsave命令时,redis会fork一个子进程来执行快照生成操作,需要注意的是redis在fork子进程的这个简短时间内是阻塞的(此段时间内redis不会响应客户端的请求,但是这个过程时间一般很短),当子进程创建完成以后redis响应客户端的请求。其实redis自动生成快照也是会用bgsave来完成的。来看下具体流程:

     上述过程的描述:

      1. 客户端执行bgsave命令,redis主进程接收到指令并判断此时是否存在正在执行的子进程(如bgrewriteaof等子进程)如果存在则bgsave直接返回,不fork子进程,如果不存在则进入下一个阶段;

      2. redis主进程调用fork方法创建子进程,在创建子进程过程中redis主进程阻塞,所以不能响应客户端请求;

      3. 子进程创建完成以后,bgsave命令返回 "Background save started",此时标志着redis可以响应客户端请求了;

      4. 子进程根据主进程的内存副本创建临时快照文件,当快照文件完成以后对原快照进行替换;

      5. 子进程发送信号给redis主进程完成快照操作,主进程更新统计信息(可以通过info persistence进行查看),子进程退出。

    3. save m n规则触发(自动触发)

    save m n规则:在指定的m秒内,redis有n个key发生了改变,则自动触发bgsave。可以在配置文件的SNAPSHOTTING下进行规则配置:

     

     4. flushall触发

    flushall命令用于清空所有数据库,请慎用,当我们使用了此命令表名我们需要对所有数据库的数据进行清空,那么redis当然需要对快照文件也进行清空,所以会触发bgsave。

    5. shutdown 触发

    shutdown命令用于关闭redis服务器,redis在关闭前出于安全角度将所有数据保存下来,以便下次启动时恢复。

    6. 主从触发

    在redis主从复制中,从节点执行全量复制操作,主节点会执行bgsave命令,并将rdb文件发送给从节点。

    save和bgsave的对比

    命令 save bgsave
    IO类型 同步 异步
    阻塞? 是(阻塞发生在fork)
    复杂度 O(n) O(n)
    优点 不会消耗额外内存 不阻塞客户端命令
    缺点 阻塞客户端命令 需要fork,消耗内存

    RDB持久化的优缺点

    优点:

      1. RDB是一个紧凑压缩的二进制文件,代表redis在某个时间点上的数据快照。非常适用于备份,全量复制和灾难恢复等场景;

      2. 生成RDB文件的时候,redis会fork一个子进程来处理所有的保存工作,主进程不需要进行任何磁盘IO操作;

      3. RDB在恢复大数据集时的速度比AOF的恢复速度快。

    缺点:

      1. RDB方式数据没法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本过高。

      2. RDB文件使用特定二进制格式保存,Redis版本演变过程中有多个格式的RDB版本,存在老版本redis服务无法兼容新版RDB格式的问题。

      3. RDB快照是次全量备份,存储的是内存数据的二进制序列化形式,存储上非常紧凑。当进行快照持久化时,会fork一个子进程专门负责快照持久化,子进程会拥有父进程的内存数据,父进程修改内存后,子进程不会立即反应出来,所以在快照持久化期间修改的数据不会被保存,可能丢失数据。

    RDB持久化配置

    save m n
    # 配置快照(rdb)触发规则,格式: save <seconds> <changes>
    # 如:
    # save 900 1 表示900秒内至少有一个key被改变则触发快照
    # save 300 10 表示300秒内至少有10个key被改变则触发快照
    # save 60 10000 表示60秒内至少有10000个key被改变则触发快照
    # 关闭快照规则可以使用: save ""
    
    dbfilename dump.rdb
    # rdb持久化存储数据库文件名,默认为dump.rdb
    
    stop-writes-on-bgsave-error yes
    # 使用bgsave命令持久化出错时是否停止写RDB快照文件
    
    rdbcompression yes
    # 是否开启RDB文件压缩,该功能可以节约磁盘空间
    
    rdbchecksum yes
    # 在写入和读取RDB文件时是否开启RDB文件检查,检查是否有损坏
    # 如果在启动时检查发现损坏,则停止启动
    
    dir /usr/local/bin/redis_dump/
    # 数据文件存放目录,rdb快照文件和aof文件都会存放至该目录,请确保有写权限

    三、AOF机制

     当redis存储非临时数据时,为了降低redis故障而引起的数据丢失,redis提供了AOF(Append Only File)持久化,即将命令追加到文件。AOF可以将redis执行的每一条命令追加到磁盘文件(appendonly,aof)中,在redis启动时优先选择从AOF文件恢复数据。由于每一次的写操作,redis都会记录到文件中,所以开启AOF会对性能有一定的影响,但是大部分情况下这个影响是可以接受的,我们可以使用读写读写速率高的硬盘来提高AOF的性能。与RDB持久化相比,AOF持久化数据丢失更少,其消耗内存更少(RDB执行bgsave会有内存拷贝)。

    通俗的理解AOF机制就是日志记录。

    1. 开启AOF机制

    默认情况下,AOF机制是关闭的,开启AOF持久化可以将配置项appendonly修改为yes进行开启,我们可以直接修改配置文件或者在客户端使用 ‘config set ’命令进行修改,然后使用 'config  rewrite' 命令同步到配置文件中。通过客户端修改的好处就是不需要重启redis服务,AOF持久化直接生效。

    127.0.0.1:6379> config get appendonly
    1) "appendonly"
    2) "no"
    127.0.0.1:6379> config set appendonly yes
    OK
    127.0.0.1:6379> config get appendonly
    1) "appendonly"
    2) "yes"
    127.0.0.1:6379> config rewrite
    OK
    

      

     2. AOF持久化过程

    Redis AOF持久化过程可分为以下3个阶段:

    1)追加写入

      Redis将每条写命令以redis通讯协议添加到缓冲区aof_buf,这样的好处在于大量写请求的情况下,采用缓冲区暂存一部分命令随后根据策略一次性写入磁盘,这样可以减少磁盘的I/O次数,提高性能。

    2)同步命令到硬盘

      当写命令写入aof_buf缓冲区后,redis会将缓冲区的写命令写入到文件,redis提供了三种同步策略,由配置参数appendfsync决定:

    • no:不使用fsync方法同步,而是交给操作系统write函数去执行同步操作,在Linux操作系统中大约每30秒刷新一次缓冲。这种情况下,缓冲区数据同步不可控,并且在大量的写操作下,aof_buf缓冲区堆积会越来越严重,一旦redis出现故障,数据丢失严重。
    • always:表示每次有写操作都会调用fsync方法强制内核将数据写入到aof文件。这种情况下由于每次写命令都写入到了文件中,虽然数据比较安全,但是因为每次写操作都会同步到AOF文件,所以在性能上会有影响,同时由于频繁的IO操作,硬盘的使用寿命会降低。
    • everysec:数据将使用操作系统write函数写入文件,并使用fsync每秒一次从内核刷新到磁盘。这是折中的方案,兼顾性能和数据安全,所以redis默认使用该配置。

    3)文件重写(bgrewriteaof)

      当开启AOF时,随着时间的推移,AOF文件会越来越大,当然redis也对AOF文件进行了优化,即触发AOF文件重写条件的时候,redis将使用bgrewriteaof对AOF文件进行重写。这样的好处在于减少aof文件的大小,同时有利于数据的恢复。

      为什么需要重写?

      当先后执行了 "set k1 a set k1 b set k1 c"命令时,此时AOF文件将会记录三条命令,这显然是不合理的,因为文件中应该只保留 " set k1 c "这个最后一条执行的命令,前面的都是多余的。

      aof文件重写策略:

    • 重复或无效的命令不写入文件
    • 过期的数据不再写入文件
    • 多条命令合并写入(当多条命令能够合并成一条命令的时候会将其优化合并成一条命令写入文件,如:"rpush list1 a rpush list1 b" 合并为 "rpush list a b"一条命令写入文件)

    3. 重写触发的条件

    aof文件触发条件可分为手动触发和自动触发:

      1)手动触发:客户端执行bgrewriteaof命令。

      2)自动触发:自动触发通过以下两个配置协作生效:

      • auto-aof-rewrite-min-size:AOF文件最小重写大小,只有当AOF文件大小大于该值时才可能重写,默认配置64mb
      • auto-aof-rewrite-percentage:当AOF文件大小和最后一次重写后的大小之间的比率等于或大于指定的增长的百分比,如100表示当前AOF文件的大小是上次重写的两倍的时候才重写。

    redis在开启aof功能的情况下,会维持一下三个变量:

    • aof_current_size:记录当前aof文件的大小
    • aof_rewrite_base_size:记录最后一次AOF重写之后,aof文件的大小
    • aof_rewrite_perc:增长百分比

    每次当serverCron(服务器周期性操作函数)函数执行时,它会检查一下条件是否全部满足,如果满足就触发自动的aof重写操作:

    • 没有bgsave命令(RDN持久化)/ AOF持久化在执行;
    • 没有bgrewriteaof在执行;
    • 当前AOF文件大小要大于server.aof_rewrite_min_size的值;
    • 当前AOF文件大小和最后一次重写后的大小之间的比率等于或大于指定增长百分比(auto-aof-rewrite-percentage参数)

    4. 重写过程

       AOF文件重写过程与RDB快照bgsave工作过程有点相似,都是通过fork子进程,由子进程完成相应的操作,同样在fork子进程的简短时间内,redis是阻塞的,重写过程如下图:

     

     过程说明:

      aof_rewrite_buf代表重写缓冲区,aof_buf代表写命令缓冲区;

      1) 开始rbgewriteaof,判断当前有没有bgsave命令(RDB持久化)/bgrewriteaof在执行,若有,则在这些命令执行完成后再执行;

      2)主进程fork出子进程,在这一个短暂的时间内,redis是阻塞的;

      3)主进程fork完子进程后继续接收客户端请求,所有写明了依然写入AOF文件缓冲区并根据appendfsync策略同步到磁盘,保证原有AOF文件完整和正确。由于fork出的子进程仅仅只共享主进程fork时的内存,因此redis采用重写缓冲区(aof_rewrite_aof)机制保存fork之后的客户端的写入请求,防止新AOF文件生成期间丢失这部分数据。因此,客户端的写入请求不仅仅是写入原来的aof_buf缓冲,还写入重写缓冲区(aof_rewrite_buf);

      4)子进程通过内存快照,按照命令重写策略写入到新的AOF文件;

      4.1)子进程完成新的AOF文件后,向主进程发信号,父进程更新统计信息;

      4.2)主进程把aof_rewrite_buf中的数据写入到新的AOF文件(避免写文件时数据丢失);

      5)使用新的AOF文件覆盖旧的AOF文件,标志AOF重写完成。

    AOF实现的本质

      AOF实现的本质是基于redis通讯协议,将命令以纯文本的方式写入到文件。

      redis协议:

      首先redis是以行来划分,每行以 结束。每一行都有一个消息头,消息头工分为5种,分别如下:

      (+):表示一个正确的状态信息,具体信息是当前行+后面的字符;

      (-):表示一个错误的信息,具体信息是当前行-后面的字符;

      (*):表示消息体总共有多少行,不包括当前行,*后面是具体的行数;

      ($):表示下一行数据的长度,不包括换行符号 ,$后面则是对应长度的数据。

      (:):表示返回一个数值,:后面是相应的数字字节。

    查看appendonly.aof文件如下:

    [root@localhost configs]# tail -f appendonly.aof 
    SELECT
    $1
    0
    *3
    $3
    set
    $2
    k2
    $2
    v2
    *3
    $3
    set
    $2
    k3
    $2
    v3  

    数据恢复

      AOF开启时,redis数据恢复优先用AOF文件进行数据恢复,使用启停redis服务来模拟redis故障

    127.0.0.1:6379> shutdown
    not connected> exit
    [root@localhost bin]# redis-server configs/redis.conf 
    [root@localhost bin]# redis-cli 
    127.0.0.1:6379> keys *
    1) "k2"
    2) "k3"
    3) "k1"
    

      查看redis日志:

    3763:M 03 Jan 2021 19:44:47.496 * Reading RDB preamble from AOF file...
    3763:M 03 Jan 2021 19:44:47.496 * Loading RDB produced by version 6.0.9
    3763:M 03 Jan 2021 19:44:47.496 * RDB age 615 seconds
    3763:M 03 Jan 2021 19:44:47.496 * RDB memory usage when created 0.88 Mb
    3763:M 03 Jan 2021 19:44:47.496 * RDB has an AOF tail
    3763:M 03 Jan 2021 19:44:47.496 * Reading the remaining AOF tail...
    3763:M 03 Jan 2021 19:44:47.496 * DB loaded from append only file: 0.000 seconds #数据库加载AOF文件恢复数据
    3763:M 03 Jan 2021 19:44:47.496 * Ready to accept connections  

    AOF配置参数

    appendonly no
    # yes开启AOF,no关闭AOF
    
    appendfilename "appendonly.aof"
    # 指定AOF文件名,4.0之后无法通过config set设置,只能通过修改配置文件设置
    
    appendfsync everysec
    #no:不使用fsync方法同步,而是交给操作系统write函数去执行同步操作,在linux操作系统中大约每30秒刷一次缓冲。这种情况下,缓冲区数据同步不可控,并且在大量的写操作下,aof_buf缓冲区会堆积会越来越严重,一旦redis出现故障,数据将丢失严重;
    #always:表示每次有写操作都调用fsync方法强制内核将数据写入到aof文件。这种情况下由于每次写命令都写到了文件中, 虽然数据比较安全,但是因为每次写操作都会同步到AOF文件中,所以在性能上会有影响,同时由于频繁的IO操作,硬盘的使用寿命会降低。
    #everysec:数据将使用调用操作系统write写入文件,并使用fsync每秒一次从内核刷新到磁盘。 这是折中的方案,兼顾性能和数据安全,所以redis默认推荐使用该配置。
    
    auto-aof-rewrite-percentage 100
    #当前AOF文件大小和最后一次重写后的大小之间的比率等于或者等于指定的增长百分比,如100代表当前AOF文件是上次重写的两倍时候才重写。
    
    auto-aof-rewrite-min-size 64mb
    #AOF文件最小重写大小,只有当AOF文件大小大于该值时候才可能重写,4.0后默认配置64mb。
    
    aof-load-truncated yes
    #当redis突然运行崩溃时,会出现aof文件被截断的情况,Redis可以在发生这种情况时退出并加载错误,以下选项控制此行为。
    #如果aof-load-truncated设置为yes,则加载截断的AOF文件,Redis服务器启动发出日志以通知用户该事件。
    #如果该选项设置为no,则服务将中止并显示错误并停止启动。当该选项设置为no时,用户需要在重启之前使用“redis-check-aof”实用程序修复AOF文件在进行启动。 

    aof-use-rdb-preamble yes
    # 是否开启混合持久化,新版本默认开启

     四、RDB-AOF混合持久化

    混合持久化就是同时结合RDB持久化以及AOF持久化混合写入AOF文件。这样做的好处是可以结合RDB和aof的优点,快速加载数据同时避免丢失过多数据,缺点是aof里面的RDB部分就是压缩格式不再是aof格式,可读性差。

    开启混合持久化

    可以通过aof-use-rdb-preamble配置项控制,yes表示开启,no表示关闭,新版本中默认开启,可以通过config set设置。

    混合持久化过程

    混合持久化同时也是通过bgrewriteaof完成的,不同的是开启混合持久化时,fork出的子进程先将共享的内存副本全量的以RDB方式写入aof文件,然后再将重写缓冲区的增量命令以aof方式写入文件,写完后通知主进程更新统计信息,并将新的含有RDB格式和AOF格式的文件替换旧的AOF文件。简单的说就是:新的AOF文件前半段hiRDB格式的全量数据后半段是AOF格式的增量数据,如下图:

     

     数据恢复

    当我们开启了混合持久化时,启动redis仍然优先加载aof文件,aof文件加载可能有两种情况,如下:

    • aof文件开头是RDB格式,先加载RDB文件内容再加载剩余的aof;
    • aof开头不是RDB的格式,直接以aof格式加载整个文件。

    五、RDB-AOF-混合持久化的优缺点

    RDB

    优点:

      1. RDB是一个非常紧凑的文件,体积小,因此在传输速度上比较快,因此适合灾难恢复;

      2. RDB可以最大化redis的性能,父进程在保存RDB文件时唯一要做的就是fork出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘I/O操作;

      3. RDB在恢复大数据集的时候速度要比AOF恢复速度快。

    缺点:

      1. RDB是一个快照过程,无法完整的保存所有数据,尤其是在数据量比较大的时候,一旦出现故障丢失的数据将更多;

      2. 当redis数据集比较大时,由于RDB方式需要对数据完整拷贝并生成快照文件,fork的子进程会消耗CPU,并且数据量越大,RDB快照生成会越耗时;

      3. RDB是特定的格式,阅读性差,由于格式固定,版本迭代过程中可能存在不兼容的情况。

    AOF

    优点:

      1. 数据更完整,秒级数据丢失(取决于fsync策略设置);

      2. 兼容性较高,由于是基于redis通讯协议而形成的命令追加方式,无论何种版本的redis都兼容,再者aof是明文的,阅读性较好;

    缺点:

      1. 数据文件体积较大,即使有重写机制,但是在相同的数据集情况下,AOF文件通常比RDB文件大;、

      2. 相对RDB方式,AOF速度慢于RDB,并且在数据量大的时候,回复速度AOF速度也是慢于RDB的;

      3. 由于频繁的将命令同步到文件中,AOF持久化对性能的影响相对RDB较大,但是对于我们来说是可以接受的。

    混合持久化

    优点:

      混合持久化结合了RDB持久化和AOF持久化的优点,由于绝大部分是RDB格式,加载速度快,同时结合AOF,增量的数据以AOF方式保存了,数据更少的丢失。

    缺点:

      兼容性差,一旦开启了混合持久化,由于aof文件前半段是rdb格式,版本迭代中各版本可能存在兼容性问题,且阅读性比较差。

  • 相关阅读:
    Objects类源码详解(基于jdk1.8.0_261)
    Object类源码详解(基于jdk1.8.0_261)
    Collections工具类源码详解(基于jdk1.8.0_261)
    版本控制神器——git的基本使用
    数据结构练习1~插入排序~
    求指错啊急是求幂的
    编完了个代码大家给看看还能不能精简
    关于KMP算法
    有网友建议写一个新手指南或者FAQ一类的置顶你们认为呢
    c语言新手编程错误求解
  • 原文地址:https://www.cnblogs.com/huige185/p/14212982.html
Copyright © 2020-2023  润新知