文章目录
- 前置知识
- RDB(定期备份)
- 触发机制
- 流程说明
- RDB文件的处理
- RDB 的优缺点
- AOF(实时备份)
- 使用AOF
- 命令写入
- AOF工作流程
- 文件同步
- 重写机制
- 重写触发机制
- AOF进制重写流程
- 混合持久化
- 启动时数据恢复
- 总结
前置知识
回顾MySQL
MySQL的事务有4个比较核心的特性:
- 原子性 一致性 持久性 隔离性
何为持久
重启进程/重启主机之后数据是否仍然存在 => 把数据存储到硬盘上:持久,如果把数据存储到内存上 :不持久
redis是一个内存数据库,将数据存储到内存当中,但是内存当中的数据是不持久的,如果想做到持久,那么就需要让redis把数据存储到硬盘上。Redis相比于MySQL这样的关系型数据库,最明显的特点/优势:效率高,快
为了保证速度快,数据肯定还得在内存当中,但是为了持久,数据还得想办法存储在硬盘上,这样的两份数据,理论上是完全相同的。Redis⽀持RDB和AOF两种持久化机制,持久化功能有效地避免因进程退出造成数据丢失问题,当下次重启时利⽤之前持久化的⽂件即可实现数据恢复
RDB(定期备份)
RDB持久化是定期把当前redis内存当中的数据⽣成快照保存到硬盘的过程,触发RDB持久化过程分为⼿动触发和⾃动触发
触发机制
⼿动触发:程序员通过redis客户端执行特定的命令,来触发快照生成,对应save和bgsave命令:
- save命令:阻塞当前Redis服务器,直到RDB过程完成为⽌,对于内存⽐较⼤的实例,会造成⻓时间阻塞,基本不采⽤
- bgsave 命令:Redis 进程执⾏ fork 操作创建⼦进程,RDB 持久化过程由⼦进程负责,完成后⾃动结束。阻塞只发⽣在 fork 阶段,⼀般时间很短,不会影响redis服务器处理其它客户端的请求和命令
Redis 内部的所有涉及 RDB 的操作都采⽤类似 bgsave 的⽅式
何时会自动触发
1.使⽤ save 配置。如 “save m n
” 表⽰ m 秒内数据集发⽣了 n 次修改,⾃动 RDB 持久化
- 在redis配置文件当中进行设置,让redis每隔多长时间/每产生多少次修改就触发
虽然此处的数值可以自由修改配置,但是因为生成一次rdb快照是有成本的,所i有不能让这个操作执行的太频繁,所以可能就会导致快照里面的数据和当前实时的数据情况可能存在偏差
2.redis进行主从复制的时候,从节点进⾏全量复制操作时,主节点会进行RDB 持久化(生成rdb快照),随后将rdb快照文件内容发送给从结点
3.执⾏ shutdown 命令关闭 Redis 时 / 正常关闭redis服务器
,也会执⾏ RDB 持久化
如果插入新的key,不手动执行
bgsave
,然后重启redis服务器会怎么样
1.如果是正常流程重启redis服务器,此时redis服务器会在退出的时候,自动触发生成rdb持久化操作
2.如果是异常重启(kill -9
或者服务器掉电),此时redis服务器来不及进行rdb持久化操作,那么此时内存当中尚未保存到快照中的数据就会随着重启而丢失
流程说明
bgsave 命令的运作流程
1.执⾏ bgsave 命令,Redis ⽗进程判断当前进是否存在其他正在执⾏的⼦进程,如 RDB/AOF ⼦进 程,如果存在 bgsave 命令直接返回
2.⽗进程执⾏ fork 创建⼦进程,fork 过程中⽗进程会阻塞,通过 info stats 命令查看 latest_fork_usec
选项,可以获取最近⼀次 fork 操作的耗时,单位为微秒
3.⽗进程 fork 完成后,bgsave 命令返回 “Background saving started” 信息并不再阻塞⽗进程,可 以继续响应其他命令
4.⼦进程创建 RDB ⽂件,根据⽗进程内存⽣成临时快照⽂件,完成后对原有⽂件进⾏原⼦替换。执 ⾏ lastsave 命令可以获取最后⼀次⽣成 RDB 的时间,对应 info 统计的 rdb_last_save_time 选 项
5.进程发送信号给⽗进程表⽰完成,⽗进程更新统计信息
RDB文件的处理
保存:RDB ⽂件保存在配置文件/etc/redis/redis.conf
指定的⽬录:
⽂件名通过 dbfilename
配置(默认 dump.rdb)指定
- 这个是rdb机制生成的镜像文件,它是二进制文件,将内存当中的数据以压缩的形式保存当这个二进制文件当中,压缩需要消耗一定的CPU在资源,但是能节省存储空间
- 后续redis服务器重新启动就会尝试加载这个rdb文件,如果发现格式错误,就可能导致加载数据失败
- 注意:rdb文件虽然不主动修改它,仍然可能会出现意外问题,比如网络传输或者其它问题可能会引起这个文件的损坏,此时redis服务器就无法启动
可以通过执⾏config set dir {newDir}
和 config set dbfilename {newFilename}
运⾏期间动态执⾏,当下次运⾏时 RDB ⽂件会保存到新⽬录
**压缩:**Redis 默认采⽤ LZF 算法对⽣成的 RDB ⽂件做压缩处理,压缩后的⽂件远远⼩于内存⼤ ⼩,默认开启,可以通过参数 config set rdbcompression {yes|no} 动态修改
虽然压缩 RDB 会消耗 CPU,但可以⼤幅降低⽂件的体积,⽅便保存到硬盘或通过⽹络发送到 从节点,因此建议开启
注意1:rdb持久化操作可以进行多次,当执行生成rdb镜像文件操作的时候,此时就会把要生成的快照数据先保存到一个临时文件当中,当这个快照生成完成之后,在删除之前的rdb文件,然后把新生成的rdb文件的名字改成刚才的dump.rdb
。但是rdb文件始终是只有一个的!!!
验证:可以通过Linux的
stat
命令查看文件的相关信息,观察文件inode编号的变化
注意2:执行flushall
操作会清空rdb文件
如果把rdb文件故意改坏了,此时会怎么样
手动的把rdb文件内容改坏,然后一定是通过kill
掉redis进程的方式,然后重新启动redis服务器,如果是通过service redis-server restart
重启,就会在redis服务器退出的时候重新生成rdb快照,就会把刚才改坏的rdb文件重新替换为正确的。
rdb文件坏了,得到的结果是不可预期的,如果 Redis 启动时加载到损坏的 RDB ⽂件会拒绝启动。这时可以使⽤ Redis 提供的 redis-check-dump
⼯具检测 RDB ⽂件并获取对应的错误报告
当redis服务器挂了的时候,可以看看redis日志了解发生了什么:/var/log/redis/
RDB 的优缺点
1)RDB 是⼀个压缩的⼆进制⽂件,代表 Redis 在某个时间点上的数据快照。⾮常适⽤于备份,全 量复制等场景。⽐如每 6 ⼩时执⾏ bgsave 备份,并把 RDB ⽂件复制到远程机器或者⽂件系统中 (如 hdfs)⽤于灾备
2)Redis 加载 RDB 恢复数据远远快于 AOF 的⽅式。
- 因为RDB使用的是二进制的方式来组织数据,直接把数据读取到内存当中,然后按照字节的个数读取出来放到结构体/对象当中,而AOF是用文本的方式组织数据,需要进行一系列的字符串切分操作
3)RDB ⽅式数据没办法做到实时持久化 / 秒级持久化。因为 bgsave 每次运⾏都要执⾏ fork 创建⼦进 程,属于重量级操作,频繁执⾏成本过⾼
4)RDB ⽂件使⽤特定⼆进制格式保存,Redis 版本演进过程中有多个 RDB 版本,兼容性可能有⻛ 险
- 老版本的redis的rbd文件,放到新版本的redis当中不一定能识别到,如果确实要又一些升级版本的需要,就可以通过程序,直接遍历旧的redis当中的所有key,将数据取出来,插入到新的redis服务器当中
RDB最大的问题,就是不能实时的持久化保存数据,在两次生成快照之间,实时的数据可能会随着重启而丢失
例如:配置文件当中save 60 10000
代表的含义就是:两次生成rdb之间的间隔,最少是60s,并且需要执行10000次操作以上才会触发。
- 假设
12:00:00
生成了rdb文件(此时硬盘的那个字的快照数据和内存当中的一样),从12:00:01
开始,redis收到了大量的key的变化请求,此时会在12:01:00
生成下一个快照文件 - 但是如果在
12:00:01
~12:01:00
之间redis服务器挂了,那么就会导致12:00:00
之后的数据丢失了,此时就需要使用AOF持久化方式来解决上述的问题
如果需要关闭自动生成快照,只需要在配置文件当中改为:save ""
AOF(实时备份)
AOF(Append Only File)持久化:以独⽴⽇志的⽅式记录每次写命令(把用户的每个操作都记录到文件当中),redis重启时,再重新执⾏ AOF ⽂件中的命令达到恢复数据的⽬的。AOF 的主要作⽤是解决了数据持久化的实时性,⽬前已经是 Redis 持久化的主流⽅式
使用AOF
开启 AOF 功能需要设置配置:appendonly yes
,默认不开启
如果开启了aof功能,那么rdb功能就不生效了,启动的时候不再去读取rdb文件的内容
AOF ⽂件名通过 appendfilename
配置(默认是 appendonly.aof
)设置。保存⽬录同 RDB 持久化⽅式⼀致,通过 dir 配置指定
AOF 的⼯作流程操作:命令写⼊(append)、⽂件同步(sync)、⽂件重写 (rewrite)、重启加载(load),AOF是一个文本文件每次进行的操作都会记录到文本文件当中,通过一些特殊符号为分隔符,来对命令的细节作出区分
命令写入
AOF 命令写⼊的内容直接是⽂本协议格式。例如 set hello world 这条命令,在 AOF 缓冲区会追加如下 ⽂本:
*3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n
此处遵守 Redis 格式协议,Redis 选择⽂本协议可能的原因:⽂本协议具备较好的兼容性;实现简单; 具备可读性
AOF工作流程
1.所有的写⼊命令会追加到 aof_buf
(缓冲区)中
2.AOF 缓冲区根据对应的策略向硬盘做同步操作
3.随着 AOF ⽂件越来越⼤,需要定期对 AOF ⽂件进⾏重写,达到压缩的⽬的
4.当 Redis 服务器启动时,可以加载 AOF ⽂件进⾏数据恢复
问:引入了AOF之后,redis既要学内存又要写硬盘,速度还能和之前一样快吗
实际上AOF机制并没有影响到redis处理请求的速度
1)因为AOF机制并非是直接让工作线程把数据写入硬盘,而是先写入一个内存当中的缓冲区,积累一部分内容之后再统一写入硬盘,就能大大降低写硬盘的次数。 写硬盘的时候,写入硬盘数据的多少对于性能影响没有多大,但是写入硬盘的次数则影响很大
2)硬盘上读写数据,顺序读写的速度是比较快的(但是还是比内存要慢),但是在硬盘上随机访问的速度是比较慢的,而AOF是每次把新的操作写入到原有文件的末尾,属于是顺序写入
注意:因为是先把数据写入到缓冲区当中,本质上还是在内存当中,如果这个时候进程挂了或者主机掉电了,此时缓冲区当中没有来得及写入硬盘的数据是会丢失的!
redis给出了一些选项,根据实际情况来决定取舍,也就是缓冲区的刷新策略
- 刷新频率越高,性能影响越大,同步数据的可靠性越高
- 刷新频率越低,性能影响越小,同步数据的可靠性越低
文件同步
Redis 提供了多种 AOF 缓冲区同步⽂件策略,由参数 appendfsync
控制
可配置项 | 说明 | 说明 |
---|---|---|
always | 命令写⼊ aof_buf 后调⽤ fsync 同步,完成后返回 | 频率最高,数据可靠性最高,性能最低 |
everysec | 命令写⼊aof_buf 后只执⾏ write 操作,不进行fsync,每秒由同步线程进行fsync | 频率低一些,数据可靠性降低,性能提高 |
no | 命令写⼊ aof_buf 后只执⾏ write 操作,由OS控制fsync频率 | 频率最低,数据可靠性最低,性能最高 |
系统调⽤
write
和fsync
说明:
1.write
操作会触发延迟写(delayed write)机制。Linux 在内核提供⻚缓冲区⽤来提供硬盘 IO 性 能。write 操作在写⼊系统缓冲区后⽴即返回。同步硬盘操作依赖于系统调度机制,例如:缓冲区 ⻚空间写满或达到特定时间周期。同步⽂件之前,如果此时系统故障宕机,缓冲区内数据将丢失
2.Fsync
针对单个⽂件操作,做强制硬盘同步,fsync
将阻塞直到数据写⼊到硬盘
3.配置为 always 时,每次写⼊都要同步 AOF ⽂件,性能很差,在⼀般的 SATA 硬盘上,只能⽀持⼤ 约⼏百 TPS 写⼊。除⾮是⾮常重要的数据,否则不建议配置
4.配置为 no
时,由于操作系统同步策略不可控,虽然提⾼了性能,但数据丢失⻛险⼤增,除⾮数据 重要程度很低,⼀般不建议配置
5.配置为 everysec
,是默认配置,也是推荐配置,兼顾了数据安全性和性能。理论上最多丢失 1 秒的 数据
重写机制
随着命令不断写⼊ AOF,⽂件会越来越⼤,会影响redis下次启动的时间(因为redis启动要读取aof文件的内容),为了解决这个问题,Redis 引⼊ AOF 重写机制压缩文件体积
AOF ⽂件重写是把 Redis 进程内的数据转化为写命令同步到新的 AOF ⽂件(因为原来的aof文件还记录了中间的过程,然而实际上redis在重新启动的时候,只关注最终结果),较⼩的 AOF ⽂件⼀⽅⾯降低了硬盘空间占⽤,还可以提升启动 Redis 时数据恢复的速度
重写后的 AOF文件为什么可以变⼩?
- 进程内已超时的数据不再写⼊⽂件
- 旧的 AOF 中的⽆效命令,例如
del
、hdel
、srem
等重写后将会删除,只需要保留数据的最终版本 - 多条写操作合并为⼀条,例如
lpush list a
、lpush list b
、lpush list c
从可以合并为lpush list a b c
剔除冗余操作,合并一些操作,减小aof文件的占用空间
重写触发机制
AOF 重写过程可以⼿动触发和⾃动触发:
-
⼿动触发:调⽤
bgrewriteaof
命令 -
⾃动触发:根据
auto-aof-rewrite-min-size
和auto-aof-rewrite-percentage
参数确定⾃动触发时机auto-aof-rewrite-min-size
:表⽰触发重写时 AOF 的最⼩⽂件⼤⼩,默认为 64MBauto-aof-rewrite-percentage
:代表当前 AOF 占⽤⼤⼩相⽐较上次重写时增加的⽐例
AOF进制重写流程
1.执⾏ AOF 重写请求
- 如果当前进程正在执⾏ AOF 重写,请求不执⾏。
- 如果当前进程正在执⾏
bgsave
操作(生成rdb快照文件),此时aof重写操作就会等待,等待bgsave
完成之后再执⾏aof重写
2.⽗进程执⾏ fork 创建⼦进程,父进程仍然负责接收请求,子进程负责针对aof文件进行重写
3.重写
- 主进程 fork 之后,继续响应其他命令。所有新来的修改操作写⼊ AOF 缓冲区并根据
appendfsync
策 略同步到硬盘,再刷新到原来的AOF文件当中,保证旧 AOF ⽂件机制正确, - ⼦进程里的内存数据是父进程fork之前的状态,fork之后新来的请求,对内存造成的修改,子进程是不知道的,⽗进程中需要将 fork 之后这段时间的修改操作写⼊ AOF 重写缓冲区(
aof_rewrite_buf
)中,专门用户存放fork之后收到的数据
4.⼦进程根据内存快照,将命令合并到新的 AOF ⽂件中
- 注意:重写的时候不关心原来的aof文件,只是关心内存当中最终的数据状态,子进程只需要把内存当中的数据获取出来,以AOF的格式写入到一个新的AOF文件当中(内存当中的数据的状态就相当于是把AOF文件结果整理后的样子)
5.⼦进程完成重写
- 新⽂件写⼊后,⼦进程发送信号给⽗进程
- 父进程把 AOF重写缓冲区内临时保存的命令追加到新 AOF ⽂件中
- ⽤新 AOF ⽂件替换⽼ AOF ⽂件
注意:父进程fork之后,就已经让子进程写新的aof文件了,并且随着时间推移,子进程很快就写完了新的文件,要让新的aof文件代替旧的,但是父进程此时还在继续写这个即将消亡的进的aof文件,此时是否还有意义?
- 不能不写!要考虑极端情况,假设在重写的过程当中,重写到一半服务器挂了,此时子进程内存的数据就会丢失,新的aof文件内容还不完整,如果父进程不坚持写aof文件,此时重启服务器的时候就没办法保证数据的完整性了
混合持久化
AOF本来是按照文本的方式写入文件的,但是文本的方式写入文件,后续加载的成本比较高,所以redis就引入了混合持久化的方式,结合了rdb
和aof
的特点:
- 按照aof的方式,每一个请求/操作都记录到文件当中,在触发aof重写之后,就会把当前内存的状态按照rdb的二进制格式写入到新的aof文件当中,后续再进行的操作,仍然是按照aof文本的形式追加到文件的后面
在配置文件当中,这个选项为yes
表示开启混合持久
注意:如果当redis上同时存在aof文件和rdb快照的时候,以aof文件为主,此时rdb快照就直接被忽略了!
- 这是因为AOF中包含的数据比RDB更全
启动时数据恢复
当 Redis 启动时,会根据 RDB 和 AOF ⽂件的内容,进⾏数据恢复
Redis 根据持久化⽂件进⾏数据恢复
总结
1.RDB 视为内存的快照,产⽣的内容更为紧凑,占⽤空间较⼩,恢复时速度更快。但产⽣ RDB 的开 销较⼤,不适合进⾏实时持久化,⼀般⽤于冷备和主从复制
2.AOF 视为对修改命令保存,在恢复时需要重放命令。并且有重写机制来定期压缩 AOF ⽂件
3.RDB 和 AOF 都使⽤ fork 创建⼦进程,利⽤子进程拥有⽗进程内存快照的特点进⾏持久化, 尽可能不影响主进程继续处理后续命令