redis版本
- redis版本:3.2.11
持久化
- Redis的数据全部在内存中,因此必须有一种机制来保证Redis的数据不会因为故障而丢失,这种机制就是持久化机制
- Redis的持久化机制有两种
- 快照,一次全量备份。快照是内存数据的二进制序列化形式,在存储上非常紧凑
- AOF日志是连续的增量备份。AOF日志记录的是内存数据修改的指令记录文本。AOF日志在长期的运行过程中会变得无比庞大,数据库重启时需要加载AOF日志进行指令重放,这个时间会非常耗时,所以需要定期进行AOF重写,给AOF日志瘦身
- 快照带来的问题:
- Redis需要一边持久化,一遍响应客户端请求。但是持久化的同时,内存数据结构在不断变化,比如一个特别大的hash正在持久化,同时另一个请求过来将它删除,但是此时持久化还未完成,此时该如何解决这个问题呢?Redis使用操作系统的多进程
Copy On Write
机制来实现快照持久化,在下面的触发流程中有详细描述
- Redis需要一边持久化,一遍响应客户端请求。但是持久化的同时,内存数据结构在不断变化,比如一个特别大的hash正在持久化,同时另一个请求过来将它删除,但是此时持久化还未完成,此时该如何解决这个问题呢?Redis使用操作系统的多进程
RDB
- RDB持久化是将当前进程数据生成快照保存到硬盘的过程,此过程可以手动或者自动触发
触发
- 手动触发
- save命令:阻塞当前的Redis服务器,直到RDB过程完成为止,对于内存比较大的实例会造成长时间阻塞,线上环境不建议使用
- bgsave:Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间比较短。bgsave是对save阻塞问题的优化,因此Redis内部所有涉及RDB的操作都采用bgsave的方式,而save命令已经废弃
- 自动触发
- 配置文件中配置
save m n
。表示m秒内数据集存在n次修改时,自动触发bgsave - 如果从节点执行全量复制操作,主节点自动执行bgsave生成RDB文件并发送给从节点
- 执行debug reload重新加载Redis时,也会自动触发save操作
- 默认情况下执行shutdown命令时,如果没有开启AOF持久化功能则自动执行bgsave
触发流程
-
触发流程
-
执行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 ...省略...
-
-
过程图
-
fork的原因:
- 为了不阻塞线上的业务,单独的子进程进行持久化,父进程可以正常响应客户端请求。子进程只做数据持久化,不会修改已有内存的数据结构,仅仅是遍历读取然后序列化到磁盘。当子进程产生时,数据再也不会改变,而父进程的数据可能在不断改变,操作系统的copy on write机制会将父进程中修改的页面复制出来,然后对这个复制的页面进行修改,子进程相应的页面不会有任何变化,随着父进程修改的持续进行,越来越多的共享页面被复制出来,内存就会持续增长,但是也不会超过原有数据内存的2倍。一般情况下Redis中的数据大部分都是冷数据,所以被复制出来的往往只有一部分页面。
RDB文件的处理
-
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
-
RDB文件的修复
-
如果Redis加载损坏的RDB文件,此时Redis会拒绝启动,并打印日志。此时可以使用
redis-check-dump
工具检测RDB文件并获取对应的错误报告。Short read or OOM loading DB. Unrecoverable error, aborting now.
-
RDB文件的优缺点
- 优点
- RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据快照。非常适合定期全量备份(每6小时执行bgsave备份一次,用于灾备)、全量复制等场景。
- Redis加载RDB恢复数据远远快于AOF的方式
- 缺点
- RDB方式数据没办法做到实时持久化或者秒级持久化。因为bgsave每次运行都要fork创建子进程,属于重量级操作,频繁执行成本过高
- RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,老版本与新版本无法兼容
AOF
-
AOF(append only file)持久化:以独立日志的方式记录每次写命令(即对内存进行修改的命令),重启时再重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用是解决数据持久化的实时性。假设AOF记录了Redis实例自创建以来所有的修改性指令序列,那么就可以通过对一个空的Redis实例顺序执行所有的命令(即重放)来恢复Redis当前实例的内存数据。
-
Redis在收到修改命令后,进行参数校验、逻辑处理,如果处理成功,就立即将该指令文本存储到AOF日志中,即先执行内存修改然后再将日志存盘,与一般的存储引擎不同,比如hbase等(先存储日志后执行命令)
-
开启AOF需要配置在配置文件中配置,默认不开启
appendonly yes 开启 appendfilename "appendonly.aof" 文件名 dir /usr/local/redis-3.2.11/data 保存路径
-
工作流程
- 命令写入(append):所有的写入命令会追加aof_buf(缓冲区)中
- 文件同步(sync):AOF缓冲区根据对应的策略向磁盘做同步
- 文件重写(rewrite):随着AOF文件越来越大,需要定期对AOF文件进行重写(rewrite),达到压缩的目的
- 重启加载(load):redis重启时,可以加载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为什么先写入aof_buf缓冲区?
- 可以提供多种缓冲区同步到磁盘的策略
- 写入缓冲区减少与磁盘的交互次数
文件同步
-
Redis有三种AOF缓冲区同步文件策略,由参数appendfsync控制
配置值 描述 always 命令写入aof_buf后调用系统fsync操作同步到AOF文件,fsync完成后线程返回。每次写入都要同步AOF文件,一般不建议配置 everysec 命令写入aof_buf后调用系统write操作,write完成后线程返回,fsync同步文件操作由专门的线程每秒调用一次。默认配置,也是建议的同步策略。既兼顾性能又兼顾数据安全性 no 命令写入aof_buf后调用系统write操作,不对AOF文件做fsync同步,同步硬盘操作由操作系统负责,通常同步周期最长30s。操作系统每次同步AOF文件的周期不可控,而且会加大每次同步硬盘的数据量,虽然提升了性能,但是数据安全性无法保证 -
系统调用write和fsync
- write操作会触发延迟写机制,linux在内核提供页缓冲区(Page Cache)用来提高硬盘IO性能。writer操作在写入系统缓冲区后直接返回。同步硬盘操作依赖于系统调度机制,例如:缓冲区页空间写满或达到特定时间周期。同步文件之前,如果此时系统故障宕机,缓冲区内数据将丢失
- fsync针对单个文件操作(比如AOF文件),做强制硬盘同步,fsync将阻塞直到写入硬盘完成后返回,保证了数据持久化。fsync是一个耗时的IO操作,会降低Redis的性能,并增加系统IO负担
重写
-
随着命令不断写入AOF,文件会越来越大,Redis引用AOF重写机制压缩文件体积。AOF文件重写是把Redis进程内的数据转化为写命令同步到新AOF文件的过程
-
重写后的AOF文件变小的原因
- 进程内已经超时的数据不再写入文件
- 旧的AOF文件含有很多无效的命令,重写使用的是进程内数据直接生成,即只保留当前最终数据的写入命令
- 多条写命令合并为一条。比如
lpush list a
、lpush list b
、lpush list c
转化为lpush list a b c
。为了防止单条命令过大导致缓冲区溢出,对于list、set 、hash、zset等操作,以64个元素为界拆分为多条
-
AOF重写降低文件占用空间,而且更小的AOF可以更快地被Redis加载。
-
bgrewriteaof
重写原理- 开辟一个子进程对内存进行遍历,转换为Redis的操作命令,序列化到一个新的AOF日志文件中。序列化完毕后再将操作期间发生的增量AOF日志追加到这个新的AOF日志文件中,追加完毕后就立即替换旧的AOF日志文件。
-
触发重写
-
手动触发:调用
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
-
重启加载
-
启动流程
-
AOF持久化开启且存在AOF文件时,优先加载AOF文件
-
AOF关闭或者AOF文件不存在时,加载RDB文件
-
加载AOF/RDB文件成功后,Redis启动成功
-
AOF/RDB文件存在错误时,Redis启动失败并打印错误信息
-
-
加载损坏的AOF文件
-
第一步备份AOF文件
-
第二步使用
redis-check-aof-fix
进行修复,修复后使用diff -u对比修复前后的数据差异,找出丢失的数据,看是否可以人工补全
-
Redis 4.0 混合持久化
- 重启Redis时,很少使用rdb来恢复内存状态,因为会丢失大量数据(尤其在上一次快照后下一次快照前,有大量请求发生)。通常使用AOF重放,但是重放AOF日志相对于使用rdb来说要慢很多,当Redis内存的数据量很大时,启动花费的时间很长
- Redis4.0为了解决以上问题,增加了一个新的持久化选项——
混合持久化
。即将RDB文件的内容和增量的AOF日志(上一次持久化开始到持久化结束区间发生的日志)存在一起。
最佳实践
-
一般建议Redis的主节不要进行持久化操作,持久化操作主要在从节点进行,因为从节点一般是备份节点,不会有客户端的请求压力,系统资源比较充裕。在生产环境下要有多个从节点,并且要监控好主从节点之间网络延迟等问题,避免发生网络分区导致数据从节点与主节点数据相差太大。
-
一般情况下fork耗时1GB大约20ms左右,可以使用
info stats
统计查看latest_fork_usec
获取最近一次fork操作耗时(微妙)。改善fork耗时的实践- 优先使用物理机器
- 控制Redis实例最大可用内存大小,fork耗时与实例占用内存量成正比。一般建议Redis实例内存控制在10GB以内
- 合理配置linux内存分配策略,避免物理内存不足导致fork失败
- 降低fork操作的频率
-
子进程开销
-
Redis是CPU密集型服务,不要做绑定CPU操作。由于子进程非常消耗CPU(进程内的数据写入文件),会和父进程产生单核资源竞争。如果部署多个Redis实例, 避免同一时刻有多个子进程执行重写
-
避免在大量写入时做子进程重写操作,这样将导致父进程维护大量页副本,造成内存消耗。
-
不要和其他高硬盘负载的服务部署在一起。AOF重写时会消耗大量磁盘IO,可以配置
no-appendfsync-on-rewrite
,默认关闭,表示在AOF重写期间不做fsync操作。单机多个Redis实例,建议配置多个不同的磁盘存储AOF文件。
-