文章目录
Redis持久化
在这之前要提及到Mysql的事务的四个比较核心的特性
原子性 一致性 持久性 隔离性
这里mysql的持久性就是把数据存储在硬盘上,而redis数据是存在内存里的,要想做到持久,就需要让redis把数据存储到硬盘上
Redis相比于Mysql这样的关系型数据库,最明显的特点/优势就是 快,所以为了保证速度快,数据肯定还是得在内存中,但是为了持久,数据还得办法存储在硬盘上
Redis决定 内存中也存数据,硬盘上也存数据,这样的两份数据,理论上是完全相同的
- 当要插入一个新的数据的时候,就需要把这个数据同时写入到内存和硬盘(说是两边都写,但是实际上具体怎么写硬盘还有不同的策略,可以保证整体的效率还是足够的高)
- 当查询某个数据的时候,直接从内存读取
- 硬盘的数据只是在redsi重启的时候,用来恢复内存中的数据
- 代价就是消耗了更多的空间,同一份数据,存储了两遍(但是毕竟硬盘比较便宜,这样的开销并不会带来太多的成本)
Redis的两种持久化的策略
- RDB => Redis DataBase 定期备份:每隔一段时间,进行一次备份
- AOF => Append Only File 实时备份:只要有数据变动,立即备份
定期备份:RDB
RDB 持久化是把当前进程数据生成快照 保存到硬盘的过程,触发 RDB 持久化过程分为手动触发 和自动触发。
触发机制
- 手动触发:通过redis客户端执行特定的命令,来执行快照生成 (手动触发分别对应 save 和 bgsave 命令)
• save 命令:阻塞当前 Redis 服务器 ,直到 RDB 过程完成为止,对于内存比较大的实例造成长时间阻塞,基本不采用 。
• bgsave 命令:Redis 进程执行 fork 操作创建子进程,RDB 持久化过程由子进程负责,完成后自动结束。阻塞只发生在 fork 阶段,⼀般时间很短。
Redis 内部的所有涉及 RDB 的操作都采用类似 bgsave 的方式。
- 执行 bgsave 命令,Redis 父进程判断当前进是否存在其他正在执行的子进程,如 RDB/AOF 子进
程,如果存在 bgsave 命令直接返回。- 父进程执行 fork 创建子进程,fork 过程中父进程会阻塞,通过 info stats 命令查看latest_fork_usec 选项,可以获取最近⼀次 fork 操作的耗时,单位为微秒。
- 父进程 fork 完成后,bgsave 命令返回 "Background saving started" 信息并不再阻塞父进程,可以继续响应其他命令。
- 子进程创建 RDB 文件,根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换。执行 lastsave 命令可以获取最后⼀次生成 RDB 的时间,对应 info 统计的 rdb_last_save_time 选项。
- 进程发送信号给父进程表⽰完成,父进程更新统计信息。
- 自动触发:在Redis配置文件中,设置一下,让Redis每隔多长时间/每产生多少次修改 就触发
Redis生成的RDB文件,是存放在redis的工作目录中的,也是在redis的配置文件中进行设置的
这里的dump.rdb文件,就是rdb机制生成的镜像文件,redis服务器默认就是开启了rdb的,这个镜像文件,是一个二进制的文件,把内存中的数据,以压缩的形式,保存到这个二进制文件中
后续redis服务器重新启动,就会尝试加载这个rdb文件,如果发现格式有错误,就可能会加载数据失败
rdb文件,虽然我们没有主动去动他,但是也有可能会出现一些意外问题,一旦通过一些操作(比如网络传输)引起这个文件被破坏,此时redis服务器就无法启动
- redis提供了rdb文件的检查工具 redis-check-rdb
查看一下RDB文件里面是什么内容
里面都是二进制内容,当我们对redis文件中的数据进行增删查改时,这里的RDB文件并不会立即更新
rdb的触发时机:
- 手动(save,bgsave)
- 自动(配置文件中,进行设置)
save //seconds之内至少存在changes次key的修改
虽然此处的这些数值,都可以自由修改配置,但是此处修改上述数据的时候,要有一个基本规则
生成一次rdb快照,这个成本是一个比较高的成本,不能让这个操作执行的太频繁
正因为rdb不能生成的太频繁,这就导致,快照里的数据,和当前实时的数据情况可能会存在偏差
save 60 10000 (两次生成rdb之间的间隔,至少得是60s)
假设当前
12:00:00生成了rdb(硬盘上的快照数据和内存中一致)
12:00:01开始,redis收到了大量的key的变化请求
12:01:00生成下一个快照文件
在上述过程之间,redis服务器挂了,此时就会导致,12:00:00之后的这些数据,就丢了(数新的据只是在内存里,还没存到rdb)
手动执行save&bgsave保存测试
由于这里执行的数据比较少,所以瞬间就完成了,立即查看应该是有结果的,如果数据比较多,执行bgsave就需要消耗一定的时间,立即查看不一定就是生成完毕了
保存后查看rdb文件可以看到已经发生了修改
持久化验证,可以看到,通过上述操作,redis服务器在重新启动的时候,加载了rdb文件的内容,恢复了内存中之前的状态
redis生成快照操作,不仅仅是手动执行命令才触发,也可以自动触发~
- 通过配置文件中save执行M时间内,修改N次...
- 通过shotdown命令(redis里的一个命令)关闭redis服务器,也会触发
- redis进行主从复制的时候,主节点也会自动生成rdb快照,然后把rdb快照文件内容传输给从节点
不手动执行bgsave测试
插入新的key,不手动执行bgsave,重新启动redis服务器
如果是通过正常流程重新启动redis服务器,此时redis服务器会在退出的时候,自动触发生成rdb操作,但是如果是异常重启(比如kill -9或者服务器 掉电)此时redis服务器来不及生成rdb,内存中尚未保存在快照中的数据,就会随着重启而丢失了
bgsave操作流程测试
bgsave操作流程是创建子进程,子进程完成持久化操作,持久化会把数据写入到新的文件中,然后使用新的文件替换旧的文件
而子进程完成持久化的速度太快了(数据少),难以观察到子进程,但是我们可以通过文件的inode来验证bgsave操作流程
在redis客户端执行bgsave后
可以看到Inode从18685044变为了18685080
通过配置,自动生成rdb快照
这里执行flashall也会自动生成rdb快照
将时间设置为60秒 设置两次保存rdb
如果rdb文件故意改坏了,会怎样
如果改的是结尾的位置,基本没什么影响,如果改的hi中间的位置,那么redis服务器就会挂掉
- redis提供了rdb文件的检查工具,可以先通过检查工具,检查一下rdb文件格式是否符合要求
运行的时候,加入rdb文件作为命令行参数,此时就是以检查工具的方式来运行,不会真的启动redis服务器
redis-check-rdb dump.rdb
RDB的优缺点
-
RDB 是⼀个紧凑压缩的二进制文件,代表 Redis 在某个时间点上的数据快照。非常适用于备份,全量复制等场景。比如每 6 小时执行 bgsave 备份,并把 RDB 文件复制到远程机器或者文件系统中 (如 hdfs)用于灾备。
-
Redis 加载 RDB 恢复数据远远快于 AOF 的方式。
-
RDB 方式数据没办法做到实时持久化 / 秒级持久化。因为 bgsave 每次运行都要执行 fork 创建子进程,属于重量级操作,频繁执行成本过高。
-
RDB 文件使用特定二进制格式保存,Redis 版本演进过程中有多个 RDB 版本,兼容性可能有风险。
实时备份:AOF
Append Only File
类似于mysql的binlog,会把用户的每个操作,都记录到文件中,当redis重新启动的时候,就会读取这个aof文件中的内容,用来恢复数据
将这个appendonly的属性设置为 yes后,重启redis服务器,这里我执行两次set命令后查看 /var/lib/redis目录下的文件,发现多了一个文件appendonly.aof
++由文件内容可以看到,AOF是一个文本文件,每次进行的操作都会被录到文本文件中,通过一些特殊的符号作为分隔符,来对命令的细节进行区分++
AOF是否会影响到redis的性能
redis虽然是一个单线程的服务器,但是速度很快,引入AOF后,又要写内存,又要写硬盘,还能和之前一样快了吗?
实际上,是没有影响的
- AOF机制并非是直接让工作线程把数据直接写入硬盘,而是++先写入一个内存的缓冲区++,积累一波后,再统一写入硬盘(假设由100个请求,100个请求一次写入硬盘,比分100次写入一个请求要快得多,写硬盘的时候,写入硬盘的数据的多少,对于性能影响没有很大,但是写入硬盘的次数则影响很大)
- 硬盘上读写数据,顺序读写的速度是比较快的(还是比内存要慢得多)随机访问则速度是比较慢的,而AOF是每次把新的操作写到原有文件的末尾,属于 顺序写入
如果把数据写入缓冲区里,本质还是在内存中,万一这个时候,进程突然挂了,或者主机掉电了,咋办?是不是缓冲区中的数据就丢了?? 答案是的,缓冲区没有来得及写入硬盘的数据会丢的
redis给出了一些选项,让程序猿,根据实际情况决定怎么取舍,缓冲区的刷新策略
刷新频越越高,性能影响就越大,同时数据的可靠性就越高
刷新频率越低,性能影响就越小,数据的可靠性就越低~~
可配置值 | 说明 | 取舍情况 |
---|---|---|
always | 命令写入aof_buf 后调用 fsync 同步,完成后返回 | 频率最高,数据可靠性最高,性能最低 |
everysec | 命令写入aof_buf 后只执行 write 操作,不进行 fsync。每秒由同步线程进行 fsync。 | 频率低一些,数据可靠性也会降低,性能会提高 |
no | 命令写入 aof_buf 后只执行 write 操作,由 OS 控制 fsync 频率。 | 频率最低,数据可靠性也是最低的,性能是最高的 |
通过配置文件可以看到redis默认是everysec策略
AOF的重写机制
AOF文件持续增长,体积越来越大,会影响到redis下次启动的启动时间,redis启动的时候要读取aof文件的内容
redis存在一个机制,能够对aof文件进行整理操作,这个整理就是能够提出其中的冗余操作,并且合并一些操作,达到给aof文件瘦身这样的效果,例如lpush 111 ,lpush 222 合并为 lpush 111 222
重写触发方式
- 手动触发:调用bgrewriteaof命令
- 自动触发:根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定自动触发时机。
- auto-aof-rewrite-min-size:表示触发重写时AOF的最小文件大小,默认为64MB
- auto-aof-rewrite-percentage:代表当前AOF占用大小相比较上次重写时增加的比例
AOF重写的流程
创建子进程fork,父进程仍然负责接收请求,子进程负责针对aof文件进行重写
注意: 重写的时候,不关心aof文件中原来都有啥,只是关心内存中的最终的数据状态!
子进程只需要把内存中当前的数据,获取出来,以AOF的格式写入到一个新的AOF文件中
此处子进程写数据的额过程,非常类似于RDB生成一个镜像快照,只不过RDB这里是按照二进制的方式来生成的,AOF重写则是按照AOF这里的要求的文本格式来生成的
在创建子进程的一瞬间,子进程就继承了当前父进程的内存状态,因此子进程里的内存数据是父进程fork之前的状态,fork之后,新来的请求,对内存造成的修改,是子进程不知道的
此时父进程这里又准备额一个aof_rewrite_buf缓冲区,专门放fork之后收到的数据,子进程这边,把aof数据写完之后,会通过信号通知一下父进程,父进程再把aof_rewrite_buf缓冲区中的内容也谢入到新AOF文件里
就可以用新的AOF文件代替旧的AOF文件了
如果,在执行bgrewriteaof的时候,当前redis已经正在进行aof重写了,会咋样呢?? ------------此时不会再次执行aof重写,直接就返回了
如果,在执行bgrewriteaof的时候,当前redis已经正在进行生成rdb文件快照,会咋样呢?? ------此时aof重写操作就会等待,等待rdb快照生成完毕之后,再进行执行aof重写
rdb对于fork之后的新数据,就置之不理了,aof则对于fork之后的新数据,采取了aof_rewrite_buf缓冲区的方式来处理
父进程fork完毕之后,就已经让子进程写心得aof文件了,并且随着时间的推移,子进程很快就写完了新的文件 ,要让新的aof文件代替旧的,父进程此时还在继续写这个即将消亡的旧的aof文件是否还有意义??
这里就要考虑到一种极端的情况
假设重写过程中,重写了一半,服务器挂了,子进程内存的数据就会丢失,新的aof文件内容还不完整,所以如果父进程不坚持写旧的aof文件,重启就没法保证数据的完整性
打开aof文件可以看到所有的操作都被记录了下来
查看一下执行bgrewriteaof之前的aof文件信息
执行一下bgrewriteaof再查看aof文件信息
查看aof文件内容会发现已经变了, 并且会变成二进制
为什么会变成二进制呢?
AOF本来是按照文本的方式来写入文件的,但是文本的写入方式,后续的加载成本是很高的,redis就引入了"混合持久化的方式",结合了rdb和aof的特点。按照aof的方式每一个请求/操作,都录入文件,在触发aof重写之后,就会把当前内存的状态按照rdb的二进制格式写入到新的aof文件中,后续再进行操作,仍然是按照aof文本的方式追加到文件后面的
在配置文件中,可以选择是否开启混合持久化
当redis上同时存在aof文件和rdb快照的时候,此时以谁为主?以aof为主!!rdb直接被忽略了(因为AOF中包含的数据比RDB更全)
总结
-
Redis 提供了两种持久化方案:RDB 和 AOF。
-
RDB 视为内存的快照,产生的内容更为紧凑,占用空间较小,恢复时速度更快。但产生 RDB 的开
销较⼤,不适合进行实时持久化,⼀般用于冷备和主从复制。
-
AOF 视为对修改命令保存,在恢复时需要重放命令。并且有重写机制来定期压缩 AOF 文件。
-
RDB 和 AOF 都使用 fork 创建⼦进程,利用 Linux ⼦进程拥有⽗进程内存快照的特点进行持久化,
尽可能不影响主进程继续处理后续命令。