今天小小的盘了一下Redis的持久化机制,赶紧记下来,要么过两天就忘了哈哈哈!!~~
目录
Redis提供了两种持久化机制,一种是RDB快照,是全量的备份;另一种是AOF日志,记录的是连续的增量备份。
RDB
RDB持久化机制可以内存中的数据库状态保存在磁盘中,避免数据意外丢失。
Redis有两个命令用于生成RDB文件,一个是SAVE,一个是BGSAVE。
SAVE命令会阻塞Redis服务器进程,知道RDB文件创建完为止,在服务器进程阻塞期间,服务器不能处理任何命令请求。
BGSAVE会派生出一个子进程,然后由子进程负责创建RDB文件,父进程(服务器进程)可以继续处理命令请求。
下面的伪代码可以很明显看出来两个命令的区别:
def SAVE {
// 创建RDB文件
rdbSave();
}
def BGSAVE{
//创建子进程
pid = fork();
if pid == 0:
//子进程负责创建RDB文件
rdbSave();
//完成后像父进程发送信号
signal_parent();
else if > 0 :
//父进程继续处理命令,并通过轮询等待子进程的信号
...
else:
//处理出错的情况
handle_fork_error;
}
Redis使用操作系统的多进程COW(Copy On Write)机制来实现持久化。
这个机制核心思想总结一句话就是:只有在不得不复制内容时才去复制内容。
当一个用户父进程创建自己的子进程时,父进程会把其申请的用户空间设置为只读,子进程可共享父进程占用的用户内存空间中的页面(这就是一个共享的资源)。当其中任何一个进程修改此用户内存空间中的某页面时,内核会通过page fault异常获知该操作,并完成拷贝内存页面,使得两个进程都有各自的内存页面。这样一个进程所做的修改不会被另外一个进程可见了。
通过COW机制,使得内核可以尽可能地延迟内存页的访问,最重要的是,由于很多情况下并不需要写操作或只需要很少量的写操作,因此该机制将节省大量时间,提高效率。
虽然子进程在进行GBSAVE命令的时候不影响父进程执行其他命令,但是:
- 在BGSAVE命令执行期间,客户端发送的SAVE命令会被服务器拒绝(服务器禁止SAVE命令和BGSAVE命令同时执行,防止产生条件竞争)。
- 在BGSAVE命令执行期间,客户端发送的BGSAVE命令会被服务器拒绝。
- BGREWORITEAOF和BGSAVE俩个命令不能同时执行。(这两个都是子进程执行,不能同时执行是出于性能考虑)。
AOF
AOF日志存储的是Redis服务器的顺序指令序列。
AOF日志事以文件额形式存在的,当程序对AOF日志文件进行写操作时,实际上是将内容写到了内核分配的一个内存缓冲中,然后内核会异步刷新到磁盘。这里Linux提供了flushAppendOnlyFile()函数可以保证机器突然宕机后,一部分内存缓冲中还未来得及刷新到的磁盘的数据丢失。
假设AOF日志记录了自Redis创建以来所有的修改指令序列,那么就可以通过对一个空的Redis实例顺序执行所有指令--重放,来恢复Redis当前实例的内存数据结构状态。
flushAppendOnlyFile()函数的执行由appendfsync的值来控制:
appendfsync的值 | flushAppendOnlyFile()函数的行为 | 效率 |
always | 来一个指令就将将aof_buffer中的所有内容写入并同步到AOF文件 | 三个中最慢 |
everysec | 将aof_buffer中的所有内容写入AOF文件,每隔一秒在子线程中对AOF文件进行一次同步 | 足够快 |
no | 将aof_buffer中的所有内容写入AOF文件,何时同步有操作系统决定。 | 文件写入时间最快,同步时间最短 |
我在读到这里有有过一个疑问,那就是写入AOF文件跟同步AOF文件有啥不同,不就是指的把aof_buffer中的内容真正写到AOF文件中保存下来吗?
其实,在现代操作系统中,为了提高文件的写入效率,当用户调用wirte函数,将一些数据写入到文件的时候,操作系统会通常将写入数据暂时保存在一个内存缓冲os_buffer中(这一步就叫写入),等到缓冲区的空间被填满,或者超过了指定的时限后,才真正地将缓冲区中的数据写入到磁盘里面(这一步就交同步)。而上文如果appendfsync = no时,就是操作操作系统等到缓冲区的空间被填满,或者超过了指定的时限后才进行同步。那么always和everysec是通过什么来控制强制同步操作的呢,操作系统提供了fsync()和fdatasync()两个函数,来强制让操作系统立即让缓冲区中的数据写入到硬盘里面。
这里感觉跟mysql InnoDB的redo log持久化好像
个人理解:欢迎指正。
AOF重写
AOF持久话打开时,服务器在执行完一个写命令后,会以协议格式将被执行的写命令追加到服务器状态的aof_buffer缓冲区末尾,在Redis长期的执行过程中,AOF的日志会越来越长。如果实例宕机重启,Redis提供了BGREWIRTEAOF命令实现AOF文件重写功能:Redis服务器可以创建一个新的AOF文件来替代现有的AOF文件,新旧两个文件锁保存的数据结构状态完全一致,但新的AOF文件体积要小的多,多说无用,看下面的例子:
rpush list "A" "B" //["A","B"]
rpush list "C" //["A","B","C"]
rpush list "D" E" //["A","B","C","D","E"]
lpop list //["B","C","D","E]
服务器保存当前list状态,必要在AOF文件中写入4条指令;而AOF重写会直接从数据库中读取键list的值,然后只用一条指令来代替AOF文件中的4条指令:
rpush list "B","C","D","E"
注意:为了避免执行命令是造成客户端输入缓冲区溢出,重写是会先判断键所包含元素的个数是否超过一定个数(64),如没超过就一条指令执行,若超过了就分多条指令执行。
AOF后台缓冲
Redis不希望AOF重写造成服务器无法处理客户端发来的命令请求,所以AOF重写程序也是在子线程里进行的。这样有两个好处:
- 子进程在AOF期间,父进程可以继续处理命令请求。
- 子进程带有服务器进程的数据副本,使用子进程而不是线程,可以避免使用锁的情况下,保证数据的安全性。
同时也带来一个问题,若子进程在AOF期间,父进程也要对现有数据进行修改。那不就数据有误差了,不同步了怎么办?诶,Redis设置了一个AOF重写缓冲区,这个缓冲区在子进程开始之后使用,当Redis服务器执行完一个写命令后,会同时将这个写命令发送给AOF缓冲区和AOF重写缓冲区。
下面模拟一段AOF后台重写过程
时间 | 服务器进程(父进程) | 子进程 |
T1 | set k1 v1 | |
T2 | set k1 v2 | |
T3 | set k1 v3 | |
T4 | 创建子进程,执行AOF文件重写 | 开始文件重写 |
T5 | set k2 100 | 执行重写操作 |
T6 | set k2 200 | 执行重写操作 |
T7 | set k2 300 | 完成AOF文件重写,像父进程发送信号 |
T8 | 接收到子进程发来的信号,将命令set k2 100、set k2 200、set k2 300(从AOF重写缓冲区中拿的)追加到新AOF文件的末尾 | |
T9 | 用新的AOF文件覆盖旧的AOF文件 |
Redis 4.0混合持久化
当Redis AOF文件很大的时候,重放的速度就会慢很多,所以Redis4.0带来了一个新的持久化选项--混合持久化,将rdb文件的内容和增量的AOF日志文件放在一起。这里的AOF不再是全量的日志,而是持久化开始到持久化结束这段时间发生的增量AOF日志。
在Redis重启过程中,可以先加载rdb的内容,然后在重放增量AOF日志,就可以完全替代之前的AOF全量文件,重启效率会大幅提升。
总结:
- RDB文件用于保存和还原Redis服务器所有数据库中的所有键值对数据。
- SAVE命令由服务器直接执行保存操作,该命令会阻塞服务器。
- BGSAVE命令由子进程执行保存操作,不会阻塞服务器。
- 服务器状态中会保存所有用save选项设置的保存条件,当任意一个保存条件被满足时,服务器会自动执行BGSAVE命令。
- RDB文件是一个经过压缩的二进制文件,由多个部分组成。
- 对于不同类型的键值对,RDB文件保存方式不同。
- AOF文体通过保存所有修改数据库的写命令请求来记录服务器的数据库状态。
- AOF命令会先保存在AOF缓冲区里面,之后再定期写入并同步到AOF文件。
- AOF文件重写后新文件和原文件所保存的数据状态完全一致,只不过体积更小。
- 在执行BGREWRITEAOF命令时,Redis服务器会维护一个AOF重写缓冲区。
参考:
1. 《Redis设计与实现》 --黄健宏
2. 《Redis深度历险》 --钱文品