redis的持久化有两种方式:RDB和AOF。
- RDB持久化功能是将某个时间点的数据库状态保存到一个RDB文件上(通过保存数据库的键值对来记录数据库状态)。RDB持久化功能所生成的RDB文件是一个经过压缩的二进制文件,通过该文件可以还原成生成RDB文件时的数据库状态。
- AOF持久化是通过保存redis服务器所执行的写命令来记录数据库状态的。
1.RDB的创建与载入
RDB文件的生成有两个命令,SAVE和BGSAVE。save会直接阻塞服务器进程,bgsave是派生出一个子进程,然后父进程继续处理命令请求。
RDB文件的载入工作是在服务器启动时自动执行的,所以没有专门用于载入rdb文件的命令,只要redis服务器在启动时检测到rdb文件存在,它就会自动载入rdb文件。
如果服务器开启了AOF持久化功能,那么服务器优先使用aof文件来还原数据库状态。只有在aof持久化功能处于关闭状态时,服务器才会使用rdb文件来还原数据库状态。(流程为:服务器启动->执行载入程序->是否开启aof持久化功能?是,载入aof文件;否,载入rdb文件)。
1.1 bgsave命令执行时的服务器状态
1.在执行BGSAVE命令期间,客户端发送的SAVE和BGSAVE都会被服务器拒绝。因为这两个命令同时执行会产生竞争条件。
2.BGSAVE命令正在执行,那么客户端发送的BGREWRITEAOF命令会被延迟到BGSAVE命令执行完毕后执行。如果BGREWRITEAOF命令正在执行,那么客户端发送的BGSAVE命令会被服务器拒绝。这两个命令都由子进程来执行,并没有冲突,不能同时执行他们是一个性能方面的考虑,并发两个子进程,且都是同时执行大量的磁盘写入操作,不是一个好主意。
1.2 RDB文件载入时的服务器状态
服务器在载入RDB文件时,会一直阻塞,直到载入完成。
2.自动间隔性保存RDB
用户可以通过服务器的save选项,让服务器每隔一段时间自动执行一次BGSAVE命令。
struct redisServer
{
//...
// 记录了保存条件的数组
struct saveparam *saveparams;
// 修改计数器
long long dirty;
//上一次执行保存的时间
time_t lastsave;
//...
};
当保存条件有一个被满足时,服务器就会执行BGSAVE命令。
- dirty计数器记录距离上一次成功执行SAVE命令或者BGSAVE命令之后,服务器对数据库状态(服务器中的所有数据库)进行了多少次修改。
- lastsave属性是一个UNIX时间戳,记录了服务器上一次成功执行SAVE命令或者BGSAVE命令的时间。
例如SADD database redis mongodb mariadb,那么程序会将dirty计数器的值增加3.
2.1 检查保存条件是否满足
redis的服务器周期性操作函数serverCron默认每个100ms就会执行一次,对正在运行中的服务器进行维护,其中一项工作就是检查save选项所设置的保存条件是否已经满足,如果满足的话,就执行BGSAVE命令。
3. AOF的实现
AOF持久化功能的实现可以分为命令追加(append),文件写入,文件同步(sync)三个步骤。
3.1 命令追加
当AOF持久化功能处于打开状态时,服务器在执行完一个写命令之后,会以redis的命令请求协议的格式将被执行的写命令追加到服务器状态的aof_buf缓冲区的末尾。
struct redisServer
{
// ...
//aof缓冲区
sds aof_buf;
// ...
};
3.2 AOF文件的写入与同步
redis的服务器进程就是一个事件循环(loop),这个循环中的文件事件负责接收客户端的命令请求,以及向客户端发送命令回复,而时间事件则负责执行像serverCron函数这样需要定时运行的函数。
事件循环流程如下:
def eventLoop():
while True:
# 处理文件事件,接收命令请求以及发送命令回复
# 处理命令请求时可能会有新内容被追加到aof_buf缓冲区中
processFileEvents()
# 处理时间事件
processTimeEvents()
# 考虑是否要将aof_buf中的内容写入和保存到AOF文件里面
flushAppendOnlyFile()
flushAppendOnlyFile
函数的行为由服务器配置的appendfsync选项的值来决定。默认值为everysec
.
文件的写入和同步
为了提高文件的写入效率,在现代操作系统中,当用户调用write函数,将一些数据写入到文件的时候,操作系统通常会将写入数据暂时保存在一个内存缓冲区里面,等到缓冲区的空间被填满,或者超过了指定的时限后,才真正的将缓冲区中的数据写入到磁盘里面。
这种做法虽然提高了效率,但也为写入数据带来了安全问题,因为如果计算机发生停机,那么保存在内存缓冲区里面的写入数据将会丢失。
为此,系统提供了fsync和fdatasync两个同步函数,它们可以强制让操作系统立即将缓冲区里的数据写入到硬盘里面,从而确保写入数据的安全性。
AOF持久化的效率和安全性
服务器配置appendfsync选项的值直接决定AOF持久化功能的效率和安全性。
- 当appendfsync的值为always时,服务器在每个事件循环都要将aof_buf缓冲区中的所有内容写入到AOF文件,并且同步AOF文件,所以always的效率是三个选项中最低的一个,但从安全性来讲,always是最安全的,因为即使出现故障停机,aof持久化也只会丢失一个事件循环中所产生的命令数据。
- 当appendfsync的值为everysec时,服务器在每个事件循环都要将aof_buf缓冲区中的所有内容写入到AOF文件,并且每隔一秒就要在子线程中对AOF文件进行一次同步。从效率上讲,everysec模式足够快,并且就算出现故障停机,数据库也只丢失一秒钟的命令数据。
- 当appendfsync的值为no时,服务器在每个事件循环都要将aof_buf缓冲区中的所有内容写入到AOF文件,至于何时对AOF文件进行同步,则由操作系统控制。因为处于no模式下的flushAppendOnlyFile调用无须执行同步操作,所以该模式下的AOF文件写入速度总是最快的,不过因为这种模式会在系统缓存中积累一段时间的写入数据,所以该模式的单次同步时长通常是三种模式中时间最长的。从平摊操作的角度来看,no模式和everysec模式的效率类似,当出现故障停机时,使用no模式的服务器将会丢失上次同步AOF文件之后的所有写命令数据。
4. AOF文件的载入与数据还原
AOF文件包含了重建数据库状态所需的所有写命令,所以只需要读入并执行一遍命令,就可以还原之前的数据库状态。
读取AOF文件并还原数据库状态的步骤如下:
- 创建一个不带网络连接的伪客户端(fake client):因为redis的命令只能在客户端上下文中执行,而载入的命令来源于AOF文件不是网络连接。所以创建一个没有网络连接的伪客户端,这个执行命令的效果和带网络连接的客户端执行命令的效果完全一样。
- 从AOF文件中分析并读取出一条写命令。
- 使用伪客户端执行被读出的命令。
- 一直执行步骤2和步骤3,直到AOF文件中的所有写命令都被处理完毕为止。
5. AOF重写
背景是随着服务器运行,AOF文件的内容越来越多,文件体积过大。所以通过AOF文件重写,提供一个新的文件来代替,但体积小很多。
5.1 AOF文件重写的实现
该功能是通过读取服务器中的数据库状态来实现的,程序无须对现有aof文件进行任何读入,分析或者写入操作。使用aof_rewrite函数来实现。
同时,为了避免客户端输入缓冲区溢出,AOF重写的每一条命令的键不能超过64个(取REDIS_AOF_REWRITE_ITEMS_PER_CMD常量)。比如一个集合键元素过多,就会采用多条命令来记录。
5.2 AOF后台重写
上面aof_rewrite
函数会进行大量写入,这个线程会被长时间阻塞。因为redis服务器使用单个线程来处理命令请求,所以在重写AOF文件期间,服务器无法处理客户端发来的命令请求。
所以,redis决定将AOF重写程序放入到子进程里执行,这样做可以达到两个目的:
- 子进程进行AOF重写期间,服务器进程(父进程)可以继续处理命令请求;
- 子进程带有服务器进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全性。
为了解决在重写过程中新产生的命令请求,redis新增了一个AOF重写缓冲区,这个缓冲区在服务器创建子进程之后开始使用,当redis服务器执行完一个写命令之后,它会同时将这个写命令发送给AOF缓冲区和AOF重写缓冲区,如下图所示。
也就是说在子进程执行AOF重写期间,服务器需要执行客户端发来的命令,将执行后的写命令追加到AOF缓冲区和AOF重写缓冲区。
当子进程完成AOF重写工作后,会向父进程发送一个信号,父进程接受到信号后,调用一个信号处理函数,执行以下工作:
- 将AOF重写缓冲区中的所有内容写入到新的AOF文件中,这时新的AOF文件所保存的数据库状态将和服务器当前的数据库状态一致。
- 对新的AOF文件进行改名,原子地覆盖现有AOF文件,完成新旧两个文件的替换。
这个函数调用完成后,父进程就可以正常的接收命令请求了。在整个AOF后台重写过程中,只有信号处理函数会阻塞父进程,其他都不会,将AOF重写对服务器性能造成的影响降了最低。
6. 重点回顾
- aof文件通过保存所有修改数据库的写命令请求来记录服务器的数据库状态。
- aof文件中的所有命令都以redis命令请求协议的格式保存。
- 命令请求会先保存到aof缓冲区里面,之后再定期写入并同步到aof文件。
- appendfsync选项的不同值对aof持久化功能的安全性以及redis服务器的性能有很大的影响。
- aof重写是一个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无须对现有aof文件进行任何读入,分析或者写入操作。
- 在执行BGREWRITEAOF命令时,redis服务器会维护一个aof重写缓冲区,该缓冲区会在子进程创建新aof文件期间,记录服务器所执行的所有写命令。当子进程完成创建新的aof文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新aof文件的末尾,使得新旧两个aof文件所保存的数据库状态一致。最后,服务器用新的aof文件替换旧的aof文件,以此来完成aof文件重写操作。
7.触发AOF重写的条件(待补充)
- AOF重写可以由用户通过调用BGREWRITEAOF手动触发。
- 自动触发机制
redis如何保证持久化,突然断电情况下????