什么是Redis持久化?
Redis读写速度快、性能优越是因为它将所有数据存在了内存中,然而,当Redis进程退出或重启后,所有数据就会丢失。所以我们希望Redis能保存数据到硬盘中,在Redis服务重启之后,原来的数据能够恢复,这个过程就叫持久化。
Redis持久化的两种方式?RDB和AOF
AOF:会将每次执行的命令及时保存到硬盘中,实时性更好,丢失的数据更少
RDB:会根据指定的规则定时将内存中的数据保存到硬盘中。
通常两种方式结合使用,下面详细介绍RDB和AOF
AOF
AppendOnlyFile,AOF会保存服务器执行的所有写操作到日志文件中,在服务重启以后,会执行这些命令来恢复数据。
日志文件默认为appendonly.aof,Redis以Redis协议格式将命令保存至aof日志文件末尾,aof文件还会被重写,使aof文件的体积不会大于保存数据集状态所需要的实际大小
默认情况下,aof没有被开启。需要在redis.conf开启
appendonlyyes
日志文件名
appendfilenameappendonly.aof
日志文件所在目录(RDB日志文件也是在这里)
dir./
fsync持久化策略
appendfsynceverysec
always:命令写入aof_buf后立即调用系统fsync操作同步到AOF文件,fsync完成后线程返回。这种情况下,每次有写命令都要同步到AOF文件,硬盘IO成为性能瓶颈,Redis只能支持大约几百TPS写入,严重降低了Redis的性能;即便是使用固态硬盘(SSD),每秒大约也只能处理几万个命令,而且会大大降低SSD的寿命。no:命令写入aof_buf后调用系统write操作,不对AOF文件做fsync同步;同步由操作系统负责,通常同步周期为30秒。这种情况下,文件同步的时间不可控,且缓冲区中堆积的数据会很多,数据安全性无法保证。everysec:命令写入aof_buf后调用系统write操作,write完成后线程返回;fsync同步文件操作由专门的线程每秒调用一次。everysec是前述两种策略的折中,是性能和数据安全性的平衡,因此是Redis的默认配置,也是我们推荐的配置。
其余参数:
●no-appendfsync-on-rewriteno:在重写AOF文件的过程中,是否禁止fsync。如果这个参数值设置为yes(开启),则可以减轻重写AOF文件时CPU和硬盘的负载,但同时可能会丢失重写AOF文件过程中的数据;需要在负载与安全性之间进行平衡。
●auto-aof-rewrite-percentage:指定Redis重写AOF文件的条件,默认为,它会对比上次生成的AOF文件大小。如果当前AOF文件的增长量大于上次AOF文件的%,就会触发重写操作;如果将该选项设置为0,则不会触发重写操作。
●auto-aof-rewrite-min-size64mb:指定触发重写操作的AOF文件的大小,默认为64MB。如果当前AOF文件的大小低于该值,此时就算当前文件的增量比例达到了auto-aof-rewrite-percentage选项所设置的条件,也不会触发重写操作。换句话说,只有同时满足以上这两个选项所设置的条件,才会触发重写操作。
●auto-load-truncatedyes:当AOF文件结尾遭到损坏时,Redis在启动时是否仍加载AOF文件。
AOF持久化的实现
(1)命令追加(append):Redis服务器每执行一条写命令,这条写命令都会被追加到缓存区aof_buf中。(避免每次执行的命令都直接写入硬盘中,会导致硬盘I/O的负载过大,使得性能下降。)
命令追加的格式使用Redis命令请求的协议格式
(2)AOF持久化文件写入(write)和文件同步(sync):根据appendfsync参数设置的不同的同步策略,将缓存区aof_buf中的数据内容同步到硬盘中。
先了解操作系统的write和fsync函数。为了提高文件的写入效率,当用户调用write函数将数据写入文件中时,操作系统会将这些数据暂存到一个内存缓存区中,当这个缓存区被填满或者超过了指定时限后,才会将缓存区中的数据写入硬盘中,这样做既提高了效率,又保证了安全性。
Redis的服务器进程是一个事件循环(loop),这个事件循环中的文件事件负责接收客户端的命令请求,处理之后,向客户端发送命令回复;而其中的时间事件则负责执行像serverCron函数这样需要定时运行的函数。
服务器在处理文件事件时,可能会执行客户端发送过来的写命令,使得一些命令被追加到缓存区aof_buf中。因此,在服务器每次结束一个事件循环之前,都会调用flushAppendOnlyFile函数,来决定是否将缓存区aof_buf中的数据写入和保存到AOF文件中。
flushAppendOnlyFile函数的运行与服务器配置的appendfsync参数有关。
AOF文件的重写
AOF文件定期重写,以达到压缩的目的。
AOF文件过于庞大,会影响Redis的写入速度,在执行数据恢复时,也非常满。AOF文件重写就是解决这个问题。
AOF文件重写就是把Redis进程内的数据转化为写命令,然后同步到新的AOF文件中。在重写的过程中,Redis服务器会创建一个新的AOF文件来替代现有的AOF文件,新、旧两个AOF文件所保存的数据库状态相同,但是新的AOF文件不会包含冗余命令。
AOF文件重写并不会对旧的AOF文件进行读取、写入操作,这个功能是通过读取服务器当前的数据库状态来实现的。
举例说明:
比如Redis使用五条rpush命令分别插入五种颜色
AOF重写后
RPUSHcolor〝yellow〝〝green〝〝black〝〝pink〝〝white〝
所以AOF重写的原理:
●AOF文件重写功能会丢弃过期的数据,也就是过期的数据不会被写入AOF文件中。
●AOF文件重写功能会丢弃无效的命令,无效的命令将不会被写入AOF文件中。无效命令包括重复设置某个键值对时的命令、删除某些数据时的命令等。
●AOF文件重写功能可以将多条命令合并为一条命令,然后写入AOF文件中。
怎么触发AOF重写?
●手动触发:执行BGREWRITEAOF命令触发AOF文件重写。该命令与BGSAVE命令相似,都是启动(fork)子进程完成具体的工作,且都在启动时阻塞。
●自动触发:自动触发AOF文件重写是通过设置Redis配置文件中auto-aof-rewrite-percentage和auto-aof-rewrite-min-size参数的值,以及aof_current_size和aof_base_size状态来确定何时触发的。
auto-aof-rewrite-percentage参数是在执行AOF文件重写时,当前AOF文件的大小(aof_current_size)和上一次AOF文件重写时的大小(aof_base_size)的比值,默认为。
auto-aof-rewrite-min-size参数设置了执行AOF文件重写时的最小体积,默认为64MB。
AOF文件的后台重写
AOF重写执行大量的写入操作,就会使得这个函数的线程被长时间阻塞。Redis服务器使用单线程来处理命令请求。如果让服务器直接调用aof_rewrite重写函数,那么在AOF文件重写期间,服务器将不能继续执行其他命令,就会一直处于阻塞状态。
Redis将AOF文件重写程序放到了一个子进程中执行,这样做的好处是:
●子进程在执行AOF文件重写的过程中,Redis服务器进程可以继续处理新的命令请求。
●子进程带有服务器进程的数据副本,使用子进程可以在使用锁的情况下,保证数据的安全性。
使用子进程会导致数据库状态不一致,原因是:当子进程进行AOF文件重写的时候,Redis服务器可以继续执行来自客户端的命令请求,就会有新的命令对现有数据库状态进行修改,进而使得服务器当前的数据库状态与重写的AOF文件所保存的数据库状态不一致。
为了解决使用子进程导致数据库状态不一致的问题,Redis服务器设置了一个AOF文件重写缓存区。这个AOF文件重写缓存区在服务器创建子进程之后开始使用,可以利用它来解决数据库状态不一致的问题。当Redis服务器成功执行完一条写命令后,它会同时将这条写命令发送给AOF文件缓存区(aof_buf)和AOF文件重写缓存区。
子进程在执行AOF文件重写的过程中,服务器进程的执行过程如下:
(1)服务器接收到来自客户端的命令请求,并成功执行。
(2)服务器将执行后的写命令转化为对应的协议格式,然后追加到AOF文件缓存区(aof_buf)中。
(3)服务器再将执行后的写命令追加到AOF文件重写缓存区中。
有了AOF文件重写缓存区,就可以保证数据库状态的一致性。AOF文件缓存区的内容会被定期写入和同步到AOF文件中,AOF文件的写入和同步不会因为AOF文件重写缓存区的引入而受到影响。当服务器创建子进程之后,服务器执行的所有写命令都会同时被追加到AOF文件缓存区和AOF文件重写缓存区中。
如果子进程完成了AOF文件重写的工作,它就会发送一个完成信号给父进程。当父进程接收到这个信号后,就会调用信号处理函数,继续执行以下工作:
(1)将AOF文件重写缓存区中的所有内容写入新的AOF文件中。新的AOF文件所保存的数据库状态与服务器当前的数据库状态保持一致。
(2)修改新的AOF文件的文件名,新生成的AOF文件将会覆盖现有(旧)的AOF文件,完成新、旧两个文件的互换。
在完成上述两个步骤之后,就完成了一次AOF文件后台重写工作。
在整个AOF文件后台重写的过程中,只有在信号处理函数执行的过程中,服务器进程才会被阻塞,在其他时候不存在阻塞情况。
AOF文件恢复数据的过程
(1)创建一个伪客户端,用于执行AOF文件中的写命令。这个伪客户端是一个不带网络连接的客户端。因为只能在客户端的上下文中才能执行Redis的命令,而在AOF文件中包含了Redis服务器启动加载AOF文件时所使用的所有命令,而不是网络连接,所以服务器创建了一个不带网络连接的伪客户端来执行AOF文件中的写命令。
(2)读取AOF文件中的数据,分析并提取出AOF文件所保存的一条写命令。
(3)使用伪客户端执行被读取出的写命令。
(4)重复执行步骤(2)和(3),直到将AOF文件中的所有命令读取完毕,并成功执行为止。这个过程完成之后,就可以将服务器的数据库状态还原为关闭之前的状态。
如果在Redis服务器启动加载AOF文件时,发现AOF文件被损坏了,那么服务器会拒绝加载这个AOF文件,以此来确保数据的一致性不被破坏。而AOF文件被损坏的原因可能是程序正在对AOF文件进行写入与同步时,服务器出现停机故障。如果AOF文件被损坏了,则可以通过以下方法来修复。
●及时备份现有AOF文件。
●利用Redis自带的redis-check-aof程序,对原来的AOF文件进行修复,命令如下:
●使用diff-u来对比原始AOF文件和修复后的AOF文件,找出这两个文件的不同之处。
●修复AOF文件之后,重启Redis服务器重新加载,进行数据恢复。
AOF持久化的优劣
使用AOF持久化会让Redis持久化更长:通过设置不同的fsync策略来达到更长的持久化。具体有3种策略。兼容性比较好:AOF文件是一个日志文件,它的作用是记录服务器执行的所有写命令。当文件因为某条写命令写入失败时,可以使用redis-check-aof进行修复,然后继续使用。支持后台重写:当AOF文件的体积过大时,在后台可以自动地对AOF文件进行重写,因此数据库当前状态的所有命令集合都会被重写到AOF文件中。重写完成后,Redis就会切换到新的AOF文件,继续执行写命令的追加操作。AOF文件易于读取和加载:AOF文件保存了对数据库的所有写命令,这些写命令采用Redis协议格式追加到AOF文件中,因此非常容易读取和加载。AOF持久化具有以下缺点:
●AOF文件的体积会随着时间的推移逐渐变大,导致在加载时速度会比较慢,进而影响数据库状态的恢复速度,性能快速下降。
●根据所使用的fsync策略,使用AOF文件恢复数据的速度可能会慢于使用RDB文件恢复数据的速度。
●因为AOF文件的个别命令,可能会导致在加载时失败,从而无法进行数据恢复。
RDB
RDB持久化生成的RDB文件是一个经过压缩的二进制文件,也可以称之为快照文件,通过该文件可以还原生成RDB文件时的数据库状态
在指定的时间间隔内,Redis会自动将内存中的所有数据生成一份副本并存储在硬盘上,这个过程就是「快照」。
快照怎么生成
根据Redis配置文件redis.conf中的配置自动进行快照
当在时间m内被修改的键的个数大于n时,就会触发BGSAVE命令,服务器就会自动执行快照操作。
Redis的savemn命令是通过serverCron函数、dirty计数器及lastsave时间戳来实现的。
serverCron函数:这是Redis服务器的周期性操作函数,默认每隔毫秒执行一次,它主要的作用是维护服务器的状态。其中一项工作就是判断savemn配置的条件是否满足,如果满足就会触发执行BGSAVE命令。
dirty计数器:这是Redis服务器维持的一种状态,它主要用于记录上一次执行SAVE或BGSAVE命令后,服务器进行了多少次状态修改(执行添加、删除、修改等操作);当SAVE或BGSAVE命令执行完成后,服务器会将dirty重新设置为0。dirty计数器记录的是服务器进行了多少次状态修改,而不是客户端执行了多少次修改数据的命令。
lastsave时间戳:主要用于记录服务器上一次成功执行SAVE或BGSAVE命令的时间,它是Redis服务器维持的一种状态。
dirty计数器和lastsave时间戳属性都保存在服务器状态的redisServer结构中。
savemn命令的实现原理:服务器每隔毫秒执行一次serverCron函数;serverCron函数会遍历savemn配置的保存条件,判断是否满足。如果有一个条件满足,就会触发执行BGSAVE命令,进行快照保存。
对于每个savemn条件,只有以下两个条件同时满足才算满足:
当前服务器时间减去lastsave时间戳大于m。
当前dirty计数器的个数大于等于n。
●用户在客户端执行SAVE或BGSAVE命令时会触发快照(手动触发)。
●如果用户定义了自动快照条件,则执行FLUSHALL命令也会触发快照。
当执行FLUSHALL命令时,会清空数据库中的所有数据。如果用户定义了自动快照条件,则在使用FLUSHALL命令清空数据库的过程中,就会触发服务器执行一次快照。
●如果用户为Redis设置了主从复制模式,从节点执行全量复制操作,则主节点会执行BGSAVE命令,将生产的RDB文件发送给从节点完成快照操作。
快照的实现过程
(1)Redis调用执行fork函数复制一份当前进程(父进程)的副本(子进程),也就是同时拥有父进程和子进程。
(2)父进程与子进程各自分工,父进程继续处理来自客户端的命令请求,而子进程则将内存中的数据写到硬盘上的一个临时RDB文件中。
(3)当子进程把所有数据写完后,也就表示快照生成完毕,此时旧的RDB文件将会被这个临时RDB文件替换,这个旧的RDB文件也会被删除。这个过程就是一次快照的实现过程。
当Redis调用执行fork函数时,操作系统会使用写时复制策略。也就是在执行fork函数的过程中,父、子进程共享同一内存数据,当父进程要修改某个数据时(执行一条写命令),操作系统会将这个共享内存数据另外复制一份给子进程使用,以此来保证子进程的正确运行。因此,新的RDB文件存储的是执行fork函数过程中的内存数据。
写时复制策略也保证了在执行fork函数的过程中生成的两份内存副本在内存中的占用量不会增加一倍。但是,在进行快照的过程中,如果写操作比较多,就会造成fork函数执行前后数据差异较大,此时会使得内存使用量变大。因为内存中不仅保存了当前数据库数据,还会保存fork过程中的内存数据。
在进行快照生成的过程中,Redis不会修改RDB文件。只有当快照生成后,旧的RDB文件才会被临时RDB文件替换,同时旧的RDB文件会被删除。在整个过程中,RDB文件是完整的,因此我们可以使用RDB文件来实现Redis数据库的备份。
RDB文件
默认情况下,Redis将数据库快照保存在名为dump.rdb的文件中。这个文件可以修改,即AOF的dir和dbfilename属性
RDB文件结构:
在RDB文件结构中,通常使用大写字母表示常量,使用小写字母表示变量和数据。
●REDIS常量:该常量位于RDB文件的头部,它保存着「REDIS」5个字符,它的长度是5字节。在Redis服务器启动加载文件时,程序会根据这5个字符判断加载的文件是不是RDB文件。
●db_version常量:该常量用于记录RDB文件的版本号,它的值是一个用字符串表示的整数,占4字节,注意区分它不是Redis的版本号。
●databases数据:它包含0个或多个数据库,以及各个数据库中的键值对数据。
如果它包含0个数据库,也就是服务器的数据库状态为空,那么databases也是空的,其长度为0字节;如果它包含多个数据库,也就是服务器的数据库状态不为空,那么databases不为空,根据它所保存的键值对的数量、类型和内容不同,其长度也是不一样的。
如果databases不为空,则RDB文件结构如图
其中,SELECTDB是一个常量,表示其后的数据库编号,这里的0和1是数据库编号。
pairs数据:它存储了具体的键值对信息,包括键(key)、值(value)、数据类型、内部编码、过期信息、压缩信息等。
SELECT0pairs表示0号数据库;SELECT1pairs表示1号数据库。当数据库中有键值对时,RDB文件才会记录该数据库的信息;而如果数据库中没有键值对,这一部分就会被RDB文件省略。
●EOF常量:该常量是一个结束标志,它标志着RDB文件的正文内容结束,其长度为1字节。在加载RDB文件时,如果遇到EOF常量,则表示数据库中的所有键值对都已经加载完毕。
●check_sum变量:该变量用于保存一个校验和,这个校验和是通过对REDIS、db_version、databases、EOF4部分的内容进行计算得出的,是一个无符号整数,其长度为8字节。
当服务器加载RDB文件时,会将check_sum变量中保存的校验和与加载数据时所计算出来的校验和进行比对,从而判断加载的RDB文件是否被损坏,或者是否有错误。
RDB文件压缩
在默认情况下,Redis服务器会自动对RDB文件进行压缩。在Redis配置文件redis.conf中,默认开启压缩。配置如下:
Redis采用LZF算法进行RDB文件压缩。在压缩RDB文件时,不要误认为是压缩整个RDB文件。实际上,对RDB文件的压缩只是针对数据库中的字符串进行的,并且只有当字符串达到一定长度(20字节)时才会进行压缩。
RDB文件的创建
SAVE命令:会阻塞Redis服务器进程,此时Redis服务器将不能继续执行其他命令请求,直到RDB文件创建完毕为止。
BGSAVE命令:会派生出一个子进程,交由子进程将内存中的数据保存到硬盘中,创建RDB文件;而BGSAVE命令的父进程可以继续处理来自客户端的命令请求。
执行BGSAVE命令会返回Backgroundsavingstarted信息,但我们并不能确定BGSAVE命令是否已经成功执行,此时可以使用LASTSAVE命令来查看相关信息。
执行LASTSAVE命令返回一个UNIX格式的时间戳,表示最近一次Redis成功将数据保存到硬盘中的时间。
RDB文件的加载
RDB文件只有在启动服务器的时候才会被加载。当启动服务器时,它会检查RDB文件是否存在,如果存在,就会自动加载RDB文件。除此之外,RDB文件不会被加载,因为Redis中没有提供用于加载RDB文件的命令。
那么先加载AOF文件还是RDB文件呢?
●如果在Redis配置文件中开启了AOF持久化(appendonlyyes),那么在启动服务器的时候会优先加载AOF文件来还原数据库状态。
●如果在Redis配置文件中关闭了AOF持久化(appendonlyno),那么在启动服务器的时候会优先加载RDB文件来还原数据库状态。
RDB文件的配置
除了savemn、dbfilenamedump.rdb、dir./还有
●stop-writes-on-bgsave-erroryes:当执行BGSAVE命令出现错误时,Redis是否终止执行写命令。参数的值默认被设置为yes,表示当硬盘出现问题时,服务器可以及时发现,及时避免大量数据丢失;当设置为no时,就算执行BGSAVE命令发生错误,服务器也会继续执行写命令;当对Redis服务器的系统设置了监控时,建议将该参数值设置为no。
●rdb