Redis持久化

作者: HRADPX | 来源:发表于2019-07-05 11:16 被阅读0次

    Redis持久化方式有两种:RDB和AOF。

    1 RDB持久化

      RDB(Redis Database)持久化是把当前进程数据生成快照保存在RDB文件的过程。触发RDB持久化过程分为手动触发和自动触发。RDB持久化生成的RDB文件是一个经过压缩的二进制文件。

    (1)手动触发

       SAVEBGSAVE命令

    SAVE:会阻塞Redis服务器进程,直到RDB文件创建完毕,在服务器进程阻塞期间,不能处理任何命令。
    BGSAVE:Redis进程会fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束,阻塞只发生在fork阶段。

      BGSAVE运作流程:

    1. 执行BGSAVE命令,Redis父进程判断当前是否有正在执行的子进程,如RDB/AOF子进程,如果存在直接返回。

    2. 父进程执行fork操作创建子进程,fork操作会阻塞父进程。父进程fork操作执行完成后,便不再阻塞父进程,可以继续响应其他命令。

    3. 子进程创建RDB文件,根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换。

    4. 子进程发送信号给父进程表示完成,父进程更新统计信息。

    (2) 自动触发

       用户通过SAVE选项设置多个保存条件,让服务器每隔一段时间自动执行一个BGSAVE命令,只要有一个条件被满足,服务器就会执行BGSAVE命令。

      1) 设置保存条件、dirty计数器和lastsave属性

      当Redis服务器启动时,用户可以通过制定配置文件或者传入启动参数的方式设置SAVE选项,如果用户没有设置SAVE选项,那么服务器会为SAVE设置默认条件:

    SAVE 900 1 // 服务器在900秒之内,对数据库进行了至少1次修改。

    SAVE 300 10 // 服务器在300秒之内,对数据库进行了至少10次修改。

    SAVE 60 10000 // 服务器在60秒之内,对数据库进行了至少10000次修改。

      接着,设置服务器状态redisServer结构,除了保存条件的数组外,服务器状态还维持了一个dirty计数器,以及一个lastsave属性。

      dirty计数器:记录距离上一次成功执行SAVEBGSAVE命令之后,服务器对数据库状态进行了多少次修改(包括写入、删除和更新等操作)。

    lastsave:是一个UNIX时间戳,记录了服务器上一次成功执行SAVEBGSAVE命令的时间。

     struct redisServer{
     
       //…
     
      // 记录保存条件的数组
     
      struct saveparam *saveparams;
     
     // 修改计数器
     
       long long dirty;
     
     //上一次执行的保存时间
     
       time_t lastsave;
     
       //…
     
     }
     
     struct saveparam{
     
       //秒数
    
       time_t seconds;
     
       // 修改数
     
       int changes;
     
     }
    

       2) 检查保存条件是否满足

      Redis服务器周期性操作函数saverCron默认每隔100毫秒就会执行一次,该函数用于对正在运行的服务器进行维护,其中一个工作就是检查SAVE条件是否满足,如果满足则调用BGSAVE命令。

      saverCron函数检查保存条件的过程:

    (1) 遍历所有的保存条件。

    (2) 计算距离上次执行保存操作有多少秒.。

    (3) 如果数据库状态的修改次数超过条件所设置的次数并且距离上次保存的时间超过了条件所设置的时间,就会执行保存操作。

    (3) RDB文件的载入

      如果服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件来还原数据库状态,因为AOF文件的更新频率比RDB文件的更新频率高。

      只有在AOF持久化关闭的状态时,服务器才会使用RDB文件来还原数据库状态。

      服务器在载入RDB文件期间,会一直处于阻塞状态,直到载入工作完成为止。

    (4) RDB的优缺点

      优点:RDB文件时一个紧凑的二进制文件,适用备份、全量复制的场景。加载RDB文件恢复数据远远快于AOF文件。

       缺点:不能实时持久化,数据丢失的风险高。每次BGSAVE创建子进程属于重量级操作,频繁执行成本高。

    2 AOF持久化

    AOF(Append Only File)持久化以独立日志方式记录每次写命令,重启时在重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用就是解决了数据持久化的实时性。

    被写入AOF文件的所有命令都是以Redis的命令请求协议格式保存的,因为Redis命令请求协议格式是纯文本格式,所以我们可以直接打开一个AOF文件,观察里面的内容。

    (1) 使用AOF

       默认情况下AOF功能是关闭的。配置文件appendonly no改为appendonly yes。
    

    (2) AOF工作流程

       命令追加(append)、文件同步(sync)、文件重写(rewrite)、重启加载。
    

    1 命令追加:服务器执行完一个写命令后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区末尾。

    2 文件同步:AOF缓冲区根据对应的策略向硬盘做同步操作。

    3 文件重写:随着AOF越来越大,需要定期对AOF文件进行重写,达到压缩的目的。

    4 重启加载:当Redis重启时,可以加载AOF文件进行数据恢复。

    AOF缓冲区同步文件策略有三种:
    
    可配置值 说明
    appendfsync_always 命令写入aof_buf后调用系统的fsync操作同步到AOF文件中,fsync完成后线程返回。
    appendfsync_everysec 命令写入aof_buf中调用系统的write操作,writer完成后线程返回。fsync同步文件操作由专门下线程每秒调用一次。
    appendfsync_no 命令写入aof_buf后调用系统的write操作,不对AOF文件做fsync同步,同步硬盘由操作系统负责。
    1. 配置always时,每次写入都要同步AOF文件,从效率上看,是最慢的,从安全性上看,是最安全的。
    2. 配置为everysec时,服务器每次写入aof_buf缓冲区的所有内容都写入AOF文件中,并且每隔1秒就在子线程中对AOF文件进行同步。从效率上看,everysec模式足够快,并且就算出现故障停机,数据库也只会丢失1秒的数据。该模式也是默认的同步策略。
    3. 配置为no时,服务器每次写入缓冲区的所有内容都写入AOF文件,但是何时同步由操作系统控制,该模式的写入速度最快,但是安全性无法保证。

    (3) AOF文件的载入与数据还原

    AOF文件的载入与数据还原

      Redis读取AOF文件并还原数据库状态的流程:

    1. 创建一个不带网络连接的伪客户端。因为Redis的命令只能在客户端上下文中执行, 而载入AOF文件时所使用的命令直接来源于AOF文件而不是网络连接,所以服务器使用了一个没有网络连接的伪客户伪客户端执行命令的效果和带网络连接的客户端执行命令的效果完全一样。

    2. 从AOF文件中分析并读取一条命令。

    3. 使用伪客户端执行被读出的命令。

    4. 重复2、3步骤,直到AOF文件中所有命令都被处理完毕。

       (4)重写机制

      为什么要重写:由于AOF是通过命令追加的方式写入AOF文件中,这会导致AOF文件越来越大。如果不加以控制,体积过大的AOF文件很可能对Redis服务器,甚至整个宿主计算机造成影响。并且AOF文件越来越大,使用AOF文件进行数据还原需要的时间就越多。AOF重写机制是为了解决AOF文件越来越大的问题。Redis通过重写功能,Redis服务器可以创建一个新的AOF文件来替代现有的AOF文件,新旧两个AOF文件所保存的数据库状态相同。但是新的AOF文件体积更小。

      1) AOF重写的实现

      AOF重写并不是对现有的AOF文件进行任何的读取、分析或写入操作,而是通过读取服务器当前的数据库状态来实现的,即是把Redis进程内的数据转化为写命令同步到新AOF文件的过程。

      重写的AOF文件体积变小的原因:

    1.进程内已经超时的数据不再写入文件。对于那些已经过期的数据,AOF重写时不会再将他们写入文件。

    2.旧的AOF文件包含无效的命令。如set a 111、set a 222,数据库中的数据为a 222,所以重写时,只需写入set a 222即可。

    3.多条写命令可以合并为一个。如lpush list a、lpush list b、lpush list c,此时数据库中list中的值为a b c。可以使用lpush list a b c一条命令。同时为了防止单条命令过大造成客户端缓存溢出,对于list、set、hash、zset等类型操作,以64个元素为界拆分为多个。

      2) AOF重写的流程:

    1.父进程执行fork操作创建子进程。

    2.主线程fork操作完成后,可以继续响应其他命令,所有修改依然写入AOF缓冲区并根据appendfsync策略同步到硬盘,保证原有AOF机制正确性。

    3.由于fork操作运用写时复制(copy-on-write)技术,子进程只能共享fork操作时内存数据,可以在避免使用锁的情况下,保证数据的安全性。

    4.由于父进程依然在响应命令,为了防止在重写过程中数据的丢失,Redis使用了AOF重写缓冲区,这个缓冲区在服务器创建子线程之后开始使用,当Redis服务器执行完一个写命令后,它会同时将这个命令发送给AOF缓冲区和AOF重写缓冲区。

    5.当子进程完成AOF重写工作之后,它会向父进程发送一个信号,父进程在接到该信号之后,会调用一个信号处理函数,并执行以下工作:

        将AOF重写缓冲区中的所有内容写入到新的AOF文件中,这时AOF所保存的数据库状态将和当前的数据库状态一致。

        对新的AOF文件进行改名,原子地(atomic)覆盖现有的AOF文件,完成新旧两个AOF文件的替换。

        这个信号处理函数执行完毕后,父线程可以继续接受命令请求了。

      在整个AOF后台重写过程中,只有信号处理函数执行时会对服务器进程(父进程)造成阻塞,在其他时候不会阻塞父线程,这将AOF重写对服务器性能造成的影响降到了最低。

    (5) 重启加载

      AOF和RDB文件都可以用于服务器重启时恢复数据。如果开启了AOF持久化会选择加载AOF文件,只有在AOF持久化关闭时才会加载RDB文件。

    image

    3 小结

    (1) Redis提供了两种持久化方式:RDB和AOF

    (2) RDB使用一次性生成内存快照方式,产生的文件紧凑压缩比更高,因此读取RDB文件恢复速度更快。由于每次生成的RDB文件开销比较大,无法做到实时持久化,一般用于数据冷备和复制传输。

    (3) AOF通过追加写命令到文件实现持久化,通过appendfsync参数可以控制实时/秒级持久化。因为需要不断的追加写命令,所以AOF文件体积会越来越大,需要定期执行重写操作来降低文件体积。

    (4) AOF重写是个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无须对现有AOF文件进行任何读入、分析或者写人操作。

    (5) 在执行BGREWRITEAOF命令时,Redis服务器还维护一个AOF重写缓冲区,该缓冲区会在子进程创建新AOF文件期间,记录服务器执行的所有写命令。 当子进程完成创建新AOF文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新AOF文件的末尾,使得新旧两个AOF文件所保存的数据库状态一致。最后,服务器用新的AOF文件替换旧的AOF文件, 以此来完成AOF文件重写操作。

      本文完


      注:本文参考《Redis设计与实现》《Redis开发与运维》,如发现错误,请指正!

    相关文章

      网友评论

        本文标题:Redis持久化

        本文链接:https://www.haomeiwen.com/subject/mrsshctx.html