Redis的数据是存储在内存的,当程序崩溃或者服务器宕机,那么内存里的数据就会丢失。所以避免数据丢失的情况,需要将数据保存到其他的存储设备中。
Redis提供两种方式来持久化,分别是
RDB(Redis Database):记录某个时刻的全部数据,本质是存储二进制数据的快照到磁盘,后续通过加载RDB文件恢复数据
AOF(Append Only File):记录执行的每条命令,通过重新执行命令来恢复数据。本质是记录操作日志,后续通过日志恢复数据。
记录某个时刻的全部数据,本质是:将某一时刻Redis服务器内存中的数据保存到磁盘中的二进制文件中(dump.rdb),后续通过加载RDB文件恢复数据。
RDB的触发包括手动触发,自动触发,关闭时持久化
手动触发包括主动执行sava命令,主动执行bgsave命令:
save命令:会在redis主线程执行持久化操作,会阻塞主线程,只有RDB持久化完成,才能响应客户端的请求,所以在生产模式的时候慎用(不用)。Redis在正常关闭时,会执行save命令来持久化数据
bgsave命令:background save,即后台保存。Redis会创建一个子进程来生成快照文件,创建子进程可能会有短暂的阻塞,而父进程(Redis的服务器进程)会继续处理客户端的请求。一般用的是这个命令
自动触发:
用户可以在Redis的配置文件中进行设置(默认开启),Redis会根据设置自动调用bgsave。比如,
- #save
- save 900 1 表示每900秒内,有1条写数据操作,则触发bgsave
- save 300 10 多条命令是并集关系,如果满足其中一个就会触发
Redis官网描述:
How it works Whenever Redis needs to dump the dataset to disk, this is what happens: 1.Redis forks. We now have a child and a parent process. 2.The child starts to write the dataset to a temporary RDB file. 3.When the child is done writing the new RDB file, it replaces the old one. This method allows Redis to benefit from copy-on-write semantics.
Redis fork出一个子进程
子进程把数据写入临时的RDB文件
写完之,新RDB文件替换旧RDB文件
最后还有一句话:这种方式让Redis从”写时复制“技术收益。也就是,Redis在执行持久化的时候,依然可以处理客户端的命令,数据是可以被修改的。
具体来讲,fork创建子进程后,子进程会复制父进程的页表,页表指向的物理地址是同一个,所以子进程和父进程时共享同一片数据的。当父进程处理客户端请求,发生内存数据修改时,物理内存才会被复制一份给子进程,所以子进程的持久化操作是不会被影响。
记录执行的每条命令,通过执行命令来恢复数据。本质是记录操作日志,后续通过日志恢复数据。
AOF日志文件其实就是普通的文本,可以通过cat命令查看里面的内容。
Redis先执行写操作命令后,再将该命令记录到AOF日志中。有以下几点好处:
避免额外的检查开销。如果命令的语法有问题又不进行检查,直接先记录到AOF日志里,等到Redis使用日志文件恢复数据的时候,就会出错。所以先执行命令后,只有命令执行成功才记录到AOF日志中,就能避免额外的检查开销,保证AOF日志里的命令是正确的。
不会阻塞当前操作命令的执行。
当然也存在一定风险:
执行写操作和记录AOF日志是两个过程,当Redis还没有来得及将命令写入硬盘时,服务器宕机,数据就有丢失的风险
会阻塞下一个操作命令的执行。
因为执行命令和记录AOF日志都是在主进程完成的
appendonly no # The name of the append only file (default: "appendonly.aof") appendfilename "appendonly.aof"
默认AOF是关闭的,通过设置appendonly为yes可开启
将数据写入AOF缓冲区中,这个缓冲区叫aof_buf,是一个sds数据
将aof_buf的数据写入AOF文件,此时还没有写入硬盘,而是拷贝到内核缓冲区page cache中。一共有4个时机,会调用一个flushAppendOnlyFile的函数,这个函数会使用write函数来将数据写入操作系统缓冲区
处理完事件后,等待下一次事件之前,也就是beforeSleep中
周期函数serverCron中
服务器退出之前的准备工作时
通过配置指令关闭AOF功能时
内核缓冲区page cache:是操作系统内核中用于缓存磁盘数据的一部分内存区域
硬盘缓冲区:是硬盘或磁盘控制器中的硬件缓存
将内核缓冲区的数据写入磁盘。即调用系统的flush函数,刷盘其实还是在flushAppendOnlyFile函数中,但是不一定调用了flushApeendOnlyFile,flush就一定会被调用。这里支持了刷盘时机的配置。
- /* Perform the fsync if needed. */
- if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
- /* redis_fsync is defined as fdatasync() for Linux in order to avoid
- * flushing metadata. */
- latencyStartMonitor(latency);
- redis_fsync(server.aof_fd); /* Let's try to get this data on the disk */
- latencyEndMonitor(latency);
- latencyAddSampleIfNeeded("aof-fsync-always",latency);
- server.aof_last_fsync = server.unixtime;
- } else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC &&
- server.unixtime > server.aof_last_fsync)) {
- if (!sync_in_progress) aof_background_fsync(server.aof_fd);
- server.aof_last_fsync = server.unixtime;
- }
注意:
为什么先将数据写入aof缓冲中,而不直接同步到磁盘呢?
因为实时写入磁盘会带来很高的磁盘IO,影响性能
Redis提供了三种写入策略,决定了AOF日志什么时候写入磁盘,包括
appendfsync always:每次写操作命令执行完之后,同步将AOF日志数据写入磁盘。
appendfsync everysec:每次写操作命令执行完之后,先将命令写入到AOF文件的内核缓冲区,然后每隔一秒将缓冲区里的内容写入磁盘
appendfsync no:Redis不去控制写入硬盘的时机,由操作系统控制。每次先将命令写入AOF文件的内核缓冲区,再由操作系统决定何时写入硬盘。一般情况linux会每30秒刷一次盘。这种策略对性能的影响最小,但发生崩溃时会丢失较多数据。
Redis的默认设置是每秒钟同步一次数据到磁盘,提供较好的性能和数据可靠性。也需要根据实际业务来选择,比如只是简单的缓存,不存在热点缓存,就可以30秒同步一次数据到磁盘中。Always策略每次执行写操作就同步将AOF日志写入硬盘,影响主进程的性能。
根据业务场景进行选择:
如果要高性能,就选No策略
如果要高可靠,就选Always策略
如果折中,较好的性能和可靠性,就选Everysec策略
随着命令越来越多,AOF文件会越来越大,不仅占用大量磁盘空间,而且恢复数据也会变慢。
在AOF文件中,有些命令是没有作用的,比如一开始set一个数据为10,后来又set为100,那么前面set为10就没有作用了,完全可以把前面set为10的命令去掉。
所以,AOF重写机制的妙处在于,尽管某个键值对被多条命令重复修改,最终也只需用一条命令去记录这个键值对当前的最新状态,进而减少AOF日志中的命令数量。
写入AOF日志的操作是在主进程完成的,因为它写入的内容不多,所以不太影响命令的操作。
而AOF重写时,需要读取所有缓存的键值对数据,为每一个键值对生成一条命令,然后写入到新的AOF文件,重写完后,将旧AOF文件替换掉。所以AOF过程由后台子进程来完成,避免阻塞主进程。
这里使用子进程而不是线程,因为如果是使用线程,多线程之间会共享内存,那么在修改共享内存数据的时候,需要通过加锁来保证数据的安全,而这样就会降低性能。而使用子进程,创建子进程时,父子进程是共享内存数据的,不过这个共享的内存只能以只读的方式,而当父子进程任意一方修改了该共享内存,就会发生「写时复制」,于是父子进程就有了独立的数据副本,就不用加锁来保证数据安全。
重写时,主进程会fork出一个子进程,由子进程将这些Redis数据写入重写日志
在子进程进行重写期间,主进程也会将新的写操作写入到AOF重写缓冲区中
当新的AOF文件创建完毕,Redis就会将重写缓冲区的内容追加到新的AOF文件,新AOF文件替换旧AOF文件
所以,尽管重写过程中有新数据写入,通过追加AOF重写缓冲的方式到新文件,依然可以保证不会丢失最新的写入操作
混合持久化发生在原有的AOF流程。开启了混合持久化,在AOF重写日志的时候,fork出来的子进程会将内存数据以RDB的方式写入新的AOF文件中,期间主进程将写操作命令写入重写缓冲区中,最后追加到新的AOF文件中。
此时的AOF文件前半部分是RDB格式的全量数据,后半部分是AOF格式的增量数据。
既有RDB文件小,加载快的优势,也有AOF持久化数据损失少的优势。但因为含有二进制数据,可读性会差一些。
混合持久文件里有REDIS这个标记,加载时能通过这个标记进行判断是否开启了混合持久化。
可以通过配置文件开启,5.0之后默认是打开的,所以5.0之后只要AOF配置开启,默认就是混合持久化。