Redis笔记(六)之持久化

redis版本

  1. redis版本:3.2.11

持久化

  1. Redis的数据全部在内存中,因此必须有一种机制来保证Redis的数据不会因为故障而丢失,这种机制就是持久化机制
  2. Redis的持久化机制有两种
    • 快照,一次全量备份。快照是内存数据的二进制序列化形式,在存储上非常紧凑
    • AOF日志是连续的增量备份。AOF日志记录的是内存数据修改的指令记录文本。AOF日志在长期的运行过程中会变得无比庞大,数据库重启时需要加载AOF日志进行指令重放,这个时间会非常耗时,所以需要定期进行AOF重写,给AOF日志瘦身
  3. 快照带来的问题:
    • Redis需要一边持久化,一遍响应客户端请求。但是持久化的同时,内存数据结构在不断变化,比如一个特别大的hash正在持久化,同时另一个请求过来将它删除,但是此时持久化还未完成,此时该如何解决这个问题呢?Redis使用操作系统的多进程Copy On Write机制来实现快照持久化,在下面的触发流程中有详细描述

RDB

  1. RDB持久化是将当前进程数据生成快照保存到硬盘的过程,此过程可以手动或者自动触发

触发

  1. 手动触发
  • save命令:阻塞当前的Redis服务器,直到RDB过程完成为止,对于内存比较大的实例会造成长时间阻塞,线上环境不建议使用
  • bgsave:Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间比较短。bgsave是对save阻塞问题的优化,因此Redis内部所有涉及RDB的操作都采用bgsave的方式,而save命令已经废弃
  1. 自动触发
  • 配置文件中配置save m n 。表示m秒内数据集存在n次修改时,自动触发bgsave
  • 如果从节点执行全量复制操作,主节点自动执行bgsave生成RDB文件并发送给从节点
  • 执行debug reload重新加载Redis时,也会自动触发save操作
  • 默认情况下执行shutdown命令时,如果没有开启AOF持久化功能则自动执行bgsave

触发流程

  1. 触发流程

    • 执行bgsave命令,Redis父进程判断当前是否存在正在执行的子进程,如RDB/AOF进程,如果已经执行则直接返回

    • 父进程执行fork操作创建子进程(调用glibc的fork函数),fork操作过程中父进程会阻塞,通过info stats查看latest_fork_usec选项,可以获取最近一次fork操作的耗时,单位为微秒

      127.0.0.1:6379> info stats
      # Stats
      ...省略...
      latest_fork_usec:85632
      ...省略...
      
    • 父进程fork完成后,bgsave命令返回Background saving started后就不在阻塞父进程,可以继续响应其他命令

      127.0.0.1:6379> bgsave
      Background saving started
      
    • 父进程创建RDB文件,根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换。执行lastsave命令可以获取最后一次生成RBD的时间,对应info统计的rdb_last_save_time选项

      127.0.0.1:6379> lastsave
      (integer) 1552811724
      127.0.0.1:6379> info Persistence
      # Persistence
      ...省略...
      rdb_last_save_time:1552811724
      ...省略...
      
    • 进程发送信号给父进程表示完成,父进程更新统计信息。可通过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:1552811724
      rdb_last_bgsave_status:ok
      rdb_last_bgsave_time_sec:37
      rdb_current_bgsave_time_sec:-1
      ...省略...
      
  2. 过程图

    image-20190317185338270

  3. fork的原因:

    • 为了不阻塞线上的业务,单独的子进程进行持久化,父进程可以正常响应客户端请求。子进程只做数据持久化,不会修改已有内存的数据结构,仅仅是遍历读取然后序列化到磁盘。当子进程产生时,数据再也不会改变,而父进程的数据可能在不断改变,操作系统的copy on write机制会将父进程中修改的页面复制出来,然后对这个复制的页面进行修改,子进程相应的页面不会有任何变化,随着父进程修改的持续进行,越来越多的共享页面被复制出来,内存就会持续增长,但是也不会超过原有数据内存的2倍。一般情况下Redis中的数据大部分都是冷数据,所以被复制出来的往往只有一部分页面。

RDB文件的处理

  1. RDB文件保存在dir配置指定的目录下,文件名通过dbfilename配置指定。也可以在运行时动态配置,当下次运行时RDB文件会保存到新目录(当磁盘写满或者磁盘目录坏掉时,可动态修改目录)。Redis默认采用LZF算法对生成的RDB文件做压缩处理,压缩后文件远小于内存大小,默认开启,也可以动态实时修改。虽然压缩RDB会消耗CPU,但是可以大幅度降低文件的大小,节省磁盘空间或者通过网络发送给从节点时节省发送时间,因此建议开启

    1. 静态配置在redis.conf中
    dir /usr/local/redis-3.2.11/data
    dbfilename dump_6379.rdb
    rdbcompression yes
    
    2. 动态配置
    config set dir 新目录
    config set dbfilename 新文件名
    config set rdbcompression yes或者no
    
  2. RDB文件的修复

    • 如果Redis加载损坏的RDB文件,此时Redis会拒绝启动,并打印日志。此时可以使用redis-check-dump工具检测RDB文件并获取对应的错误报告。

      Short read or OOM loading DB. Unrecoverable error, aborting now.
      

RDB文件的优缺点

  1. 优点
    • RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据快照。非常适合定期全量备份(每6小时执行bgsave备份一次,用于灾备)、全量复制等场景。
    • Redis加载RDB恢复数据远远快于AOF的方式
  2. 缺点
    • RDB方式数据没办法做到实时持久化或者秒级持久化。因为bgsave每次运行都要fork创建子进程,属于重量级操作,频繁执行成本过高
    • RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,老版本与新版本无法兼容

AOF

  1. AOF(append only file)持久化:以独立日志的方式记录每次写命令(即对内存进行修改的命令),重启时再重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用是解决数据持久化的实时性。假设AOF记录了Redis实例自创建以来所有的修改性指令序列,那么就可以通过对一个空的Redis实例顺序执行所有的命令(即重放)来恢复Redis当前实例的内存数据。

  2. Redis在收到修改命令后,进行参数校验、逻辑处理,如果处理成功,就立即将该指令文本存储到AOF日志中,即先执行内存修改然后再将日志存盘,与一般的存储引擎不同,比如hbase等(先存储日志后执行命令)

  3. 开启AOF需要配置在配置文件中配置,默认不开启

    appendonly yes										开启
    appendfilename "appendonly.aof"	  文件名
    dir /usr/local/redis-3.2.11/data  保存路径
    
  4. 工作流程

    • 命令写入(append):所有的写入命令会追加aof_buf(缓冲区)中
    • 文件同步(sync):AOF缓冲区根据对应的策略向磁盘做同步
    • 文件重写(rewrite):随着AOF文件越来越大,需要定期对AOF文件进行重写(rewrite),达到压缩的目的
    • 重启加载(load):redis重启时,可以加载AOF文件进行数据恢复

命令写入

  1. AOF命令写入的内容直接是文本协议格式,比如set hello world在AOF缓冲区会追加如下文本

    *3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n
    
  2. AOF为什么直接采用文本协议格式?

    • 文本协议很好的兼容性
    • 开启AOF后,所有写入命令都包含追加操作,直接采用协议格式,避免了二次处理开销
    • 文本协议具有可读性,方便直接修改和处理
  3. AOF为什么先写入aof_buf缓冲区?

    • 可以提供多种缓冲区同步到磁盘的策略
    • 写入缓冲区减少与磁盘的交互次数

文件同步

  1. Redis有三种AOF缓冲区同步文件策略,由参数appendfsync控制

    配置值描述
    always命令写入aof_buf后调用系统fsync操作同步到AOF文件,fsync完成后线程返回。每次写入都要同步AOF文件,一般不建议配置
    everysec命令写入aof_buf后调用系统write操作,write完成后线程返回,fsync同步文件操作由专门的线程每秒调用一次。默认配置,也是建议的同步策略。既兼顾性能又兼顾数据安全性
    no命令写入aof_buf后调用系统write操作,不对AOF文件做fsync同步,同步硬盘操作由操作系统负责,通常同步周期最长30s。操作系统每次同步AOF文件的周期不可控,而且会加大每次同步硬盘的数据量,虽然提升了性能,但是数据安全性无法保证
  2. 系统调用write和fsync

    • write操作会触发延迟写机制,linux在内核提供页缓冲区(Page Cache)用来提高硬盘IO性能。writer操作在写入系统缓冲区后直接返回。同步硬盘操作依赖于系统调度机制,例如:缓冲区页空间写满或达到特定时间周期。同步文件之前,如果此时系统故障宕机,缓冲区内数据将丢失
    • fsync针对单个文件操作(比如AOF文件),做强制硬盘同步,fsync将阻塞直到写入硬盘完成后返回,保证了数据持久化。fsync是一个耗时的IO操作,会降低Redis的性能,并增加系统IO负担

重写

  1. 随着命令不断写入AOF,文件会越来越大,Redis引用AOF重写机制压缩文件体积。AOF文件重写是把Redis进程内的数据转化为写命令同步到新AOF文件的过程

  2. 重写后的AOF文件变小的原因

    • 进程内已经超时的数据不再写入文件
    • 旧的AOF文件含有很多无效的命令,重写使用的是进程内数据直接生成,即只保留当前最终数据的写入命令
    • 多条写命令合并为一条。比如lpush list alpush list blpush list c转化为lpush list a b c。为了防止单条命令过大导致缓冲区溢出,对于list、set 、hash、zset等操作,以64个元素为界拆分为多条
  3. AOF重写降低文件占用空间,而且更小的AOF可以更快地被Redis加载。

  4. bgrewriteaof重写原理

    • 开辟一个子进程对内存进行遍历,转换为Redis的操作命令,序列化到一个新的AOF日志文件中。序列化完毕后再将操作期间发生的增量AOF日志追加到这个新的AOF日志文件中,追加完毕后就立即替换旧的AOF日志文件。
  5. 触发重写

    • 手动触发:调用bgrewriteaof

      127.0.0.1:6379> bgrewriteaof
      Background append only file rewriting started
      
    • 自动触发

      auto-aof-rewrite-percentage 100	当前AOF文件空间/上一次重写后AOF文件空间 = 比值
      auto-aof-rewrite-min-size 64mb 运行AOF重写时文件最小体积,默认64MB
      
      127.0.0.1:6379> info Persistence
      # Persistence
      ...省略...
      aof_rewrite_in_progress:0
      aof_rewrite_scheduled:0
      aof_last_rewrite_time_sec:30
      aof_current_rewrite_time_sec:-1
      aof_last_bgrewrite_status:ok
      aof_last_write_status:ok
      

重启加载

  1. 启动流程

    • AOF持久化开启且存在AOF文件时,优先加载AOF文件

    • AOF关闭或者AOF文件不存在时,加载RDB文件

    • 加载AOF/RDB文件成功后,Redis启动成功

    • AOF/RDB文件存在错误时,Redis启动失败并打印错误信息

  2. 加载损坏的AOF文件

    • 第一步备份AOF文件

    • 第二步使用redis-check-aof-fix进行修复,修复后使用diff -u对比修复前后的数据差异,找出丢失的数据,看是否可以人工补全

Redis 4.0 混合持久化

  1. 重启Redis时,很少使用rdb来恢复内存状态,因为会丢失大量数据(尤其在上一次快照后下一次快照前,有大量请求发生)。通常使用AOF重放,但是重放AOF日志相对于使用rdb来说要慢很多,当Redis内存的数据量很大时,启动花费的时间很长
  2. Redis4.0为了解决以上问题,增加了一个新的持久化选项——混合持久化。即将RDB文件的内容和增量的AOF日志(上一次持久化开始到持久化结束区间发生的日志)存在一起。

最佳实践

  1. 一般建议Redis的主节不要进行持久化操作,持久化操作主要在从节点进行,因为从节点一般是备份节点,不会有客户端的请求压力,系统资源比较充裕。在生产环境下要有多个从节点,并且要监控好主从节点之间网络延迟等问题,避免发生网络分区导致数据从节点与主节点数据相差太大。

  2. 一般情况下fork耗时1GB大约20ms左右,可以使用info stats统计查看latest_fork_usec获取最近一次fork操作耗时(微妙)。改善fork耗时的实践

    • 优先使用物理机器
    • 控制Redis实例最大可用内存大小,fork耗时与实例占用内存量成正比。一般建议Redis实例内存控制在10GB以内
    • 合理配置linux内存分配策略,避免物理内存不足导致fork失败
    • 降低fork操作的频率
  3. 子进程开销

    • Redis是CPU密集型服务,不要做绑定CPU操作。由于子进程非常消耗CPU(进程内的数据写入文件),会和父进程产生单核资源竞争。如果部署多个Redis实例, 避免同一时刻有多个子进程执行重写

    • 避免在大量写入时做子进程重写操作,这样将导致父进程维护大量页副本,造成内存消耗。

    • 不要和其他高硬盘负载的服务部署在一起。AOF重写时会消耗大量磁盘IO,可以配置no-appendfsync-on-rewrite,默认关闭,表示在AOF重写期间不做fsync操作。单机多个Redis实例,建议配置多个不同的磁盘存储AOF文件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值