我们都是架构师!
关注架构师(JiaGouX),添加“星标”
获取每天技术干货,一起成为牛逼架构师
技术群请加若飞:1321113940 进架构师群
投稿、合作、版权等邮箱:admin@137x.com
Redis将数据存储在内存中,宕机或重启都会使内存数据全部丢失, Redis的持久化机制用来保证数据不会因为故障而丢失。
Redis提供两种持久化方式,一种为内存快照方式,生成rdb文件
,rdb是某一时间点内存数据的全量备份,文件内容是存储结构非常紧凑的二进制序列化形式;另一种是AOF日志
备份方式,日志保存的是基于数据的连续增量备份,日志文件内容是服务端执行的每一条指令的记录文本。
两种方式各有优略,下面的章节会详细介绍两种持久化机制的实现原理和使用技巧。
1.内存快照
Copy On Write
说起。COW
,又叫写时复制
,是操作系统为优化使用子进程采取的一种策略。glibc
的函数fork
,熟悉Linux的人都知道:Linux操作系统的进程都是通过init
进程(pid=1)或者其子进程fork(vfork)
出来的。fork()
会产生一个与父进程完全相同的子进程,有两次返回:将子进程的pid
返回给父进程,0返回给子进程。如果小于0,说明创建进程失败!下面是一个C语言的例子:#include <unistd.h>
#include <stdio.h>
int main() {
pid_t pid;
int count = 0;
pid = fork();
if (pid < 0)
printf("error in fork!");
else if (pid == 0) {
printf("child process, process id is %d/n", getpid());
count++;
} else {
printf("parent process, process id is %d/n", getpid());
count++;
}
printf("count total: %d/n", count);
return 0;
}
parent process, process id is 23049
count total: 1
child process, process id is 23050
count total: 1
当前进程调用fork(),会创建一个跟当前进程完全相同的子进程(除了pid),
所以子进程同样是会执行fork()之后的代码。
父子进程的count变量都是1,
说明父子进程使用了各自独有的栈区(count变量存放在栈区)。
我们先来看一下CPU执行程序的流程。
MMU
(内存管理单元)将虚拟地址转换为物理地址。因为只有程序的一部分加入到内存中(按页加载),所以会出现所寻找的地址不在内存中的情况(CPU产生缺页异常),如果在内存不足的情况下,就会通过页面调度算法来将内存中的页面置换出来,然后将在外存中的页面加入到内存中,使程序继续正常运行。虚拟地址
和物理地址
,虚拟地址和物理地址通过MMU
保持映射关系。一个进程是一个主体,它有灵魂有身体,灵魂就是其虚拟地址空间(有相应的数据结构表示),包括:正文段、数据段、堆、栈这四个部分;相应的,内核会为这四个部分分配各自的物理块(进程的身体)即:正文段块、数据段块、堆块、栈块。fork
进程时写时复制
的过程:fork
产生子进程时,操作系统只为新生成的子进程创建虚拟空间结构,它们复制于父进程的虚拟空间结构,但是不为这些段分配物理内存,它们共享父进程的物理空间,当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间,这就是写时复制。glibc
的函数fork
产生一个子进程,快照持久化完全交给子进程来处理,父进程继续处理客户端请求。bgsave
命令来触发快照保存操作,Redis调用bgsaveCommand
函数,该函数fork一个子进程,子进程刚刚产生时,它和父进程共享内存里面的代码段和数据段。这时将父子进程比喻成一个连体婴儿非常恰当,这是Linux操作系统的机制,为了节约内存资源,尽可能的将内存资源共享起来。在进程分离的一瞬间,内存的增长几乎没有明显的变化。子进程因为没有数据的变化,它能感知到的内存数据在进程产生的一瞬间就凝固了,再也不会改变。父进程可以继续处理客户端请求,当子进程推出后,父进程调用相关函数处理子进程的善后工作。2.AOF持久化
重放
。write
函数执行,但是write之后的数据只是写到了内核的一个缓冲区中,然后内核还需要异步的调用fsync
函数异步的将数据刷回磁盘。fsync函数是一个阻塞并且缓慢的操作,如果机器突然宕机,AOF日志内容可能还没来的及完全刷到磁盘,这时候就会丢失数据。Redis通过appendfsync
配置控制执行fsync的频次,具体有如下三种模式:aof_buf
中,aof_buf是个全局的SDS类型的缓冲区。AOF重写
。我们考虑一下AOF和RDB文件的加载过程:RDB只需要把相应的数据加载都内存并生成相应的数据结构就可以了,有些结构如intset、ziplist,保存的时候直接按照字符串保存,加载速度非常快。但是AOF日志文件的加载需要创建一个伪客户端,顺序执行一遍命令,根据Redis作者做的测试,RDB在10~20秒能加载1GB的文件,AOF的速度是RDB的一半(如果做了AOF重写会加快)
bgrewriteaof
指令对AOF日志进行瘦身过程如下:bgrewriteaofCommand
命令创建管道,创建管道对作用是AOF重写过程中批量接收服务端累积的命令;创建完管道以后,fork进程,子进程调用rewriteAppendOnlyFile
执行AOF重写操作;父进程记录一些统计指标后继续进入主循环处理客户端请求,待子进程结束以后,处理一些善后工作。瘦身工作就是子进程对所有数据库中的键各自生成一条相应的执行命令,最后将重写开始后父进程继续执行的命令进行回放,生成一个新的AOF文件。127.0.0.1:6379> lpush list guo zhao ran
(integer) 3
127.0.0.1:6379> lpop list
"ran"
127.0.0.1:6379> lpop list
"zhao"
127.0.0.1:6379> lpush list zhao
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "zhao"
2) "guo"
lpush list zhao guo
。日志瘦身既可以减小文件大小,又可以提高加载速度。3.混合持久化
RDB和AOF实现持久化的方式各有优缺点,我们来简单总结一下:
RDB保存的是一个时间的快照,如果发生故障,丢失的是最后一次RDB执行时间点到故障发生的时间间隔之内产生的数据。如果Redis数据量很大,QPS很高,执行一次RDB需要的时间会相应增加,发生故障时丢失的数据也会增多。
AOF保存的是一条条的命令,理论上可以做到发生故障时只丢失一条命令。但是由于操作系统中执行写文件操作代价很大,Redis提供了配置参数,可以对完全性和性能取折中,设置不同的配置策略。但是重放AOF日志相对于使用RDB来说还是慢很多。
Redis4.0为了解决这个问题,带来了一个新的持久化选项——混合持久化。混合持久化是指进行AOF重写时子进程将当前时间点的数据快照保存为RDB文件格式,而后将父进程累积命令保存为AOF格式,最终生成的格式如下图所示:
4. Redis持久化相关配置
配置项 | 可选值 | 默认值 | 作用 |
save 300 10 save 60 10000 | save 900 1:在900秒内有1个key被改动,自动保存到dump.rdb文件中 save 300 10:在300秒内有10个key被改动,自动保存到dump.rdb文件中 save 60 10000:在60秒内有10000个key被改动,自动保存到dump.rdb文件中 以上3中条件任意一种被满足就会触发保存 | ||
5.总结
Redis是内存数据库,机器故障或重启之后,内存数据全部丢失,所以需要持久化来保证数据安全。
Redis提供了快照RDB和AOF日志同步两种方式进行数据持久化,快照RDB实现原理是Copy On Write,优点是机器加载速度快,缺点是执行缓慢,QPS高的情况下会丢失大量数据;AOF则是将命令一条条的有序存放到日志文件中,优点是尽可能少的丢失数据,缺点是日志文件重放缓慢,日志文件会很大,可以通过重写AOF日志来实现,另外提供了这种的配置方案异步执行fsync操作。
生产环境中推荐使用混合持久化,这种方式综合了RDB和AOF两种方式的优点。文章最后总结了一下Redis持久化配置项。本文是笔者学习Redis持久化的总结,希望能对读者有所帮助。
如喜欢本文,请点击右上角,把文章分享到朋友圈
如有想了解学习的技术点,请留言给若飞安排分享
因公众号更改推送规则,请点“在看”并加“星标”第一时间获取精彩技术分享
·END·
相关阅读:
作者:绘你一世倾城
来源:juejin.cn/post/6844904055098048519
版权申明:内容来源网络,仅供分享学习,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢!
我们都是架构师!
关注架构师(JiaGouX),添加“星标”
获取每天技术干货,一起成为牛逼架构师
技术群请加若飞:1321113940 进架构师群
投稿、合作、版权等邮箱:admin@137x.com