深度剖析 Redis 持久化机制(redis持久化包括)
mhr18 2024-10-23 11:35 20 浏览 0 评论
Redis将数据存储在内存中,宕机或重启都会使内存数据全部丢失, Redis的持久化机制用来保证数据不会因为故障而丢失。
Redis提供两种持久化方式,一种为内存快照方式,生成rdb文件,rdb是某一时间点内存数据的全量备份,文件内容是存储结构非常紧凑的二进制序列化形式;另一种是AOF日志备份方式,日志保存的是基于数据的连续增量备份,日志文件内容是服务端执行的每一条指令的记录文本。
两种方式各有优略,下面的章节会详细介绍两种持久化机制的实现原理和使用技巧。
1.内存快照
Redis进行快照数据持久化时,为了不阻塞线上业务,要能够响应客户端请求。快照持久化的工作是将一个时间点内存数据序列化后同步到磁盘rdb文件。备份数据如何在内存中瞬间凝固,不再改变?文件IO操做怎样才能不拖累服务端对客户端的正常响应?这一切都要从Copy On Write说起。
1.1 快照原理——Copy On Write
Copy On Write简写为COW,又叫写时复制,是操作系统为优化使用子进程采取的一种策略。
类Unix系统创建进程的主要方式是调用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执行程序的流程。
CPU在加载执行程序时,首先按照虚拟地址来寻址,然后通过MMU(内存管理单元)将虚拟地址转换为物理地址。因为只有程序的一部分加入到内存中(按页加载),所以会出现所寻找的地址不在内存中的情况(CPU产生缺页异常),如果在内存不足的情况下,就会通过页面调度算法来将内存中的页面置换出来,然后将在外存中的页面加入到内存中,使程序继续正常运行。
Linux操作系统的每一个进程,都会分配有虚拟地址和物理地址,虚拟地址和物理地址通过MMU保持映射关系。一个进程是一个主体,它有灵魂有身体,灵魂就是其虚拟地址空间(有相应的数据结构表示),包括:正文段、数据段、堆、栈这四个部分;相应的,内核会为这四个部分分配各自的物理块(进程的身体)即:正文段块、数据段块、堆块、栈块。
我们再来看一下fork进程时写时复制的过程:
牛逼啊!接私活必备的 N 个开源项目!赶快收藏
在fork产生子进程时,操作系统只为新生成的子进程创建虚拟空间结构,它们复制于父进程的虚拟空间结构,但是不为这些段分配物理内存,它们共享父进程的物理空间,当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间,这就是写时复制。
1.2 快照执行流程
Redis在持久化时会调用glibc的函数fork产生一个子进程,快照持久化完全交给子进程来处理,父进程继续处理客户端请求。
可以通过在Redis客户端输入bgsave命令来触发快照保存操作,Redis调用bgsaveCommand函数,该函数fork一个子进程,子进程刚刚产生时,它和父进程共享内存里面的代码段和数据段。这时将父子进程比喻成一个连体婴儿非常恰当,这是Linux操作系统的机制,为了节约内存资源,尽可能的将内存资源共享起来。在进程分离的一瞬间,内存的增长几乎没有明显的变化。子进程因为没有数据的变化,它能感知到的内存数据在进程产生的一瞬间就凝固了,再也不会改变。父进程可以继续处理客户端请求,当子进程推出后,父进程调用相关函数处理子进程的善后工作。
2.AOF持久化
AOF日志存储的Redis服务器的顺序指令序列,只记录对内存进行修改的指令记录。有了AOF文件,就可以通过一个空的Redis实例顺序执行所有的指令来恢复Redis当前实例的内存数据结构的状态,这个过程叫做重放。
2.1 AOF日志文件写入
AOF日志以文件的形式存在,写文件通过操作系统提供的write函数执行,但是write之后的数据只是写到了内核的一个缓冲区中,然后内核还需要异步的调用fsync函数异步的将数据刷回磁盘。fsync函数是一个阻塞并且缓慢的操作,如果机器突然宕机,AOF日志内容可能还没来的及完全刷到磁盘,这时候就会丢失数据。Redis通过appendfsync配置控制执行fsync的频次,具体有如下三种模式:
- no: 永远不调用fsync,让操作系统决定何时同步磁盘,这样做很不安全,但是Redis的性能最高。
- always: 每执行一次写入操作就执行一次fsync,虽然数据安全性高,会导致执行非常缓慢。
- everysec: 每隔1s执行一次fsync,这个1s的周期是可以配置的,这是数据安全性和性能之间的折中方案,在保证高性能的同时,尽量使数据少丢失。推荐在生产环境中使用。
2.2 AOF执行流程
Redis收到客户端的指令以后,首先进行参数校验、逻辑处理,如果没有问题,会判断是否开启AOF,如果开启,则会将每条命令执行完毕后同步写入aof_buf中,aof_buf是个全局的SDS类型的缓冲区。
每一条命令的执行都会调用call函数,注意:Redis服务端是先执行指令再将命令写入aof_buf。
2.3 日志瘦身——AOF重写
Redis服务端在长期运行过程中,AOF日志会越来越长,如果实例宕机或者重启,重放整个AOF日志会非常耗时,导致Redis长时间无法对外提供服务,所以需要对AOF日志进行瘦身,即:AOF重写。
我们考虑一下AOF和RDB文件的加载过程:RDB只需要把相应的数据加载都内存并生成相应的数据结构就可以了,有些结构如intset、ziplist,保存的时候直接按照字符串保存,加载速度非常快。但是AOF日志文件的加载需要创建一个伪客户端,顺序执行一遍命令,根据Redis作者做的测试,RDB在10~20秒能加载1GB的文件,AOF的速度是RDB的一半(如果做了AOF重写会加快)
通过Redis客户端bgrewriteaof指令对AOF日志进行瘦身过程如下:
Redis服务端调用bgrewriteaofCommand命令创建管道,创建管道对作用是AOF重写过程中批量接收服务端累积的命令;创建完管道以后,fork进程,子进程调用rewriteAppendOnlyFile执行AOF重写操作;父进程记录一些统计指标后继续进入主循环处理客户端请求,待子进程结束以后,处理一些善后工作。瘦身工作就是子进程对所有数据库中的键各自生成一条相应的执行命令,最后将重写开始后父进程继续执行的命令进行回放,生成一个新的AOF文件。另外,搜索公众号Java架构师技术后台回复“面试题”,获取一份惊喜礼包。
例如执行了下面的命令:
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
AOF文件会保存对list操作的4条命令,但是list现在内存中的元素是这样的:
127.0.0.1:6379> lrange list 0 -1
1) "zhao"
2) "guo"
AOF重写以后就日志文件内容直接就变为了lpush list zhao guo。日志瘦身既可以减小文件大小,又可以提高加载速度。
3.混合持久化
RDB和AOF实现持久化的方式各有优缺点,我们来简单总结一下:
RDB保存的是一个时间的快照,如果发生故障,丢失的是最后一次RDB执行时间点到故障发生的时间间隔之内产生的数据。如果Redis数据量很大,QPS很高,执行一次RDB需要的时间会相应增加,发生故障时丢失的数据也会增多。
AOF保存的是一条条的命令,理论上可以做到发生故障时只丢失一条命令。但是由于操作系统中执行写文件操作代价很大,Redis提供了配置参数,可以对完全性和性能取折中,设置不同的配置策略。但是重放AOF日志相对于使用RDB来说还是慢很多。
Redis4.0为了解决这个问题,带来了一个新的持久化选项——混合持久化。混合持久化是指进行AOF重写时子进程将当前时间点的数据快照保存为RDB文件格式,而后将父进程累积命令保存为AOF格式,最终生成的格式如下图所示:
将RDB文件内容和增量AOF日志文件存在一起,这里的AOF日志不再是全量日志,通常这部分AOF日志很小。于是在Redis重启的时候,可以先加载rdb内容,然后再重放增量AOF日志,就可以完全替代之前的AOF全量文件重放,重启效率会得到大幅度提升。
4. Redis持久化相关配置
下面总结一下Redis4.0版持久化相关的配置及其含义。
配置项 | 可选值 | 默认值 | 作用 |
save | save <seconds> <changes> | save 900 1 | save "":禁用快照备份,默认关闭 |
stop-writes-on-bgsave-error | yes/no | yes | 开启该参数后,如果开启了RDB快照(即配置了save指令),并且最近一次快照执行失败,则Redis将停止接收写相关的请求 |
rdbcompression | yes/no | yes | 执行rdb的时候是否将string类型的数据压缩 |
rdbchecksum | yes/no | yes | 是否开启rdb文件内容的校验 |
dbfilename | 文件名称 | dump.rdb | rdb文件名称 |
dir | 文件路径 | ./ | RDB和AOF文件存放路径 |
appendonly | yes/no | no | 是否开启AOF功能 |
appendfilename | 文件名称 | appendonly.aof | AOF文件名称 |
appendfsync | always/everysec/no | everysec | fsync执行频次,上边有说到 |
no-appendfsync-on-rewrite | yes/no | no | 开启该参数后,如果后台正在执行一次rdb快照或者aof重写,则主进程不再进行fsync操作,即使将appendsync配置成always或者everysec |
auto-aof-rewrite-percentage | 百分比 | 100 | 和auto-aof-rewrite-min-size配和使用,下面会讲解 |
auto-aof-rewrite-min-size | 文件大小 | 64M | 当AOF文件大于64M时,并且AOF文件当前大小比基准大小增长了100%时会触发一次AOF重写。 |
aof-load-truncated | yes/no | yes | AOF以追加日志的方式生成,当服务端发生故障时会有命令不完整的情况。开启该参数后,在这种情况下,AOF会截断尾部不完整的命令继续加载,并且在日志中给出提示。 |
aof-use-rdb-preamble | yes/no | yes | 是否开启混合持久化 |
aof-rewrite-incremental-fsync | yes/no | yes | 开启该参数后,AOF重写时每产生32MB数据执行一次fsync |
5.总结
Redis是内存数据库,机器故障或重启之后,内存数据全部丢失,所以需要持久化来保证数据安全。
Redis提供了快照RDB和AOF日志同步两种方式进行数据持久化,快照RDB实现原理是Copy On Write,优点是机器加载速度快,缺点是执行缓慢,QPS高的情况下会丢失大量数据;AOF则是将命令一条条的有序存放到日志文件中,优点是尽可能少的丢失数据,缺点是日志文件重放缓慢,日志文件会很大,可以通过重写AOF日志来实现,另外提供了这种的配置方案异步执行fsync操作。
生产环境中推荐使用混合持久化,这种方式综合了RDB和AOF两种方式的优点。文章最后总结了一下Redis持久化配置项。本文是笔者学习Redis持久化的总结,希望能对读者有所帮助。
你还有什么想要补充的吗?
PS:欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,欢迎转发分享给更多人。
版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢!欢迎加入后端架构师交流群,在后台回复“学习”即可。最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。在这里,我为大家准备了一份2021年最新最全BAT等大厂Java面试经验总结。
相关推荐
- Redis合集-使用benchmark性能测试
-
采用开源Redis的redis-benchmark工具进行压测,它是Redis官方的性能测试工具,可以有效地测试Redis服务的性能。本次测试使用Redis官方最新的代码进行编译,详情请参见Redis...
- Java简历总被已读不回?面试挂到怀疑人生?这几点你可能真没做好
-
最近看了几十份简历,发现大部分人不是技术差,而是不会“卖自己”——一、简历死穴:你写的不是经验,是岗位说明书!反面教材:ד使用SpringBoot开发项目”ד负责用户模块功能实现”救命写法:...
- redission YYDS(redission官网)
-
每天分享一个架构知识Redission是一个基于Redis的分布式Java锁框架,它提供了各种锁实现,包括可重入锁、公平锁、读写锁等。使用Redission可以方便地实现分布式锁。red...
- 从数据库行锁到分布式事务:电商库存防超卖的九重劫难与破局之道
-
2023年6月18日我们维护的电商平台在零点刚过3秒就遭遇了严重事故。监控大屏显示某爆款手机SKU_IPHONE13_PRO_MAX在库存仅剩500台时,订单系统却产生了1200笔有效订单。事故复盘发...
- SpringBoot系列——实战11:接口幂等性的形而上思...
-
欢迎关注、点赞、收藏。幂等性不仅是一种技术需求,更是数字文明对确定性追求的体现。在充满不确定性的网络世界中,它为我们建立起可依赖的存在秩序,这或许正是技术哲学最深刻的价值所在。幂等性的本质困境在支付系...
- 如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享
-
如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享在高流量场景下。首先,我需要回忆一下常见的优化策略,比如负载均衡、缓存、数据库优化、微服务拆分这些。不过,可能还需要考虑用户的具体情况,比...
- Java面试题: 项目开发中的有哪些成长?该如何回答
-
在Java面试中,当被问到“项目中的成长点”时,面试官不仅想了解你的技术能力,更希望看到你的问题解决能力、学习迭代意识以及对项目的深度思考。以下是回答的策略和示例,帮助你清晰、有说服力地展示成长点:一...
- 互联网大厂后端必看!Spring Boot 如何实现高并发抢券逻辑?
-
你有没有遇到过这样的情况?在电商大促时,系统上线了抢券活动,结果活动刚一开始,服务器就不堪重负,出现超卖、系统崩溃等问题。又或者用户疯狂点击抢券按钮,最后却被告知无券可抢,体验极差。作为互联网大厂的后...
- 每日一题 |10W QPS高并发限流方案设计(含真实代码)
-
面试场景还原面试官:“如果系统要承载10WQPS的高并发流量,你会如何设计限流方案?”你:“(稳住,我要从限流算法到分布式架构全盘分析)…”一、为什么需要限流?核心矛盾:系统资源(CPU/内存/数据...
- Java面试题:服务雪崩如何解决?90%人栽了
-
服务雪崩是指微服务架构中,由于某个服务出现故障,导致故障在服务之间不断传递和扩散,最终造成整个系统崩溃的现象。以下是一些解决服务雪崩问题的常见方法:限流限制请求速率:通过限流算法(如令牌桶算法、漏桶算...
- 面试题官:高并发经验有吗,并发量多少,如何回复?
-
一、有实际高并发经验(建议结构)直接量化"在XX项目中,系统日活用户约XX万,核心接口峰值QPS达到XX,TPS处理能力为XX/秒。通过压力测试验证过XX并发线程下的稳定性。"技术方案...
- 瞬时流量高并发“保命指南”:这样做系统稳如泰山,老板跪求加薪
-
“系统崩了,用户骂了,年终奖飞了!”——这是多少程序员在瞬时大流量下的真实噩梦?双11秒杀、春运抢票、直播带货……每秒百万请求的冲击,你的代码扛得住吗?2025年了,为什么你的系统一遇高并发就“躺平”...
- 其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。
-
其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。比如上周有个小伙伴找我,五年经验但简历全是'参与系统设计''优化接口性能'这种空话。我就问他:你做的秒杀...
- PHP技能评测(php等级考试)
-
公司出了一些自我评测的PHP题目,现将题目和答案记录于此,以方便记忆。1.魔术函数有哪些,分别在什么时候调用?__construct(),类的构造函数__destruct(),类的析构函数__cal...
- 你的简历在HR眼里是青铜还是王者?
-
你的简历在HR眼里是青铜还是王者?兄弟,简历投了100份没反应?面试总在第三轮被刷?别急着怀疑人生,你可能只是踩了这些"隐形求职雷"。帮3630+程序员改简历+面试指导和处理空窗期时间...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)