持久化(怎么保证Redis挂掉后再重启数据可以进行恢复)
什么是持久化
持久化:持久化数据就是将内存中的数据写到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后再恢复数据)
持久化流程
redis的数据可以保存到磁盘上,持久化的流程是什么样的?
- 客户端向服务端发送写操作(数据在客户端的内存中)。
- 数据库服务端接收到写请求的数据(数据在服务端的内存中)。
- 服务端调用write这个系统调用,将数据往磁盘上写(数据在系统内存的缓冲区中)。
- 操作系统将缓冲区中的数据转移到磁盘控制器上(数据在磁盘缓存中)。
- 磁盘控制器将数据写到磁盘的物理介质中(数据真正落到磁盘上)。
Redis支持持久化,支持两种不同的持久化操作。第一种是持久化方式快照(RDB),另一种是只追加文件(AOF)
RDB(快照持久化)
什么是RDB
是指在指定的时间间隔内将内存中的数据集写入磁盘,也就是快照,它恢复是将快照文件直接读入到内存。 将内存中的数据以快照的方式写入到二进制文件中,默认的文件名问dump.rdb
在我们安装了redis之后,所有的配置都是在redis.conf文件中,里面保存了RDB和AOF两种持久化机制的各种配置。
Redis会单独创建(fork)一个子进程进行持久化,会将数据写入到一个临时文件中,待持久化的过程都结束,再用这个临时文件替换上次持久化的文件。整个过程主进程是不进行任何IO操作的,这就确保了极高的性能。
注意:如果需要进行大规模数据的恢复,且对数据恢复的完整性不是非常敏感,那么RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化的数据可能丢失。
fork
fork作用是复制一个与当前进程一样的进程。新进程的所有数据都和原进程一致,但是一个新的进程,并作为当前进程的子进程。
触发方式——手动触发
触发rdb持久化的方式有2种,分别是手动触发和自动触发。
save
:阻塞当前服务器,Redis不能处理其他命令,直到RDB过程完成为止,对于内存比较大的的实例会造成长时间的阻塞。
只管保存,其他不管,全部阻塞(不能进行get set操作)。
bgsave
命令:Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求
Redis进程执行fork
操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短
bgsave
流程:
具体流程如下:
- redis客户端执行
bgsave
命令或者自动触发bgsave
命令 - 主进程判断当前是否已经存在正在执行的子进程,如果存在,那么主进程直接返回。
- 如果不存在正在执行的子进程,
fork
一个新的子进程进行持久化数据,fork过程就是阻塞的,fork操作完成后主进程即可执行其他操作。 - 子进程先将数据写入到临时的
rdb
文件中,待快照数据写入完成后再原子替换旧的rdb文件 - 同时发送信号给主进程,通知主进程rdb持久化完成,主进程更新相关的统计信息(info Persitence下的rdb_*相关选项)
触发方式——自动触发
以下四种情况会自动触发
-
redis.conf
中配置save m n,即在m秒内有n次修改时,自动触发bgsave生成rdb文件
; - 主从复制时,
将节点从主节点进行全量复制时也会触发bgsave命令
,生成当时的快照发送到从节点。 - 执行debug reload 命令重新加载Redis时也会触发bgsave操作
- 默认情况下执行shutdown命令时,如果没有开启aof持久化,也会触发bgsave操作
redis.conf
中配置RDB
快照周期:内存快照虽然可以通过技术人员手动执行SAVE
或BGSAVE
命令来进行,但生产环境下多数情况都会设置其周期性执行条件。
Redis中默认的周期性设置
# 周期性执行条件的设置格式为
save <seconds> <changes>
# 默认的设置为:
save 900 1
save 300 10
save 60 10000
# 以下设置方式为关闭RDB快照功能
save ""
代码解释:
- 如果有900秒内有1条key信息发生变化,则进行快照
- 如果300秒内有10条key信息发生变化,则进行快照
- 如果60秒内有10000条key信息发生变化时,则进行快照
其他相关配置
- dbfilename:RDB文件在磁盘上的名称。
# 文件名称
dbfilename dump.rdb
dir:RDB文件的存储路径。默认设置为“./”,也就是Redis服务的主目录。
# 文件保存路径
dir /home/work/app/redis/data/
stop-writes-on-bgsave-error:(如果持久化出错,主进程是否停止写入。)在快照操作正常情况下,在快照进行过程中,主进程照样可以接受客户端的任何写操作的特性。如果快照操作出现异常时(例如操作系统用户权限不够、磁盘空间写满等等),Redis就会禁止写操作。
这个特性的主要目的是使运维人员在第一时间就发现Redis的运行错误,并进行解决。 默认值为yes
stop-writes-on-bgsave-error yes
rdbcompression
:是否压缩。该属性将在字符串类型的数据被快照到磁盘文件时,启用LZF压缩算法。Redis官方的建议是请保持该选项设置为 yes
# 是否压缩
rdbcompression yes
rdbchecksum
:是否设置检查。这个功能大概会多损失10%左右的性能,但获得了更高的数据可靠性。
# 导入时是否检查
rdbchecksum yes
如何保证数据一致性
由于生产环境中我们为Redis开辟的内存区域都比较大(例如6GB),那么将内存中的数据同步到硬盘的过程可能就会持续比较长的时间,而实际情况是这段时间Redis服务一般都会收到数据写操作请求。那么如何保证数据一致性呢?
RDB中的核心思路是Copy-on-Write,来保证在进行快照操作的这段时间,写入磁盘上的数据在内存中不会发生变化。
在正常的快照操作中,一方面Redis主进程会fork一个新的快照进程专门来做这个事情,这样保证了Redis服务不会停止对客户端包括写请求在内的任何响应。另一方面这段时间发生的数据变化会以副本的方式存放在另一个新的内存区域,待快照操作结束后才会同步到原来的内存区域。
解释:如果主线程对这些数据也是读操作(例如图中的键值对A),那么主线程bgsave子线程之间相互不影响
但是,如果主线程需要一块数据例如,图中键值对C,那么这块数据就会被复制一份,
生成该数据的的副本
。
然后bgsave子线程把这个副本数据写入到RDB文件中
,而这个过程中,主线程仍然可以直接修改原来的数据
在进行快照操作的这段时间,如果发生服务崩溃怎么办?
在快照操作过程中不能影响上一次备份的数据,将上一次完整的RDB快照文件作为恢复内存你的参考。
redis服务器会在磁盘上创建一个临时文件进行数据操作,待操作成功,采用临时文件备份替换上一次的备份。
可以每秒做一次快照吗?
对于快照来说,所谓“连拍”就是指连续地做快照。这样一来,快照的间隔时间变得很短,即使某一时刻发生宕机了,因为上一时刻快照刚执行,丢失的数据也不会太多。但是,这其中的快照间隔时间就很关键了
bgsave 执行时不阻塞主线程,但是,如果频繁地执行全量快照,也会带来两方面的开销:
一方面:频繁将全量数据血热磁盘,会给磁盘带来很大压力,多个快照竞争有限的磁盘带宽,前一个快照还没有做完,后一个快照开始,容易造成恶性循环
另一方面:bgsave
子线程需要通过fork
操作从主线程创建出来。虽然子线程在创建后不会阻塞主线程,但是fock
创建过程中本身会阻塞主线程,而是主线程的内存越大,阻塞时间越长。如果频繁fock出bgsave子进程,这就会频繁阻塞主线程
解决办法:增量快照,就是做了一次全量快照后,后续的快照只对修改的数据进行快照记录,这样可以避免每次全量快照的开销。但是有一个弊端,需要使用额外的元数据信息去记录哪些记录被数据修改,会带来额外的空间开销问题。
RDB优缺点
优点:
- RDB文件紧凑,全量备份,非常适合用于进行备份和灾难恢复。
- 生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。
- RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
缺点: - RDB方式实时性不够,无法做到秒级的持久化;
- 每次调用bgsave都需要fork子进程,fork子进程属于重量级操作,频繁执行成本较高;
- RDB文件是二进制的,没有可读性
AOF(只追加文件)
什么是AOF
全量备份总是耗时的,有时候我们提供一种更加高效的方式AOF,工作机制很简单,redis会将每一个收到的写命令都通过write函数追加到文件中。通俗的理解就是日志记录。
Redis是写后日志,先执行命令,把数据写入内存,然后才记录日志,先写内存后写日志
如何实现AOF
AOF日志记录Redis的每个写命令,步骤为:命令追加append
,文件写入write和
文件同步sync
- 命令追加 当AOF持久化功能打开了,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器的
aof_buf
缓冲区。 - 文件写入和同步
**关于何时将 aof_buf 缓冲区的内容写入AOF文件中,Redis提供了三种写回策略?**
Always
:同步写回,每个写命令写完,立马同步将日志写会到磁盘Everysec
:每秒写回,每个命令执行完,只是先把日志写到AOF缓冲区,每隔一秒把缓冲区写入磁盘No
:操作系统控制的写回,每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
redis.conf中配置AOF
默认是没有开启AOF 的,可以通过配置redis.conf
文件来开启AOF持久化
但是redis启动时 ,先加载的是aof
文件
什么是重写
AOF的方式也同时带来了另一个问题。持久化文件会变的越来越大。为了压缩aof的持久化文件。redis提供了bgrewriteaof
命令。将内存中的数据以命令的方式保存到临时文件中,同时会fork
出一条新进程来将文件重写。
重写的触发方式
手动执行 bgrewriteaof
触发AOF重写
在redis.conf
文件中配置重写的条件,如
auto-aof-rewrite-min-size 64MB // 当文件小于64M时不进行重写
auto-aof-rewrite-min-percenrage 100 // 当文件比上次重写后的文件大100%时进行重写
Redis会记录上次重写时的AOF大小,默认设置是当AOF文件大小是是上次重写大小的一倍且文件大小大于64M时触发。
重写原理
AOF重写过程是由后台进程bgrewriteaof
来完成的。
主线程fork出后台的bgrewriteaof
子进程,fork会把主线程的内存拷贝一份给bgrewriteaof子进程,这里面就包含了数据库的最新数据。然后,bgrewriteaof子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。
AOF在重写时,在fork进程时是会阻塞住主线程
重写过程
- 从主进程中fork出子进程,并拿到fork时的AOF文件数据写到一个临时AOF文件中
- 在重写过程中,redis收到的命令会同时写到AOF缓冲区和重写缓冲区中,这样保证重写不丢失重写过程中的命令
- 重写完成后通知主进程,主进程会将AOF缓冲区中的数据追加到子进程生成的文件中
- redis会原子的将旧文件替换为新文件,并开始将数据写入到新的aof文件上
重写日志时,有新数据写入咋整?
子进程在进行AOF重写期间,服务器进程还要继续处理命令请求,而新的命令可能对现有的数据进行修改,这会让当前数据库的数据和重写后的AOF文件中的数据不一致。
解决办法:
为了解决这种数据不一致的问题,Redis增加了一个AOF重写缓存,这个缓存在fork出子进程之后开始启用,Redis服务器主进程在执行完写命令之后,会同时将这个写命令追加到AOF缓冲区和AOF重写缓冲区,即子进程在执行AOF操作时,主要进程需要执行三个工作
- 执行client发来的命令请求;
- 将写命令追加到现有的AOF文件中;
- 将写命令追加到AOF重写缓存中。
可以保证
- AOF缓冲区的内容会定期被写入和同步到AOF文件中,对现有的AOF文件的处理工作会正常进行
- 从创建子进程开始,服务器执行的所有写操作都会被记录到AOF重写缓冲区中;
完成AOF重写之后
当子进程完成对AOF文件重写之后,它会向父进程发送一个完成信号,父进程接到该完成信号之后,会调用一个信号处理函数,该函数完成以下工作
- 将AOF重写缓存中的内容全部写入到新的AOF文件中;这个时候新的AOF文件所保存的数据库状态和服务器当前的数据库状态一致;
- 对新的AOF文件进行改名,原子的覆盖原有的AOF文件;完成新旧两个AOF文件的替换
总结
AOF重写的目的是为了解决AOF文件体积膨胀的问题,使用更小的体积来保存数据库状态,整个重写过程基本上不影响Redis主进程处理命令请求;整个过程中只有最后的主进程写入命令到AOF缓存和对新的AOF文件进行改名覆盖原有的AOF文件会造成阻塞。
RDB和AOF混合方式
内存快照以一定的频率执行,**在两次快照之间,**使用 AOF 日志记录这期间的所有命令操作。
如上图所示:T1时刻和T2时刻的修改,用AOF日志记录,等到第二次作全量快照时,就可以清空AOF日志,因为修改的记录已经记录到快照了,会服就不再用日志了。
从持久化中恢复数据
数据的备份、持久化做完了,我们如何从这些持久化文件中恢复数据呢?如果一台服务器上有既有RDB文件,又有AOF文件,该加载谁呢?
从文件中恢复数据,只需要重启Redis即可,通过下图了解
- redis重启时判断是否开启aof,如果开启了aof,那么就优先加载aof文件;
- 如果aof存在,那么就去加载aof文件,加载成功的话redis重启成功,如果aof文件加载失败,那么会打印日志表示启动失败,此时可以去修复aof文件后重新启动;
- 若aof文件不存在,那么redis就会转而去加载rdb文件,如果rdb文件不存在,redis直接启动成功;
- 如果rdb文件存在就会去加载rdb文件恢复数据,如加载失败则打印日志提示启动失败,如加载成功,那么redis重启成功,且使用rdb文件恢复数据;
总结