文章目录
- 一、前言
- 二、RDB持久化
- 2.4.1 底层原理:RDB持久化和读入实际是RDB文件的生成和载入
- 2.4.2 底层原理:Redis自动间隔保存是通过内部调用BGSAVE完成的
- 2.4.3 底层原理:RDB文件结构
- 2.4.4 从原理到实践:实际的RDB文件
- 2.4.4.1 不包含任何键值对的RDB文件
- 2.4.4.2 包含任何键值对的RDB文件
- 2.4.4.3 包含带有过期时间的字符串键的RDB文件
- 2.4.4.4 包含一个集合键的RDB文件
- 四、尾声
一、前言
Redis是一个内存数据库,它将自己的数据库状态存储到内存中,Redis提供两个持久化方式:RDB持久化、AOF持久化,如果选择呢?如图:
对于上图的解释是:持久化策略
RDB :Redis DataBase,记录快照
AOF :Append Only File,记录日志
因为AOF文件的更新频率通常比RDB文件的更新频率高,所以:
1)如果服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件来还原数据库状态;
2)只有在AOF持久化功能处于关闭状态时,服务器才会使用RDB文件来还原数据库状态。
二、RDB持久化
2.1 RDB持久化触发
2.1.1 自动触发的三种方式
方式1:配置规则自动触发
方式2:shutdown 或者 shutdown save 触发,保证服务器正常关闭。
方式3:flushdb 和 flushall,前者清空数据库并持久化,后者清空数据库但不持久化,即使是持久化出来,也是空的,没什么意义(唯一的意义就是模拟删库跑路)。
RDB持久化触发后,这个 dump.rdb 文件存在 linux 磁盘哪个目录,redis.conf 配置如下:
在四个属性里面,没有配置rdb持久化的开关,即rdb持久化默认就是打开的,也无法关闭。
在redis中,flushdb和flushall 都是清空当前数据库的操作,但是两者有很大的区别:
1.flushall 清空数据库并执行持久化操作,也就是rdb文件会发生改变,变成76个字节大小(初始状态下为76字节),所以执行flushall之后数据库真正意义上清空了.
2.flushdb 清空数据库,但是不执行持久化操作,也就是说rdb文件不发生改变.而redis的数据是从rdb快照文件中读取加载到内存的,所以在flushdb之后,如果想恢复数据库,则可以直接kill掉redis-server进程,然后重新启动服务,这样redis重新读取rdb文件,数据恢复到flushdb操作之前的状态.
注意:关闭redis-server,要直接 kill 掉redis-server服务,因为shutdown操作会触发持久化.
lsof -i:6379 命令查看redis-server的进程号,然后kill即可
2.1.2 手动触发的两种方式
RDB手动触发两个命令:save、bgsave,如下:
命令 | 含义 | 异同点 |
SAVE命令 | SAVE命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止(ps:在服务器进程阻塞期间,服务器不能处理任何命令请求) | 相同点:都是在磁盘上创建/生成RDB文件; 不同点:SAVE命令会阻塞,BGSAVE命令不会阻塞 注意:SAVE译为保存,所以会阻塞,BGSAVE为Background SAVE,译为后台保存,所以不会阻塞。 |
BGSAVE命令 | BGSAVE命令会派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程(父进程)继续处理命令请求 |
1)当SAVE命令执行时, Redis服务器会被阻塞,所以当SAVE命令正在执行时,客户端发送的所有命令请求都会被拒绝。只有在服务器执行完SAVE命令、重新开始接受命令请求之后,客户端发送的命令才会被处理。
2)BGSAVE命令的保存工作是由子进程执行的,所以在子进程创建RDB文件的过程中,Redis服务器仍然可以继续处理客户端的命令请求,但是,在 BGSAVE命令执行期间,服务器处理SAVE、 BGSAVE、 BGREWRITEAOF三个命令的方式会和平时有所不同
首先,在 BGSAVE命令执行期间,客户端发送的SAVE命令会被服务器拒绝,服务器禁止SAVE命令和 BGSAVE命令同时执行是为了避免父进程(服务器进程)和子进程同时执行两个rdbsave调用,防止产生竞争条件
其次,在BGSAVE命令执行期间,客户端发送的 BGSAVE命令会被服务器拒绝,因为同时执行两个BGSAVE命令也会产生竞争条件
最后, BGREWRITEAOF和 BGSAVE两个命令不能同时执行(如果 BGSAVE命令正在执行,那么客户端发送的 BGREWRITEAOF命令会被延迟到RGSAVE命令执行完毕之后执行;如果 BGREWRITEAOF命令正在执行,那么客户端发送的 RGSAVE命令会被服务器拒绝)。实际上,BGREWRITEAOF和 BGSAVE两个命令的实际工作都由子进程执行,所以这两个命令在操作方面并没有什么冲突的地方,但是,这两个子进程都同时执行大量的磁盘写入操作,不能同时执行它们只是一个性能方面的考虑一并发出两个子进程。
2.2 实践:RDB持久化
2.2.1 shutdown命令触发持久化
2.2.2 flushall命令模拟数据丢失
2.2.3 通过备份文件恢复数据
2.3 RDB特点
RDB特点
优势:
紧凑,生成相同数据的文件比AOF占用提交小,适合备份和灾难恢复
生成文件过程不影响主进程
大数据集恢复速度较快
不足:
不能实时持久化,可能丢失数据
2.4 RDB底层原理浅析
2.4.1 底层原理:RDB持久化和读入实际是RDB文件的生成和载入
RDB文件的生成+RDB文件的载入,如下图所示:
RDB文件的生成:内存上的redis数据库 --> 磁盘上的RDB文件;
RDB文件的载入:磁盘上的RDB文件 -->内存上的redis数据库
注意:Redis服务器在生成RDB可以使用 BGSAVE 不影响主线程,但是Redis服务器在载入RDB文件期间,(redis服务器)会一直处于阻塞状态,直到载入工作完成为止。
2.4.2 底层原理:Redis自动间隔保存是通过内部调用BGSAVE完成的
对于redis数据库保存的两个命令(SAVE命令和 BGSAVE命令):SAVE命令由服务器进程执行保存工作, BGSAVE命令则由子进程执行保存工作,所以SAVE命令会阻塞服务器,而 BGSVE命令则不会。因为BGSAVE命令可以在不阻塞服务器进程的情况下执行,所以用户可以通过save选项设置多个保存条件,但只要其中任意一个条件被满足,服务器就会执行 BGSAVE命令,即我们的Redis自动间隔保存就是通过设置条件,在满足阈值的时候调用BGSAVE命令来实现的。
举个例子,如果我们向服务器提供以下配置
只要满足三个条件中的任意一个,BGSAVE命令就会执行,实现RDB文件保存至磁盘。
服务器在900秒之内,对数据库进行了至少1次修改。
服务器在300秒之内,对数据库进行了至少10000次修改。
服务器在60秒之内,对数据库进行了至少10000次修改。
Redis自动间隔保存的底层实现(saveparam数组(seconds秒数+changes修改数)+dirty计数器+lastsave属性)
2.4.3 底层原理:RDB文件结构
让我们来看一下磁盘上这个神秘的RDB文件:
针对上图给出表格(各个部分含义用表格看清晰些)
RDB文件结构各个部分 | 含义 | 长度 | 备注 |
第一个部分REDIS | 该部分保存着“REDIS”五个字符,程序载入文件时,通过这五个字符,快速检查所载入的文件是否是RDB文件 | 5字节 | 这里是二进制数据而不是字符串,即“REDIS”表示’R’‘E’‘D’‘I’‘S’五个字符,而不是’R’‘E’‘D’‘I’‘S’'\0’五个字符 |
第二个部分db_version | 该部分记录RDB文件版本号,如“0006”代表RDB文件为第六版本。 | 4字节 | 无 |
第三个部分databases | 该部分根据实际情况记录着0个或多个数据库 | 0~n字节 | 根据数据库锁保存键值对的数量、类型和内容,该部分长度不同 |
第四个部分EOF | 该部分一个EOF常量,表示RDB文件正文部分结束 | 1字节 | 当程序读到这个值的时候,它知道所有数据库的所有键值对都已经载入完毕了 |
第五个部分check_sum | 该部分为一个无符号数,保存着一个检验和,载入RDB文件时,用来检查是否损坏 | 8字节 | 这个检验和是程序通过对REDIS、db_version databases EOF四个部分的内容计算得出的 |
对于上图的解释:上图同时给出了“RDB文件结构、RDB文件中的数据库结构、RDB文件中的数据库中的键值对的结构” 三层结构,我们要对三层结构同时解析。
关于RDB文件结构:包括REDIS常量、db_version数据库版本、databases实际数据库、EOF常量标志、check_sum检验和,其中,数据库为空则没有第三部分databases,其他不难,结合上图一看就懂,略。
关于RDB文件中的数据库结构:包括SELECTDB常量、db_number数据库序号、key_value_pairs实际键值对,结合上图一看就懂,略。
关于RDB文件中的数据库中的键值对的结构:包括TYPE类型、key键、value值。
RDB文件中的数据库中的键值对的结构(TYPE+key+value)
TYPE记录了value的类型,长度为1个字节(每一个TYPE常量都代表一个对象类型或底层编码)
TYPE常量 | 对应的对象类型或编码类型 |
REDIS_RDB_TYPE_STRING | string类型 int底层编码/raw底层编码/embstr编码 |
REDIS_RDB_TYPE_LIST | list类型 linkedlist底层编码 |
REDIS_RDB_TYPE_SET | set类型 hashtable编码 |
REDIS_RDB_TYPE_ZSET | sorted set类型 skiplist编码 |
REDIS_RDB_TYPE_HASH | hash类型 hashtable编码 |
REDIS_RDB_TYPE_LIST_ZIPLIST | list类型 ziplist底层编码 |
REDIS_RDB_TYPE_SET_INTSET | set类型 intset底层编码 |
REDIS_RDB_TYPE_ZSET_ZIPLIST | sorted set类型 ziplist底层编码 |
REDIS_RDB_TYPE_HASH_ZIPLIST | hash类型 ziplist底层编码 |
key表示键,value表示值
1)字符串对象(REDIS ENCODING_INT和REDIS_ENCODING_RAW(大于20字节压缩,小于等于20字节不压缩))
对于上表,如果TYPE的值为 REDIS_RDB_TYPE_STRING, value保存的就是一个字符串对象,字符串对象的编码可以是REDIS ENCODING_INT或者REDIS_ENCODING_RAW。
如果字符串对象的编码为 REDIS_ENCODING_INT,那么说明对象中保存的是长度不超过32位的整数 ,
如果字符串对象的编码为 REDIS_ENCODING_RAW,那么说明对象所保存的是一个字符串值,根据字符串长度的不同,有压缩和不压缩两种方法来保存这个字符串:如果字符串的长度小于等于20宇节,那么这个字符串会直接被原样保存;如果字符串的长度大于20字节,那么这个字符串会被压缩之后再保存。
一图小结:
对上图的理解:左边存放数字,中间因为字符串长度为21>20,所以压缩,右边因为字符串长度为5<20,不压缩。
2)列表对象
如果TYPE的值为 REDIS_RDB_TYPE_LIST,那么 value保存的就是一个 REDIS_ENCONDING_LINKEDLIST编码的对象,一图小结:
3)集合对象
如果TYPE的值为REDIS_RDB_TYPE_SET,那么 value保存的就是一个 REDIS_ENCODING_HT编码的集合对象,一图小结:
4)哈希表对象
如果TYPE的值为REDIS_RDB_TYPE_HASH,那么value保存的就是一个 REDIS_ENCODING_HT编码的集合对象,一图小结:
5)有序集合对象
如果TYPE的值为 REDIS_RDB_TYPE_ZSET,那么value保存的就是一个REDIS_ENCODING_SKIPLIST编码的有序集合对象,一图小结:
6)INTSET编码的集合
如果TYPE的值为 REDIS_RDB_TYPE_SET_INTSET,那么 value保存的就是一个整数集合对象,RDB文件保存这种对象的方法是,先将整数集合转换为字符串对象,然后将这个字符串对象保存到RDB里面。如果程序在读入RDB文件过程中,碰到由整数集合对象转换成的字符串对象,那么程序会根据TYPE值的指示,先读入字符串对象,再将这个字符串对象转换成原来的整数集合对象。
7)ZIPLIST编码的列表、哈希表或有序集合
如果TYPE的值为 REDIS_RDB_TYPE_LIST_ZIPLIST、REDIS_RDB_TYPE_HASH_ZIPLIST或者REDIS_RDB_TYPE_ZSET ZIPLISL,value保存的就是一个压缩列表对象,RDB文件保存这种对象的方法是:
1)将压缩列表转换成一个字符串对象;
2)将转换所得的字符串对象保存到RDB文件。
如果程序在读入RDB文件的过程中,碰到由压缩列表对象转换成的字符串对象,那么程序会根据TYPE值的指示,执行以下操作:
1)读入字符串对象,并将它转换成原来的压缩列表对象。
2)根据TYPE的值,设置压缩列表对象的类型:如果TYPE的值为 REDIS_RDB_TYPE_LIST_ZIPLIST,那么压缩列表对象的类型为列表;如果TYPE的值为REDIS_RDB_TYPE_HASH_ZIPLIST,那么压缩列表对象的类型为哈希表;如果TYPE的值为REDIS_RDB_ TYPE_ZSET_ZIPLIST,那么压缩列表对象的类型为有序集合。
从步骤2可以看出,由于TYPE的存在,即使列表、哈希表和有序集合三种类型都使用压缩列表来保存,RDB读入程序也总可以将读人并转换之后得出的压缩列表设置成原来的类型。
2.4.4 从原理到实践:实际的RDB文件
上面对RDB文件的介绍,这里对实际的RDB文件分析。
2.4.4.1 不包含任何键值对的RDB文件
2.4.4.2 包含任何键值对的RDB文件
2.4.4.3 包含带有过期时间的字符串键的RDB文件
2.4.4.4 包含一个集合键的RDB文件
三、AOF持久化
除了RDB持久化功能之外, Redis还提供了AOF( Append Only File)持久化功能。与RDB持久化通过保存数据库中的键值对来记录数据库状态不同,AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态的,如下图:
AOF 全称 Append Only File
AOF: Redis默认不开启。AOF采用日志的形式来记录每一个写操作,并追加到文件中。开启后,执行更改Redis数据的命令时,就会把命令写入到AOF文件中。Redis重启时会根据日志文件的内容把写指定从前到后执行一次以完成数据的恢复工作。
关于RDB持久化与AOF持久化的不同:RDB持久化是将进程数据写入文件,而AOF持久化(即Append Only File持久化),则是将Redis执行的每次写命令记录到单独的日志文件中(有点像MySQL的binlog);当Redis重启时再次执行AOF文件中的命令来恢复数据。所以,与RDB相比,AOF持久化拥有更好的实时性。
注意:Redis服务器默认开启RDB,关闭AOF;要开启AOF,需要在配置文件中配置:appendonly yes
3.1 AOF持久化基本命令
3.1.1 AOF持久化基本命令
如何打开aof持久化,并指定aof持久化文件名,如下:
将aof持久化打开,并使用一下,看一下持久化的
3.1.2 appendfsync属性
appendfsync属性:
always表示每次写入都执行fsync,以保证数据同步到磁盘
everysec(默认)表示每秒执行一次fsync,可能会导致丢失这1s数据
no表示不执行fsync,由操作系统保证数据同步到磁盘
appendfsync选项的值 | flushAppendOnlyFile属性的行为 | 效率 | 安全性 |
always | 将aof_buf缓冲区中的所有内容写入并同步到AOF文件中 | 最慢(服务器每个事件循环将aof_buf缓冲区的所有内容写入到AOF文件中,并同步AOF文件) | 最安全(出现故障停机,数据库丢失一个数据循环中的所有命令数据) |
everysec | 将aof_buf缓冲区中的所有内容写入到AOF文件中,如果上次同步AOF文件的时间距离现在超过一秒钟,那么再次对AOF文件进行同步,并且这个同步操作是由一个线程专门负责执行的 | 适中(服务器每个事件循环将aof_buf缓冲区的所有内容写入到AOF文件中,并每隔一秒钟在子线程中同步AOF文件) | 适中(出现故障停机,数据库会丢失一秒钟的命令数据) |
no | 将aof_buf缓冲区中的所有内容写入到AOF文件中,但并不对AOF文件进行同步,何时同步由操作系统决定 | 最快(服务器每个事件循环将aof_buf缓冲区的所有内容写入到AOF文件中,同步操作的执行由操作系统控制) | 最不安全(出现故障停机,数据库会丢失上次同步AOF文件之后的所有写命令数据) |
安全性:always最高,everysec其次,no最差
性能影响:no最小,everysec 其次,always性能影响最大
问题:如果持久化的时候,突然机器掉电会怎样?
回答:取决于AOF日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。
注意:appendfsync默认值是everysec。
3.2 AOF重写
3.2.1 AOF重写相关属性
问题:aof文件越来越大,怎么办?
回答:aof重写rewrite
1)为什么进行AOF重写?
随着时间流逝,Redis服务器执行的写命令越来越多,AOF文件也会越来越大;过大的AOF文件不仅会影响服务器的正常运行,也会导致数据恢复需要的时间过长,这个时候需要在服务器上存放一个精简版的AOF文件,这里就涉及到AOF重写。
2)什么是AOF重写?
文件重写是指定期重写AOF文件,减小AOF文件的体积,即生成新AOF文件替换旧AOF文件的功能。
注意1:AOF重写是把Redis进程内的数据转化为写命令,同步到新的AOF文件;不会对旧的AOF文件进行任何读取、写入操作,即旧文件是不会有任何读写操作的。
注意2:对于AOF持久化来说,文件重写虽然是强烈推荐的,但并不是必须的;即使没有文件重写,数据也可以被持久化并在Redis启动的时候导入;因此在一些实现中,会关闭自动的文件重写,然后通过定时任务在每天的某一时刻定时执行。
3)文件重写为什么能够压缩AOF文件?文件重写是如何实现压缩AOF文件的?
过期的数据不再写入新的AOF文件,从而使新的AOF文件相对于旧的AOF文件体积得到压缩;
无效的命令不再写入新的AOF文件,从而使新的AOF文件相对于旧的AOF文件体积得到压缩;
多条命令可以合并为一个,从而使新的AOF文件相对于旧的AOF文件体积得到压缩;
注意:为了防止单条命令过大造成客户端缓冲区溢出,对于list、set、hash、zset类型的key,并不一定只使用一条命令;而是以某个常量为界将命令拆分为多条。这个常量在redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD中定义,不可更改。
如上配置,则aof重写触发条件为 :
1、aof 文件达到 64mb 触发 (上一次重写为0m,达到最小重写文件大小)
2、aof 文件达到 128MB 触发 (超过上一次重写的aof文件大小的 100% 进行重写)
3、aof 文件达到 256MB 触发 (超过上一次重写的aof文件大小的 100% 进行重写)
4、aof 文件达到 512MB 触发 (超过上一次重写的aof文件大小的 100% 进行重写)
3.2.2 AOF重写过程中的命令
问题:重写rewrite过程中,aof文件修改了怎么办?
回答:子进程重写rewrite的时候,此时客户端redis-cli如果发过来命令,此时 主线程会负责 将这条命令同时 写入到 现有的 aof 文件 和 aof 重写缓存 两个地方,坚决保证 aof持久化文件不会丢失 重写rewrite过程中,redis-cli 发过来的命令。
另外两个与aof持久化相关的属性,如下:
3.3 AOF持久化的特点
AOF特点
优势:
同步频率灵活,最多丢失一秒数据
不足:
体积更大;
消耗更多性能,恢复相同数据量比RDB慢。
3.4 AOF命令底层原理
AOF持久化的实现=命令追加append+文件写入write+文件同步sync+文件重写rewrite,上面接触过的。
AOF持久化三个步骤:命令追加append、文件写入write、文件同步sync,且看下表:
AOF持久化步骤 | 含义 |
命令追加append | (当AOF持久化功能处于打开状态时 appendonly yes),服务器在执行完一个写命令write,会以协议格式将其(write命令)追加到服务器状态auto_aof缓冲区的末尾 |
文件写入write和文件同步sync | 根据不同的同步策略将aof_buf中的内容同步到硬盘 |
文件重写rewrite | 定期重写AOF文件,达到压缩的目的 |
命令追加append:将Redis的写命令追加到缓冲区aof_buf。
文件写入append和文件同步sync
事件循环(基础概念):事件循环就是一个Redis的服务器进程,这个循环中的文件事件负责接收客户端的命令请求,以及向客户端发送命令回复,而时间事件则负责执行像serverCron函数这样需要定时运行的函数。
因为服务器在处理文件事件时可能会执行写命令,使得些内容被追加到aof_buf缓冲区里面,所以在服务器每次结束一个事件循环之前,它都会调用flushAppendOnlyFile函数,考虑是否需要将aof_buf缓冲区中的内容写入和保存到AOF文件里面。即AOF文件写入和文件同步是通过flushAppendOnlyFile函数来完成的。
又flushAppendOnlyFile函数的行为由服务器配置的appendfsync选项的值来决定,关于appendfsync不同值的不同持久化操作,就是上面说到的 always everysec no 三个值。
四、尾声
Redis有两种持久化方式:RDB冷备份持久化和AOF热备份持久化。
RDB持久化的文件是数据快照;
AOF持久化的文件redis执行命令日志,类似mysql里面的binlog。
rdb 持久化生成了 dump.rdb 文件, 无法直接查看,默认放在 / 根目录,可以指定为 /root 目录 (在redis.conf里面指定);
aof 持久化生成 appendonly.aof 文件,可以直接查看,就是记录操作命令,因为太大,有 rewrite 重写设计。
RDB持久化生成dump.rdb文件,有两种触发方式:
被动触发:三条数据(只要匹配一条就可以了) shutdown flushall
主动触发:save bgsave
aof 持久化生成 appendonly.aof 文件,只要开启了aof持久化,就会被动触发:
每条命令都会被持久化出来,默认是是 everysec 每秒持久化一次。
优缺点:
(1)AOF 同步最多损失一秒数据,可以做到 实时持久化/秒级持久化,可以更好的保证内存断电的数据完整性,将数据损失降低到最小。【这是AOF的优点,也是RDB的不足】
(2)相同数据的Redis,AOF文件通常比RDB文件体积更大,即使 AOF 有命令压缩,因为RDB存储的时候数据快照,AOF存储的是redis执行命令 【这是AOF的不足,也是RDB的优势】
(3) RDB 对性能的影响更小(通过fork一个子线程来持久化,主线程不需要进行任何磁盘操作),而且恢复相同数据量的速度比 AOF 更快。【这是AOF的不足,也是RDB的优势】
小结:工程开发时,要么保持默认,只使用RDB持久化;要么两种都是使用,即 RDB冷备份+AOF热备份(先打开 rdb 冷备份,然后打开 aof 热备份)。
联系:默认只打开 rdb 持久化 ,aof 持久化需要手动打开;
联系:工程开发时,要么保持默认,只使用RDB持久化;要么两种都是使用,即 RDB冷备份+AOF热备份(先打开 rdb 冷备份,然后打开 aof 热备份)。