redis系列(六)—持久化机制

redis持久化机制

前言

大家好,牧码心今天给大家推荐一篇redis系列(六)—持久化机制的文章,在实际工作中有很多应用场景,希望对你有所帮助。内容如下:

  • 持久化概述
  • RDB持久化
  • AOF持久化
  • 持久化方案对比
  • 常见问题

持久化概述

redis持久化是指将在内存的数据同步或异步写到磁盘中,并永久性保存。支持RDB和AOF两种持久化机制,持久化功能有效地避免因进程退出造成的数据丢失问题,当下次重启时利用之前持久化的文件即可实现数据恢复。除此之外,为了进行灾难备份,可以将持久化文件拷贝到一个远程位置。

RDB持久化

  • 定义

    RDB持久化是将当前进程中的数据生成快照保存到硬盘(因此也称作快照持久化),保存的文件后缀是rdb;当Redis重新启动时,可以读取快照文件恢复数据。触发RDB持久化过程分为手动触发和自动触发。

  • 手动触发
    手动触发分别对应save和bgsave命令:
    save命令:阻塞当前Redis服务器, 直到RDB过程完成为止, 对于内存比较大的实例会造成长时间阻塞, 线上环境不建议使用。例如:

    127.0.0.1:6379> save
    OK
    (1.31s)
    

    bgsave命令:会创建一个子进程,由子进程来负责创建RDB文件,父进程(即Redis主进程)则继续处理请求。例如:

    127.0.0.1:6379> bgsave
    Background saving started
    

    显然bgsave命令是针对save阻塞问题做的优化。 因此Redis内部所有的涉及RDB的操作都采用bgsave的方式,而save命令已经废弃。

  • 自动触发
    除了执行命令手动触发之外,Redis内部还存在自动触发RDB的持久化机制,例如以下场景都会触发自动执行:
    1.使用save相关配置, 如“save m n”。表示m秒内数据集存在n次修改时,自动触发bgsave;
    2.执行debug reload命令重新加载Redis时, 也会自动触发save操作;
    3.默认情况下执行shutdown命令时, 如果没有开启AOF持久化功能则自动执行bgsave。

    下面对第一种场景进行说明:

    save m n
    

    例如,查看redis的默认配置文件(redis),可以看到如下配置信息:
    RDB持久化配置
    其中save 900 1的含义是:当时间到900秒时,如果redis数据发生了至少1次变化,则执行bgsave;save 300 10和save 60 10000同理。当三个save条件满足任意一个时,都会引起bgsave的调用。

  • save m n 实现原理
    Redis的save m n,是通过serverCron函数、dirty计数器、和lastsave时间戳来实现的。
    serverCron是Redis服务器的周期性操作函数,默认每隔100ms执行一次;该函数对服务器的状态进行维护,其中一项工作就是检查 save m n 配置的条件是否满足,如果满足就执行bgsave。
    dirty计数器是Redis服务器维持的一个状态,记录了上一次执行bgsave/save命令后,服务器状态进行了多少次修改(包括增删改);而当save/bgsave执行完成后,会将dirty重新置为0。
    例如,如果Redis执行了set mykey helloworld,则dirty值会+1;如果执行了sadd myset v1 v2 v3,则dirty值会+3;注意dirty记录的是服务器进行了多少次修改,而不是客户端执行了多少修改数据的命令。
    lastsave时间戳也是Redis服务器维持的一个状态,记录的是上一次成功执行save/bgsave的时间。
    save m n的原理如下:每隔100ms,执行serverCron函数;在serverCron函数中,遍历save m n配置的保存条件,只要有一个条件满足,就进行bgsave。对于每一个save m n条件,只有下面两条同时满足时才算满足:

    (1)当前时间-lastsave > m

    (2)dirty >= n
    下图是save m n触发bgsave执行时,服务器打印日志的情况:
    在这里插入图片描述

  • 执行流程
    bgsave是主流的触发RDB持久化方式, 下面根据下图了解它的运作流程。
    RDB持久化执行流程图
    从上图可以看出整个流程可分为如下几个步骤:

  1. Redis父进程首先判断:当前是否在执行save,或bgsave/bgrewriteaof的子进程,如果在执行则bgsave命令直接返回。(bgsave/bgrewriteaof 的子进程不能同时执行,主要是基于性能方面的考虑:两个并发的子进程同时执行大量的磁盘写操作,可能引起严重的性能问题;)

  2. 父进程执行fork操作创建子进程,这个过程中父进程是阻塞的,Redis不能执行来自客户端的任何命令;

  3. 父进程fork后,bgsave命令返回”Background saving started”信息并不再阻塞父进程,并可以响应其他命令;

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

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

  • RDB文件

    • 存储路径:RDB文件保存在dir配置指定的目录下, 文件名通过dbfilename配置指定。可以通过执行config set dir{newDir}和config set dbfilename{newFileName}运行期动态执行,当下次运行时RDB文件会保存到新目录;
      当遇到坏盘或磁盘写满等情况时, 可以通过config set dir{newDir}在线修改文件路径到可用的磁盘路径, 之后执行bgsave进行磁盘切换, 同样适用于AOF持久化文件。

      127.0.0.1:6379> config set dir /data/db/redis
      
    • 压缩
      Redis默认采用LZF算法对生成的RDB文件做压缩处理, 压缩后的文件远远小于内存大小, 默认开启, 可以通过参数config setrdbcompression{yes|no}动态修改。如:

      127.0.0.1:6379> CONFIG SET rdbcompression yes
      	OK
      

      虽然压缩RDB会消耗CPU, 但可大幅降低文件的体积, 方便保存到硬盘或通过网络发送给从节点, 因此线上建议开启。

  • 启动时加载
    RDB文件的载入工作是在服务器启动时自动执行的,并没有专门的命令。但是由于AOF的优先级更高,因此当AOF开启时,Redis会优先载入AOF文件来恢复数据;只有当AOF关闭时,才会在Redis服务器启动时检测RDB文件,并自动载入。服务器载入RDB文件期间处于阻塞状态,直到载入完成为止。
    Redis载入RDB文件时,会对RDB文件进行校验,如果文件损坏,则日志中会打印错误,Redis启动失败。

  • RDB常用配置总结
    下面是RDB常用的配置项,以及默认值;

    save m n:bgsave自动触发的条件;如果没有save m n配置,相当于自动的RDB持久化关闭,不过此时仍可以通过其他方式触发
    
    stop-writes-on-bgsave-error yes:当bgsave出现错误时,Redis是否停止执行写命令;设置为yes,则当硬盘出现问题时,可以及时发现,避免数据的大量丢失;设置为no,则Redis无视bgsave的错误继续执行写命令,当对Redis服务器的系统(尤其是硬盘)使用了监控时,该选项考虑设置为no
    
    rdbcompression yes:是否开启RDB文件压缩
    
    rdbchecksum yes:是否开启RDB文件的校验,在写入文件和读取文件时都起作用;关闭checksum在写入文件和启动文件时大约能带来10%的性能提升,但是数据损坏时无法发现
    
    dbfilename dump.rdb:RDB文件名
    
    dir ./:RDB文件和AOF文件所在目录
    

AOF 持久化

  • 定义

    RDB持久化是将数据写入文件,AOF的持久化则是将redis的每次执行的命令记录写到日志文件中(类似mysql的binlog)。当redis重启时再次执行AOF文件中命令进行恢复

  • 开启AOF
    Redis服务器默认开启RDB,关闭AOF;要开启AOF,AOF文件名通过appendfilename配置设置, 默认文件名是appendonly.aof。保存路径同RDB持久化方式一致, 通过dir配置指定:

    appendonly yes
    
  • 执行流程
    AOF的工作流程操作: 命令写入(append)、文件同步(sync)、文件重写(rewrite)、重启加载(load),如图所示:
    AOF持久化执行流程
    从上图可以看出整个流程可分为如下几个步骤:
    1) 所有的写入命令会追加到aof_buf( 缓冲区) 中。
    2) AOF缓冲区根据对应的策略向硬盘做同步操作。
    3) 随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的。
    4) 当Redis服务器重启时,可以加载AOF文件进行数据恢复。

    了解AOF工作流程之后, 下面针对每个步骤做详细介绍:

    • 命令写入

      • AOF 命令写入的内容直接是文本协议格式,例如set hello world 命令,在AOF 缓冲区会追加如下文本:
      *3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n
      
      • AOF 采用文本协议格式,这样具有更好的兼容性,可读性强,方便处理和避免二次处理开销;
      • AOF 命令会写入缓冲区aof buff,是因为Redis使用单线程响应命令, 如果每次写AOF文件命令都直接追加到硬盘, 那么性能完全取决于当前硬盘负载。另一方面是Redis可以提供多种缓冲区同步硬盘的策略, 在性能和安全性方面做出平衡。
    • 文件同步
      Redis提供了多种AOF缓存区的同步文件策略,策略涉及到操作系统的write函数和fsync函数,说明如下:

      • write函数会触发延迟写机制,用户线程调用write函数先将数据写入缓冲区后直接返回,当缓冲区满或超过指定时限后。系统会由专门的线程进行刷盘操作。这样提高了写入效率,但也带来不安全性。
      • 同步文件之前, 如果此时系统故障宕机, 缓冲区内数据将丢失。因此系统同时提供了fsync、fdatasync等同步函数,可以强制操作系统立刻将缓冲区中的数据写入到硬盘里,从而确保数据的安全性。
      • Redis提供了多种AOF缓冲区同步文件策略, 由参数appendfsync控制,不同值的含义如下:
        • always: 命令写入aof buff 后调用系统fsync 操作同步到AOF文件,fsync 完成后线程返回;这种情况下,每次有写命令都要同步到AOF文件,硬盘IO成为性能瓶颈,Redis只能支持大约几百TPS写入,严重降低了Redis的性能;
        • everysec:命令写入aof_buf后调用系统write操作,write完成后线程返回;fsync同步文件操作由专门的线程每秒调用一次。everysec是前述两种策略的折中,是性能和数据安全性的平衡,因此是Redis的默认配置,也是我们推荐的配置。
        • no:命令写入aof_buf后调用系统write操作,不对AOF文件做fsync同步;同步由操作系统负责,通常同步周期为30秒。这种情况下,文件同步的时间不可控,且缓冲区中堆积的数据会很多,数据安全性无法保证。
    • 文件重写
      随着命令不断写入AOF, 文件会越来越大, 为了解决这个问题, Redis引入AOF重写机制压缩文件体积。 AOF文件重写是把Redis进程内的数据转化为写命令同步到新AOF文件的过程。文件重写之所以能够压缩AOF文件,原因在于:

      • 过期的数据不再写入文件;
      • 无效的命令不再写入文件:如有些数据被重复设值(set mykey v1, set mykey v2)、有些数据被删除了(sadd myset v1, del myset)等等;
      • 多条命令可以合并为一个:如sadd myset v1, sadd myset v2, sadd myset v3可以合并为sadd myset v1 v2 v3。不过为了防止单条命令过大造成客户端缓冲区溢出,对于list、set、hash、zset类型的key,并不一定只使用一条命令;而是以某个常量为界将命令拆分为多条。这个常量在redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD中定义,不可更改,3.0版本中值是64。
    • 文件重写触发
      AOF重写过程可以手动触发和自动触发:

      • 手动触发:直接调用bgrewriteaof命令,该命令的执行与bgsave有些类似:都是fork子进程进行具体的工作,且都只有在fork时阻塞。
        在这里插入图片描述

      • 自动触发:根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数,以及aof_current_size和aof_base_size状态确定触发时机。

        • auto-aof-rewrite-min-size:执行AOF重写时,文件的最小体积,默认值为64MB。
        • auto-aof-rewrite-percentage:执行AOF重写时,当前AOF大小(即aof_current_size)和上一次重写时AOF大小(aof_base_size)的比值。
          其中,参数可以通过config get命令查看:
          在这里插入图片描述
          只有当auto-aof-rewrite-min-size和auto-aof-rewrite-percentage两个参数同时满足时,才会自动触发AOF重写,即bgrewriteaof操作。
      • 文件重写的流程
        AOF持久化重写流程图
        对照上图,文件重写的流程如下:

    1. Redis父进程首先判断当前是否存在正在执行 bgsave/bgrewriteaof的子进程,如果存在则bgrewriteaof命令直接返回,如果存在bgsave命令则等bgsave执行完成后再执行。前面曾介绍过,这个主要是基于性能方面的考虑。

    2. 父进程执行fork操作创建子进程,这个过程中父进程是阻塞的。

    3. 父进程fork后,bgrewriteaof命令返回”Background append only file rewrite started”信息并不再阻塞父进程,并可以响应其他命令。Redis的所有写命令依然写入AOF缓冲区,并根据appendfsync策略同步到硬盘,保证原有AOF机制的正确。

    4. 由于fork操作使用写时复制技术,子进程只能共享fork操作时的内存数据。由于父进程依然在响应命令,因此Redis使用AOF重写缓冲区(图中的aof_rewrite_buf)保存这部分数据,防止新AOF文件生成期间丢失这部分数据。也就是说,bgrewriteaof执行期间,Redis的写命令同时追加到aof_buf和aof_rewirte_buf两个缓冲区。

    5. 子进程根据内存快照,按照命令合并规则写入到新的AOF文件。

    6. 子进程写完新的AOF文件后,向父进程发信号,父进程更新统计信息,具体可以通过info persistence查看。

    7. 父进程把AOF重写缓冲区的数据写入到新的AOF文件,这样就保证了新AOF文件所保存的数据库状态和服务器当前状态一致。

    8. 使用新的AOF文件替换老文件,完成AOF重写。

    • 重启加载(load)
      redis启动时从持久化文件中加载数据,加载过程如图:
      redis启动加载持久化数据流程
      从上图可以看出整个流程可分为如下几个步骤:

        1) AOF持久化开启且存在AOF文件时, 优先加载AOF文件;
        
        2) AOF关闭或者AOF文件不存在时, 加载RDB文件;
        
        3) 加载AOF/RDB文件成功后, Redis启动成功。
        
        4) AOF/RDB文件存在错误时, Redis启动失败并打印错误信息。
      
      • 文件校验
        与载入RDB文件类似,Redis载入AOF文件时,会对AOF文件进行校验,如果文件损坏,则日志中会打印错误,Redis启动失败。但如果是AOF文件结尾不完整(机器突然宕机等容易导致文件尾部不完整),且aof-load-truncated参数开启,则日志中会输出警告,Redis忽略掉AOF文件的尾部,启动成功。aof-load-truncated参数默认是开启的:当遇到此问题时会忽略并继续启动, 同时打印如下警告日志:
        !!! Warning: short read while loading the AOF file !!!
        	!!! Truncating the AOF at offset 397856725 !!!
        	AOF loaded anyway because aof-load-truncated is enabled
        
  • AOF常用配置总结
    下面是AOF常用的配置项,以及默认值;前面介绍过的这里不再详细介绍。

    appendonly no:是否开启AOF
    appendfilename "appendonly.aof":AOF文件名
    dir ./:RDB文件和AOF文件所在目录
    appendfsync everysec:fsync持久化策略
    no-appendfsync-on-rewrite no:AOF重写期间是否禁止fsync;如果开启该选项,可以减轻文件重写时CPU和硬盘的负载(尤其是硬盘),但是可能会丢失AOF重写期间的数据;需要在负载和安全性之间进行平衡
    auto-aof-rewrite-percentage 100:文件重写触发条件之一
    auto-aof-rewrite-min-size 64mb:文件重写触发提交之一
    aof-load-truncated yes:如果AOF文件结尾损坏,Redis启动时是否仍载入AOF文件;
    

持久化方案对比

  • RDB和AOF的优缺点

    • RDB持久化

      优点:RDB文件紧凑,体积小,网络传输快,适合全量复制;恢复速度比AOF快很多。当然,与AOF相比,RDB最重要的优点之一是对性能的影响相对较小。

      缺点:RDB文件的致命缺点在于其数据快照的持久化方式决定了必然做不到实时持久化,而在数据越来越重要的今天,数据的大量丢失很多时候是无法接受的,因此AOF持久化成为主流。此外,RDB文件需要满足特定格式,兼容性差(如老版本的Redis不兼容新版本的RDB文件)。

    • AOF持久化

      优点:支持秒级持久化、兼容性好;
      缺点:文件大、恢复速度慢、对性能影响大。

  • 持久化策略选择
    在介绍持久化策略之前,首先要明白无论是RDB还是AOF,持久化的开启都是要付出性能方面代价的:对于RDB持久化,一方面是bgsave在进行fork操作时Redis主进程会阻塞,另一方面,子进程向硬盘写数据也会带来IO压力;对于AOF持久化,向硬盘写数据的频率大大提高(everysec策略下为秒级),IO压力更大,甚至可能造成AOF追加阻塞问题(后面会详细介绍这种阻塞),此外,AOF文件的重写与RDB的bgsave类似,会有fork时的阻塞和子进程的IO压力问题。相对来说,由于AOF向硬盘中写数据的频率更高,因此对Redis主进程性能的影响会更大。
    在实际生产环境中,根据数据量、应用对数据的安全要求、预算限制等不同情况,会有各种各样的持久化策略;如完全不使用任何持久化、使用RDB或AOF的一种,或同时开启RDB和AOF持久化等。此外,持久化的选择必须与Redis的主从策略一起考虑,因为主从复制与持久化同样具有数据备份的功能,而且主机master和从机slave可以独立的选择持久化方案。
    下面分场景来讨论持久化策略的选择,下面的讨论也只是作为参考,实际方案可能更复杂更具多样性。

    • 数据丢失可接受,可以都关闭持久化

    • 主从环境在,slave的存在既可以实现数据的热备,也可以进行读写分离分担Redis读请求,以及在master宕掉后继续提供服务。

      在这种情况下,一种可行的做法是:

      • master:完全关闭持久化(包括RDB和AOF),这样可以让master的性能达到最好

      • slave:关闭RDB,开启AOF(如果对数据安全要求不高,开启RDB关闭AOF也可以),并定时对持久化文件进行备份(如备份到其他文件夹,并标记好备份的时间);然后关闭AOF的自动重写,然后添加定时任务,在每天Redis闲时(如凌晨12点)调用bgrewriteaof。

      这里需要解释一下,为什么开启了主从复制,可以实现数据的热备份,还需要设置持久化呢?因为在一些特殊情况下,主从复制仍然不足以保证数据的安全,例如:
      1.master和slave进程同时停止:考虑这样一种场景,如果master和slave在同一栋大楼或同一个机房,则一次停电事故就可能导致master和slave机器同时关机,Redis进程停止;如果没有持久化,则面临的是数据的完全丢失。
      2.master误重启:考虑这样一种场景,master服务因为故障宕掉了,如果系统中有自动拉起机制(即检测到服务停止后重启该服务)将master自动重启,由于没有持久化文件,那么master重启后数据是空的,slave同步数据也变成了空的;如果master和slave都没有持久化,同样会面临数据的完全丢失。

    • 异地灾备:上述讨论的几种持久化策略,针对的都是一般的系统故障,如进程异常退出、宕机、断电等,这些故障不会损坏硬盘。但是对于一些可能导致硬盘损坏的灾难情况,如火灾地震,就需要进行异地灾备。例如对于单机的情形,可以定时将RDB文件或重写后的AOF文件,通过scp拷贝到远程机器,如阿里云、AWS等;对于主从的情形,可以定时在master上执行bgsave,然后将RDB文件拷贝到远程机器,或者在slave上执行bgrewriteaof重写AOF文件后,将AOF文件拷贝到远程机器上。一般来说,由于RDB文件文件小、恢复快,因此灾难恢复常用RDB文件;异地备份的频率根据数据安全性的需要及其他条件来确定,但最好不要低于一天一次。

常见问题

  • fork 操作阻塞
    当Redis做RDB或AOF重写时,一个必不可少的操作就是执行fork操作创建子进程,对于大多数操作系统来说fork是个重量级错误。 虽然fork创建的子进程不需要拷贝父进程的物理内存空间,但是会复制父进程的空间内存页表。
    例如:对于高流量的Redis实例OPS可达5万以上,如果fork操作耗时在秒级别将拖慢Redis几万条命令执行, 对线上应用延迟影响非常明显。正常情况下fork耗时应该是每GB消耗20毫秒左右。 可以在info stats统计中查latest_fork_usec指标获取最近一次fork操作耗时,单位微秒
    如何优化fork操作耗时:
    1) 优先使用物理机或者高效支持fork操作的虚拟化技术、避免使用Xen;
    2) 控制Redis实例最大可用内存,fork耗时跟内存量成正比, 线上建议每个Redis实例内存控制在10GB以内;
    3) 合理配置Linux内存分配策略、避免物理内存不足导致fork失败;
    4) 降低fork操作的频率, 如适度放宽AOF自动触发时机,避免不必要的全量复制等。

  • cpu消耗阻塞
    子进程负责把进程内的数据分批写入文件,这个过程属于CPU密集操作, 通常子进程对单核CPU利用率接近90%.由于子进程非常消耗CPU,会和父进程产生单核资源竞争。不要和其他CPU密集型服务部署在一起,造成CPU过度竞争。

  • 硬盘消耗阻塞
    子进程主要职责是把AOF或者RDB文件写入硬盘持久化。 势必造成硬盘写入压力。 根据Redis重写AOF/RDB的数据量, 结合系统工具如sar、iostat、 iotop等,可分析出重写期间硬盘负载情况。

    对于硬盘开销优化。 优化方法如下:
    a) 不要和其他高硬盘负载的服务部署在一起。 如: 存储服务、 消息队列服务等
    b) AOF重写时会消耗大量硬盘IO, 可以开启配置no-appendfsync-on-rewrite, 默认关闭。 表示在AOF重写期间不做fsync操作。
    c) 当开启AOF功能的Redis用于高流量写入场景时, 如果使用普通机械磁盘,写入吞吐一般在100MB/s左右, 这时Redis实例的瓶颈主要在AOF同步硬盘上。
    d) 对于单机配置多个Redis实例的情况, 可以配置不同实例分盘存储AOF文件, 分摊硬盘写入压力。

参考

  • 《Redis开发与运维》
  • https://www.cnblogs.com/kismetv/p/9137897.html#t4
发布了84 篇原创文章 · 获赞 27 · 访问量 9万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览