谈谈Redis的持久化——AOF日志与RDB快照
mhr18 2024-11-16 23:26 17 浏览 0 评论
一、前言
对于Mysql,数据是持久化在磁盘上的。如果误删数据,可以使用binlog进行恢复;突然宕机时,其本身可以借助redo log进行崩溃恢复。
更多关于Mysql日志的内容,可以参考我的另外一篇文章数据库日志——binlog、redo log、undo log扫盲
而对于Redis,一般是把数据直接存储在内存中。如果不做任何持久化工作,在出现宕机后,内存中的全部数据就会丢失。
显然,业务方是不能容忍这样的情况发生的。好在Redis提供了一系列的持久化机制,分别是AOF日志与RDB快照。
二、AOF
AOF全称是Append Only File,Redis每次执行完一个写类型的语句后,会将该语句以某种格式使用追加的方式顺序写入AOF日志中。
值得注意的是,AOF是默认不开启的。
?
AOF日志的格式
以winows为例,进入到redis安装目录中的redis.windows.conf中,将appendonly的值修改为yes,即可开启AOF
# 默认关闭
appendonly yes
# AOF的默认文件名称
appendfilename "appendonly.aof"
当执行以下命令后
set java helloworld
在appendonly.aof文件中,可以看到以下内容
*3 代表当前命令有3个部分
$3 第1部分命令的长度,3个字符
set 第1部分命令
$4 第2部分命令的长度,4个字符
java 第2部分命令
$10 第3部分命令的长度,10个字符
helloworld 第3部分命令
当我们首次使用某个客户端执行命令时,客户端会自动帮我们补充select 0(即选择编号0的数据库),这个命令也会被保存在AOF日志中。
写AOF日志的流程
大致的流程如图所示
在server中,主线程执行完命令之后,会立即将命令写入AOF缓冲中。之后会调用系统函数write(),将命令写入内核缓冲区,并返回给客户端成功的响应。
内核会在合适的时机将内核缓冲区的中的数据写入到磁盘中。
我们设想其中某个阶段宕机时,会不会产生不一致的情况:
1、如果命令执行成功,但写入AOF缓存前崩溃重启,客户端会收到执行失败或超时的响应。重启之后AOF文件中没有该条数据,这个时候,数据是一致的。
2、如果命令执行成功,写入AOF缓存成功,但调用write时崩溃重启。其实这种情况和第一条一样,恢复后数据还是一致的。
3、如果命令执行、写入AOF缓存与内核缓存都成功,客户端会收到成功的响应。如果这个时候机器宕机,内核缓冲区中的数据将会丢失,也就是最后的AOF文件缺少该条命令,恢复后,就会产生数据不一致的情况。
第3种情况发生时,就会出现数据不一致的后果。怎么处理呢,很简单啊,变异步为同步不就行了吗。
调用write写入内核缓冲区后,再调用fsync强制让内核缓冲区中的数据刷到磁盘上,刷盘成功后,再返回给客户端响应。
这样的解决方式看似可以,但是刷盘的操作非常耗时。在Redis执行大量命令的时候,会一直进行不断的刷盘,当磁盘压力过大时,会阻塞下一个命令的执行,大大降低性能。
看来得把握刷盘的时机,刷得慢了,机器崩溃恢复后就会丢失大量数据。刷得快了,就会严重降低性能。
不过,Redis本身也提供了3种写回策略。
写回策略
- always 同步写回。每执行一条命令,写完AOF日志后,再返回。
- everysec 每秒写回。执行命令后,将数据写入到内核缓冲区就返回。只有会有一个线程,执行每秒刷盘的定时任务。
- no 由内核自行控制的写回。每执行一条命令,将数据写入到内核缓冲区就返回。内核会在合适的时机刷盘。
这3种策略体现了不同的刷盘频率,因此拥有不同级别的一致性与性能。
always策略最大程度上保证数据不丢失,但性能最差。
no策略性能最好,但在机器崩溃重启后会丢失比较多的数据。
everysec是一种折中的策略,较always有不错的性能。在极端的情况下,只会丢失1秒内的数据,是比较推荐的方式。
redis.windows.conf中有appendfsync配置项,用来配置写回策略,默认的策略是everysec 。
随着Redis不断记录AOF日志,AOF日志文件将变得越来越大,用作恢复的时间也将越长。因此需要一种方式减少文件的大小,这时候AOF重写就派上用场了。
AOF重写
在出现触发重写的条件时(例如AOF文件达到某个阈值),Redis扫描整个库的所有数据,将数据以命令的方式记录在新的AOF日志中,待记录完成后,使用新的AOF日志替换旧的即可。
旧日志中,可能存有对同一个key的多次操作命令,重写的目的就是取最后一次有效的命令,删除那些历史命令,从而达到瘦身、压缩的效果。
刚才提到,AOF重写会扫描整个库的数据,因此注定就是一个非常耗时的操作,那么就不会在主线程中做,而是通过主线程fork出一个子进程进行重写的。
重写的流程图如下:
?
1、当AOF日志文件的大小超过执行的阈值后,就会触发AOF重写
2、主线程fork出一个子进程,fork的过程仍然是阻塞的。fork完之后,主线程依然可以接受命令并处理
3、子进程与主线程共享一个实例的所有数据,子进程会对整个实例进行扫描,将其中的数据以命令的格式写入到重写日志中。
4、在子进程重写的过程中,主线程可以接受命令,假设这个时候执行了一条写命令。
5、主线程会将数据存入到库中,利用写时复制技术,子进程不会感知到数据有任何变化。
6、主线程将日志先写入AOF缓冲区,再写入重写缓冲区。
7、由特定写回策略,将缓冲区中的数据写入到旧的AOF日志中。
8、当子进程结束扫描,并且将所有命令写入重写日志后,再将重写缓冲区中的数据追加到重写日志中。
9、最后一步,主线程感知到子进程重写日志完成,于是使用新的日志文件替换旧的文件。
也许有人会发出以下的疑问
为什么是fork出子进程,直接使用子线程不是也可以吗?
如果是创建出来一个子线程,那么主线程在写入,子线程在读取,是需要通过加锁的方式来保证线程安全的,加锁就意味着降低性能。
而如果是fork出来子进程,主线程和子进程同样需要共享数据,当主线程写入数据的时候,会利用写时复制技术,避免加锁。
什么是写时复制?
大家应该都知道三角函数吧,嗯,这和写时复制没什么关系。
?
CopyOnWriteArrayList就利用到了写时复制,读不加锁,写则是复制一份数组出来,在新的数组上进行修改,最后替换引用。非常适合应用于读多写少的场景,缺点是在替换引用前,线程读到的是旧数据。
主线程在fork出一个子进程的时候,会将自己的页表(虚拟地址与物理地址的映射表)复制一份出来给子进程,而不是直接复制内存。否则在重写的时候,Redis占用内存会立即翻倍。
这样的话,子进程就可以随意访问主线程中的数据。而当主线程修改一些实例数据时,就会复制一份物理内存出来,并变动主线程的页表,在新的内存地址上存储写之后的数据。因为没有变动子进程的页表,因此主线程写入的数据对子进程不可见。
重写AOF缓冲区的作用是什么?
CopyOnWriteArrayList的缺点在于读到的可能是旧数据,子进程在扫描的时候,其实扫描到的也是旧数据,因此需要在重写结束后做补偿。
子进程在重写的过程中,扫描的数据是fork动作结束的那一刻的快照。而在重写的过程中,主线程依然可以执行命令,那么这些多出来的写命令就可以放在一个独立的重写缓冲区中。在重写完成后,再将重写缓冲区中的内容追加到重写日志中,这就保证了数据的一致。
尽管存在AOF重写机制,但重写后的日志文件还是大,恢复速度较慢。
有没有一种直接存储数据,而不是存储命令(命令的大小显然大于数据本身)的方式呢?RDB就闪亮登场了!
三、RDB
RDB的全称是Redis Database Backup,即数据备份。
会将某一时刻内的所有数据生成一个快照文件。该文件是一种经过压缩的二进制文件,默认名称为dump.rdb,可通过修改dbfilename参数来改变RDB文件名。
快照文件仅保存数据,不保存额外的操作命令,且经过压缩,因此在恢复速度上快于AOF。但RDB没法做到实时的持久化,而AOF可以基本做到。
如何让Redis生成RDB文件
通过save命令手动触发
直接在主线程中执行,会阻塞其他命令
通过bgsave命令手动触发
主线程fork出来一个子进程,由子进程去执行备份。
整个fork的过程,是会阻塞主线程的。由于不会复制物理内存,因此fork是快速的。
fork结束后,主线程依然可以执行其他的命令。
通过配置自动触发
redis.windows.conf中有如下的几个配置可用于触发生成RDB文件
# 900秒内至少出现1条写命令就触发
save 900 1
# 300秒内至少出现10条写命令就触发
save 300 10
# 60秒内至少出现10000条写命令就触发
save 60 10000
这种方式,也是通过fork出一个子进程来做的。
三种方式的触发流程
客户端使用bgsave命令时,主线程fork出来子进程,由子进程完成备份。
在子进程备份期间,主线程依然可以执行命令。但该条数据并不会被子进程扫描到,和AOF重写一样,都利用到了写时复制。
既然RDB文件占用小,恢复速度快,那可以大幅增加RDB生成的频率吗?
那显然是不可以的,有可能上一轮RDB还未生成,下一轮又开始了。而且也存在性能问题,save全程都会阻塞主线程,bgsave的fork操作同样也会阻塞主线程。
当然,RDB这种方式,如果在持久化的过程中发生宕机,会丢失在上次备份之后产生的所有数据。
四、AOF与RDB的特点总结
下面使用一张表格来直观地展示两者之间的优缺点
AOF | RDB | |
持久方式 | 实时追加写入 | 某一时刻的数据快照 |
文件大小 | 包含操作命令,文件较大 | 只包含数据,且经过压缩,文件较小 |
恢复速度 | 慢 | 快 |
数据可靠性 | 由写回策略决定,最好情况下仅丢失1秒数据 | 由触发频率决定,会丢失上一次备份之后产生的所有数据,可靠性不如AOF |
另外值得注意的是,当同时开启AOF与RDB时,Redis会优先使用AOF日志来恢复数据。
RDB相比而言,会丢失较多的数据。AOF只有在实例数据比较大的时候,恢复速度才慢。
五、Redis4.0混合持久化模式
既然AOF与RDB独有各自的优势,能否结合二者的特点呢?
在Redis4.0中,出现了一个新的模式——混合持久化。具体来讲,就是全量RDB+增量AOF,将两种类型的日志文件存放在一起。
RDB可以以较低的频率执行,两次RDB之间的产生的增量数据记录在AOF日志中,因此增量AOF日志的文件很小。
因此Redis在恢复时,先加载RDB数据,再重放增量的AOF日志。不需要像之前重放全量AOF日志,因此恢复效率大大提升。
?
相关推荐
- 【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库
-
如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...
- Pure Storage推出统一数据管理云平台及新闪存阵列
-
PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...
- 对Java学习的10条建议(对java课程的建议)
-
不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...
- SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!
-
官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...
- JDK21有没有什么稳定、简单又强势的特性?
-
佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...
- 「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了
-
在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...
- Java面试题及答案最全总结(2025版)
-
大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...
- 数据库日常运维工作内容(数据库日常运维 工作内容)
-
#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...
- 分布式之系统底层原理(上)(底层分布式技术)
-
作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...
- oracle 死锁了怎么办?kill 进程 直接上干货
-
1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...
- SpringBoot 各种分页查询方式详解(全网最全)
-
一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...
- 《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略
-
《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...
- LoadRunner(loadrunner录制不到脚本)
-
一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...
- Redis数据类型介绍(redis 数据类型)
-
介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...
- RMAN备份监控及优化总结(rman备份原理)
-
今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- 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)