3.1Redis如何避免数据丢失?
Redis在大多数场景中,都是用于查询数据库的缓存层来使用的,它把数据库中的数据取出存储在内存中,然后直接从内存中读取数据,响应速度就会非常快了,但是一旦服务器宕机,内存中的数据将会全部丢失。Redis的持久化主要有两大机制:AOF和RDB。
【AOF是如何实现的】?
与关系型数据库的写前日志WAL不同,Redis是先执行命令,把数据写入内存,然后才记录日志。因为Redis在向AOF里面记录日志的时候,并不会先去对这些命令进行语法检查。所以先记录日志再执行命令的话,日志中就会可能会记录错误的命令,Redis在使用日志恢复时,就有可能会出错。写后日志的好处就是只有Redis的命令执行成功以后才会记录日志,如果命令执行失败,就会直接向服务端报错,避免记录错误日志。它是在命令执行后才记录日志,所以不会阻塞当前的写操作。
日志中记录的其实就是Redis的RESP协议解析后的命令,比如 set testkey testvalue,这个命令主要由三个部分组成所以是*3,其中每个命令占多少字节用$符号表示,随后再跟上具体的命令文本。
【AOF潜在的风险】
(1)如果刚执行完一个命令,还没来得及记录日志就宕机了,那这个命令和相应的数据就有丢失的风险。
(2)其次,AOF虽然避免了对当前命令的阻塞,但可能会给下一个操作带来阻塞风险。这是因此AOF日志也是在主线程中执行的,如果在把日志写入磁盘时,磁盘写压力大,就会导致写盘很慢,进而导致后续的操作也无法执行了。
【三种写回策略】
Always:同步写回,每个写命令执行完成之后,立马同步把日志写回磁盘;基本可以做到不丢数据,但每个写命令执行完后都有个慢速的落盘操作,不可避免得回影响主线程性能。
EverySec:每秒写回,每个写命令执行完后,只是把日志写到AOF文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;避免了同步写回的性能开销,虽然减少了对系统性能的影响,但是上一秒未落盘的命令操作依然会丢失。
No:操作系统控制的写回,每个写命令执行完后,只是把日志写到AOF文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。在写完缓冲区后,就可以继续执行后续的命令,但是落盘的控制时间不在Redis手上,只要AOF记录不在磁盘中,一旦宕机数据就会有丢失的风险。
很多时候做系统开发设计都是高性能和高可靠性取一个折中的平衡。
【日志文件太大了怎么办?】
AOF重写机制是在重写时,Redis根据数据库的现状创建一个新的AOF文件,也就是说读取数据库中所有的键值对,然后每个键值对用一条命令记录它的写入。AOF重写机制可以把日志文件变小,重写机制具有多变一的功能,就是在旧日志文件中的多条命令,在重写后的新日志中变成一条命令,在重写时根据当前键值对的最新状态,生成相应的写入命令。
【AOF重写会阻塞吗】
AOF重写日志与AOF日志的写回不同,重写过程是由后台子进程bgrewriteaof来完成的,这也是为了避免阻塞主线程,导致数据库性能下降。重写的过程总结是”一个拷贝,两处日志”。
一个拷贝:每次执行重写时,主线程fork出后台的bgrewriteaof子进程。fork会把主线程的内存拷贝一份给bgrewriteaof子进程,这里面包含了数据库的最新数据,然后子进程在不影响主线程的情况下,逐一把拷贝的数据写成操作,计入重写日志。
两处日志:
(1)AOF日志:主线程不会阻塞,依然可以处理新来的操作。如果这时候有写操作,Redis依然会把这个操作写入到它的缓冲区的。这样即使发生了宕机,AOF日志操作依然是齐全的,可以用于恢复。
(2)AOF重写日志:最新的写操作,也会被写入到重写日志的缓冲区。这样重写日志也不会丢失最新的数据操作。等到拷贝数据完成以后,重写日志记录的这些最新操作也会写入到新的AOF文件中,然后用新文件替换旧文件。
总结来说就是,每次AOF重写时,Redis会先执行一个内存拷贝,用于重写;然后,使用两个日志保证在重写的过程中,新写入的数据不会丢失。而且,因为Redis采用额外的线程进行数据重写,所以这个操作不会阻塞主线程。但缺点就是,当Redis宕机以后,在用日志文件从磁盘中恢复数据到内存中时,命令还是一条条按顺序执行的,再加上Redis是单线程的,所以整个重放命令的过程就会很慢了。
3.2Redis如何实现快速恢复?
因为AOF在进行故障恢复时,需要把日志文件中的命令逐一执行,如果操作日志的命令非常多,Redis就会恢复得很缓慢,影响到正常的使用。所以Redis就提供了一种内存快照的技术用于快速恢复,内存快照就是把某个时刻中的内存数据做一个状态记录,就类似于拍照片一样。Redis把某一时刻的状态以文件的形式写入到磁盘中,这样一来,即使宕机,快照文件也不会丢失,数据的可靠性得到了保证。在做数据恢复时,直接可以RDB文件读入内存,实现快速恢复。
【RDB需要解决的两个问题】
(1)对哪些数据做快照?这关系到快照的执行效率问题。
Redis为了所有数据的可靠性保证,它执行的是全量快照,把内存中的所有数据都以RDB文件的形式记录到磁盘中。在做内存快照时,Redis主线程会使用bgsave命令创建出一个子进程,专门用于写入RDB文件,避免主线程的阻塞。
(2)做快照时,数据还能被增删改查吗?这关系到Redis是否会被阻塞,能否同时正常处理请求
避免阻塞和正常处理写操作不是一回事,比如在做快照的时候确实是子进程来做这些事,但是如果为了快照数据的完整性,主线程如果只能做读操作不能执行写操作的话,就会影响吞吐量了。所以Redis会借助操作系统提供的写时复制技术,在执行快照的同时,正常处理写操作。bgsava子进程是由主线程fork生成的,可以共享主线程的所有内存数据,如果主线程要修改一份数据,这块数据就会被复制一份,生成该数据的副本,主线程在数据副本上进行修改,同时bgsave子进程可以把原来的数据写入RDB文件。这既保证了快照读的完整性,也允许主线程同时对数据进程修改,避免对正常业务的影响。
【多久做一次快照?】
bgsave子进程需要fork操作从主线程中创建出来。虽然子进程在创建后不会影响主线程,但是fork操作本身会阻塞主线程,而且主线程的内存越大,阻塞时间越长。如果频繁地fork出bgsave子进程,就会频繁阻塞主线程了。此时就可以做一个增量快照,就是在做了一次全量快照以后,后续的快照只对修改的数据进行快照记录。
比如在记录T1-T2时间时刻之间,哪些键值对数据被修改了,只对这些数据做一次增量快照,但这需要使用额外的资源来记录这两个时刻Redis数据库中数据的变化位置。
【AOF与RDB混合使用】
内存快照以一定频率执行,在两次快照之间,使用AOF日志记录这期间的所有命令,这样一来,快照不用频繁得执行,避免频繁fork对主线程的影响。而且AOF日志也只用来记录两次快照间的操作,不需要记录所有操作了,也可以避免重写的开销。等到第二次做全量快照时,就可以清空AOF日志,因为此时的修改都已经同步记录到快照中了,恢复时就不用再记录日志了。
【AOF和RDB的选择问题】
(1)数据不能丢失时,内存快照和AOF的混合使用是一个很好的选择;
(2)如果允许分钟级别的数据丢失,可以只使用RDB;
(3)如果只用AOF,优先使用everysec的配置选项,因为它在可靠性和性能之间取了一个平衡。