Redis持久化
Redis提供如下的持久化选项:
- RDB 每隔指定的时间间隔就将当前时间点的数据集快照持久化。
- AOF 在服务端每次接收到写入操作请求时都持久化logs;在服务启动时,会对logs进行回放,以‘重构恢复’原始数据集。所有操作命令都会被通过Redis内部协议写入logs,当logs太大时,redis可以重写它。
- 如果只需要数据在redis运行期间存在,就可以完全关闭持久化。
- 将RDB和AOF结合。在服务启动时,AOF会被用于重建数据集。
- RDB和AOF各有优缺点,需要自行权衡。
RDB 优势
- RDB是存储Redis当前时间点数据集的压缩文件。RDB文件对备份来说是很完美的,可以灵活使用备份策略生成各种不同时间点(采用不同的备份频率生成,如每隔24小时/每隔30天生成一个文件)的数据文件作为灾备。
- RDB 可以很容易地用于灾备数据回复,作为压缩文件可以进行远距离传输。
- RDB 可以最大限度地提高Redis的性能,需要持久化时父进程只需要fork一个子进程,由此子进程来完成持久化任务,父进程不需要涉及I/O或者类似操作。
RDB 劣势
- RDB 不能在Redis异常时(如断电)尽可能地降低数据丢失的可能。在发生异常时,redis可能会丢失最近几分钟的数据。
- RDB需要fork子进程完成持久化任务,而在数据量比较大的情况下,fork子任务需要消耗CPU而引起服务在一段时间内无法响应客户端请求。而AOF虽然也需要fork,但是你可以自动调节logs的重写频率。
AOF 优势
-
AOF模式使Redis更耐用。我们可以采用不同的fsync策略:
- 完全不执行fsync
- 每秒钟执行一次fsync 默认策略
- 每次查询时执行fsync
-
AOF log 只能append,所以它不会发生随机写问题。即使断电时AOF log最后只写了一半命令,redis-check-aof 工具可以将其修复。
-
当AOF log太大时,Redis会在后台自动重写log.即使在Redis持续向旧log中写入,重写是完全安全的。
-
AOF log存储了所有操作命令且易于理解和解析。即使你错误地使用了FLUSHALL 命令,如果在执行命令后没有执行log 重写,我们可以Stop Redis,删除log中的最新命令,然后重启Redis就可以恢复数据。
AOF 劣势
- 对同一数据集来说,AOF文件要比RDB文件大。
- 依赖于定时刷盘策略的AOF要比RDB慢。而且即使在大量写入负载的情况下,RDB可以提供最大延迟的保证。
- 由于使用增量模式,AOF可能会出现一些隐秘的bug;而RDB却不会。
应该使用哪一个?
- 在实际应用中,如果想要数据非常安全,就需要同时使用RDB和AOF.
- 如果你可以接受丢失几分钟的数据,那么久使用RDB.
- 对于只使用AOF的做法,不鼓励,因为RDB是一种非常有效的数据备份方式且可以让启动服务更快速。AOF引擎有可能存在隐秘的bug.
- 基于以上,后续版本可能会推出能够兼备RDB和AOF长处的持久化方式。
快照方式
默认情况下Redis将数据存在磁盘中一个名称为dump.rdb的文件中。快照生成策略配置的方式:每隔N秒钟如果有至少M个修改时保存一次,或者可以手动调用SAVE或者BGSAVE命令。
save 60 1000
工作方式
当Redis需要生成一个快照保存到磁盘时,以下是执行的过程:
- fork 一个子进程
- 子进程将数据集写入临时RDB文件
- 子进程写入数据完毕之后,新的RDB文件替换旧的RDB文件
AOF
快照方式不是很可靠,在Redis突然停机的情况下,你可能会丢失最近写入的尚未持久化的数据。AOF是一个完全可靠的持久化策略,你可以通过如下配置项开启AOF:
appendonly yes
开启AOF后,每次redis收到改变数据集的命令,这些命令都会被追加到AOF log中。当重启Redis后,Redis通过回放AOF 重建数据集。
Log重写
随着Redis中执行的修改数据的命令增加,AOF log会变得越来越大,但是在log中可能会存在大量冗余的不必存在的命令;所以Redis提供log重写功能,它在后台运行不会打断对客户端提供服务。当我们执行命令触发日志重写(或者自动执行日志重写)时,Redis会在新的AOF log中生成能够构建Redis内存中当前数据集的最短命令。
AOF 可靠性几何?
用户可以自定义Redis执行fsync将数据写入磁盘。有如下三个选项:
- appendfsync always:在每次新的命令追加到AOF时执行fsync,非常慢,但是非常安全。
- appendfsync everysec:没秒钟执行一次fsync,足够快(2.4版本已经与快照模式速度比肩了),但是你可能会丢失1秒钟内的数据。
- appendfsync no:永远都不要执行fsync,只将数据交给操作系统就可以了。最快但同时也是最不安全的方式。一般情况下Linux系统每30秒(可以通过内核参数调整)进行一次刷盘。
建议使用 appendfsync everysec,既快又安全。appendfsync always 非常慢,但是它支持批量提交,如果使用并行写入,则可以尝试使用此种模式。
如果AOF 被截断了我该怎么办?
在Redis写入AOF时系统崩溃或者存储AOF的目录没有容量了都是有可能发生的,在这种情况下,AOF最后写入的最新一分钟的命令可能只写了一部分(被截断了)。最新版本的Redis仍然会加载此AOF文件,忽略最后被截断的命令,在系统日志中会给出提示:
* Reading RDB preamble from AOF file...
* Reading the remaining AOF tail...
# !!! Warning: short read while loading the AOF file !!!
# !!! Truncating the AOF at offset 439 !!!
# AOF loaded anyway because aof-load-truncated is enabled
在上述情况下,用户可以通过修改配置项强制Redis停止工作,但在默认情况下重启后Redis会忽略此异常而仅仅在日志中进行提示。
旧版本的Redis可能不会自动恢复,可能需要如下步骤:
- 备份AOF文件
- 使用 redis-check-aof tool修复此AOF
redis-check-aof --fix
- 可以使用 diff -u 查看修复前后AOF文件的差异
- 重启Redis,使用恢复后的AOF.
如果AOF 文件损坏了我该怎么办?
如果AOF文件发生了损坏,Redis重启之后会给出提示然后退出。
* Reading the remaining AOF tail...
# Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>
在此种情况下我们最好运行redis-check-aof tool来修复AOF文件,最初不要使用 --fix 选项,在运行命令后,查看一下命令输出的损坏位置,去查看AOF文件内的此位置,检查是否能够手动修复此文件(需要了解Redis 协议)。如果使用 redis-check-aof --fix对文件进行修复,工具会将损坏位置后的所有命令全部忽略,这将会导致此位置后的所有数据变动丢失,可能会导致大量数据丢失。所以不要轻易操作,除非能够接受丢失大量数据。
AOF怎样工作
log重写也符合copy-on-write的原则:
- fork一个子进程
- 子进程在一个新的临时的AOF文件中写入命令
- 父进程将子进程开始写入新的临时AOF文件后的所有数据修改命令积攒起来保存在内存缓存中,同时父进程也将命令写入原来的AOF文件中(如果重写失败,没有大碍)。
- 子进程写入新AOF文件完成后,父进程获取写入完成信号,将积攒的命令全部写入到子进程生成的AOF文件。
- Redis切换到使用新的AOF文件。
如果我在使用RDB,我想开始使用AOF,怎么操作?
如果Reids版本>=2.2
- 备份最新的dump.rdb文件
- 将备份文件备份到安全的位置去
- 执行如下两个redis命令:
上述命令,第二条命令可选redis-cli config set appendonly yes redis-cli config set save ""
- 确认数据集包含的键数目与之前它包含的键数目一致
- 确认数据操作命令被正确地追加到了AOF文件中
- 编辑redis.conf 文件,配置 AOF开关打开。否则在你以后重启redis后,redis使用配置文件重启,AOF开关又将再次关闭。
Redis版本 2.0
- 备份最新的dump.rdb文件
- 将备份文件备份到安全的位置
- 停止所有的数据集写入操作
- 通过客户端触发 bgrewriteaof,此操作将会创建AOF文件。
- 在Redis生成AOF dump结束后,停止Redis服务端。
- 编辑Redis.conf文件打开AOF开关
- 重启Redis服务
- 确认数据集包含的键数目与之前它包含的键数目一致
- 确认数据操作命令被正确地追加到了AOF文件中
AOF与RDB的相互作用
2.4版本以后的Redis会在RDB快照生成操作已经在处理的情况下避免触发AOF重写操作,也会在进行AOF重写操作时避免进行BGSAVE操作。这样可以避免同时进行两个高I/O的操作。
当正在进行RDB快照生成时,用户执行BGREWRITEAOF命令时,redis会回复OK告诉用户AOF重写任务已经被调度,但是在RDB快照生成完成之后才会执行。
在同时开启AOF和RDB的情况下,AOF会在Redis启动后用于构建数据,因为AOF是最可靠的。
备份Redis Data
在RDB文件运行期间,复制RDB文件是没有任何问题的,所以我们建议做如下备份策略:
- 使用定时任务每小时生成一个RDB文件,然后每天都将当天的RDB文件备份到另一个目录下
- 每次备份时将很久之前的RDB文件删除,确保使用RDB文件生成时的时间作为文件名后缀以标记文件。
- 需要一个每天至少一次的任务将数据文件备份到数据中心以外的位置或者物理机以外的位置
- 如果只开启了AOF功能,则可以定时备份AOF文件以达到备份数据的目的。
数据恢复
- 只有存在数据备份才能恢复数据,所以数据备份一定要有
- 使用Amazon S3或者类似的服务作为数据文件备份位置
- 通过SCP协议将RDB文件发送到其他服务器作为备份
Redis 持久化机制详解
操作系统和磁盘
在执行一次简单的数据库数据写入操作时,会发生如下事项:
- 客户端向数据库发出写入指令(数据在客户端内存中)
- 数据库收到写入请求(数据在服务端内存中)
- 数据库调用操作系统调用(可以将数据写入磁盘)(数据在内核缓冲区中)
- 操作系统将数据从内核缓冲区写入磁盘控制器(数据在磁盘缓存中)
- 磁盘控制器最终将数据写入物理设备(磁盘、闪存等)
注:以上是最简化的描述,实际上会存在很多层缓存和缓冲区。
第2步 在实际中实现可能会更复杂,通常情况下写入操作会单独使用线程或者进程完成。
第3步 实际中,大多数先进的内核都实现了多层缓存,通常是文件系统级别的缓存(在linux中称为页面缓存)和一个小的缓冲区用于缓存等待提交到磁盘的数据,可以使用特殊的接口来绕过上述两个缓存。对于我们来说,我们可以认为这是一层特殊的缓存。正常情况下我们可以认为数据库本身实现了自己的缓存而操作系统的缓存是关闭的以避免操作系统缓存做同样的事。缓冲区(buffer cache)通常是从不关闭的,因为如果关闭缓冲区,则意味着每次文件写入都会进行一次磁盘数据提交,这对大多数应用来说太慢了。
对数据库来说,只要必要,它就会直接调用系统调用将缓冲区数据提交到磁盘。
在上述步骤中,我们的数据在哪一步成功提交后才是真正安全的?
如果仅仅是数据库系统奔溃而不涉及内核,则我们的数据在第3步执行成功之后就是安全的(在操作系统内核没有崩溃的情况下,数据写入缓冲区后就是安全的,因为在这一步操作之后,操作系统会负责将数据刷盘)。
如果更严重的事故,如断电,只有在第5步操作完毕之后数据才是安全的。
综上所述,上述步骤中最重要的步骤是3,4,5;所以问题就是:
- 数据库多久会执行一次系统调用,将用户空间缓冲区中的数据发送到内核空间的缓冲区中?
- 内核会多久一次将缓冲区数据刷新到磁盘控制器中?
- 磁盘控制器会多久一次将数据写入到物理设备中?
上述步骤涉及到的 系统调用
第3步:我们可以使用write 系统调用将数据发送到内核缓冲区中。此系统调用时间受限于写入的数据量,如果达到磁盘最大带宽,写入将会被阻塞,直到磁盘能够接收更多数据时,写入调用才会成功返回。
第4步:数据从内核缓冲区写入磁盘控制器,通常我们会避免太过频繁地执行此操作,因为数据量越大此操作效率越高。默认情况下linux 系统会每30秒提交一次。这意味着如果操作失败,则可能会丢失最近30秒内的所有数据。
POSIX API 提供了一系列的接口用于强制将内核缓冲区内数据写入磁盘,最著名的接口或许是fsync系统调用。使用此系统调用是数据库系统强制将内核缓冲区内数据真正地写入磁盘的方法之一,但是这种方法时非常‘昂贵的’操作:fsync每次在它被调用且存在待写入数据时时都初始化一个写入操作。它会在整个操作过程中阻塞直至操作完成;如果有必要,它可能会阻塞其他所有正在写入同一文件的其他线程。
我们不能控制的是什么?
综上所述,我们可以控制第3步和第4步,但是我们无法控制第5步,POSIX API并没有提供相应接口,即使有些系统实现了一些接口用于提示驱动将数据写入物理设备,但是仅仅是提示而已。
两种数据安全级别
- 调用write(2)系统调用将数据写入内核缓冲区或者同等操作,可以保证在进程异常(数据库系统奔溃但是不涉及内核)的情况下数据安全。
- 调用fsync(2)系统调用将数据写入磁盘或者同等操作,可以保证在操作系统崩溃的情况下数据安全。
注:不是所有的数据库系统都使用POSIX API,有一些数据库使用内核模块以更直接地操作硬件,但是所面临的的问题都是一致的。最典型的使用内核模块的数据库例子就是Oracle。
数据损坏
数据安全刷新到磁盘并不是数据库系统关心的唯一问题,需要关心的另一个问题是:数据文件是否会被损坏,如果损坏,能否将损坏的文件修复,然后重新加载重建数据?
数据文件安全的三个级别
- 数据库在向磁盘写入数据过程中不关注异常的发生,需要用户从副本恢复数据或者提供一个可以尝试重新构建有效数据的工具。
- 数据库使用一个操作命令日志,在发生异常时可以从一致状态将数据恢复。
- 在数据写入后不会再做修改(数据文件只做追加操作),所以数据文件不会被损坏。
Redis 快照生成
在指定的条件满足时(如上一个快照在2分钟之前创建且已经存在至少100个写入操作),Redis会为当前时间点数据生成快照,存为一个单独的.rdb文件。快照生成的条件可以在配置文件中指定,也可以动态修改。
快照模式受限于用户设定的快照生成条件,比如每15分钟生成一次RDB文件,则在异常时最多会丢失最近15分钟写入的数据。
由于RDB文件由子进程以追加模式生成,所以RDB文件不会被损坏。
RDB文件有可能会丢失最近几分钟的数据,所以它被用于丢失最新几分钟数据可以接受的场景下。
但是建议在开启AOF的情况下同时使用RDB。因为RDB文件对数据异地备份、恢复不同版本的数据都很有用。
AOF
每次修改数据的写入操作命令被执行时,这个命令都会被记录在AOF log中。AOF log 文件使用Redis内部协议生成,可以传输给另外一个实例,也可以很容易地解析。在Redis重启后它会重放log以重新构建数据集。
eg:
- 操作:
redis 127.0.0.1:6379> set key1 Hello
OK
redis 127.0.0.1:6379> append key1 " World!"
(integer) 12
redis 127.0.0.1:6379> del key1
(integer) 1
redis 127.0.0.1:6379> del non_existing_key
(integer) 0
最后一条命令不会修改数据,因为key non_existing_key 不存在
- AOF log:
$ cat appendonly.aof
*2
$6
SELECT
$1
0
*3
$3
set
$4
key1
$5
Hello
*3
$6
append
$4
key1
$7
World!
*2
$3
del
$4
key1
注:最后一条命令没有写入AOF log,因为它没有改变数据。
AOF 重写
当AOF文件太大时,Redis会根据当前内存中的数据集,从头开始重新生成AOF文件(从头生成的AOF文件以当前数据为基准,从零生成则意味着会去除很多重复无效命令,所以会生成最短的AOF文件),它不需要读取磁盘。在新的AOF文件生成以后,此AOF文件会同步到磁盘替换掉原来的AOF文件。
在生成新的AOF文件过程中,新的操作命令会依旧被写入原来的AOF文件中(这样即使重写失败,AOF文件依旧完整,数据依旧安全),同时这些命令也会被放在缓冲区中;在新的AOF文件写完之后,放入缓冲区中积累的最近执行的命令会追写到新的AOF文件中,至此,AOF文件重写完成。
问题是多久执行一次write(2),多久执行一次fsync(2)?
AOF和RDB生成过程中,都没有随机读取,都是顺序I/O,所以即使对于旋转磁盘来说,这种I/O操作也是非常高效的。
AOF 的耐用性
AOF 使用存放着新命令执行后生成的新数据的用户空间缓冲区。在每次事件循环返回时缓冲区数据会被刷新到磁盘,使用系统调用write(2),有如下三种配置会改变系统调用write(2)和fsync(2)的表现:
- appendfsync no
在此种配置下,不会执行fsync(2).但是这种方式客户端不能使用pipeline方式,也就是说客户端在发送一条命令后,等待服务端返回该命令执行结果,在确认该命令执行结果后,客户端才会执行下一条命令。而服务端仅在改动数据的命令被通过write(2)追加到AOF文件中——写入内核缓冲区时返回响应。
因为fsync(2)不会被调用,所以数据何时写入磁盘依赖于内核配置。默认情况下,linux内核每30s执行一次刷盘。
- appendfsync everysec
在此种配置下,数据会通过write(2)调用写入文件同时每隔1s钟就会自动执行一次fsync(2)调用将内核缓冲区数据刷新到磁盘。通常情况下在每个事件循环返回时,write(2)都会被调用,但是在特殊情况下并不保证。
如果磁盘不能够处理大量写入,fsync(2)调用执行将会超过1s,那么在接下来的1s内redis将会延迟write(2)的执行(避免write(2)阻塞在执行的fsync(2))。如果fsync(2)执行超过了2s依旧没有结束,redis将会强制执行write(2)调用不论代价如何高昂。
所以在此种模式下,Redis保证在最坏情况下,2s之内,写入的所有数据都会被写入磁盘,平均1s之内数据就会写入磁盘。
默认值是此项。平衡了 appendfsync no 与 appendfsync always。
- appendfsync always
在此种模式下,如果客户端没有使用pipeline方式,在发送命令请求后等待命令执行结果,服务端会在返回响应结果之前将写入数据同步到磁盘。
此种模式使最安全的模式,同时也是性能最差的模式。
redis 中 appendfsync always的实现是 调用group commit。
这意味着Redis在返回响应给在最新事件循环开始之前提交数据操作请求的所有客户端之前可以将所有的提交组合为一个单独的 write(2) + fsync(2)操作执行,而不是为每一个数据操作命令都执行一次fsync(2)。
实际上这意味着可以有成千上百个客户端同时执行写入操作,而Redis服务端在收到所有这些请求后,会将请求进行整合,所以在这种模式下,工作与100-200 写入 op/s 的旋转磁盘上的redis也可以支持上千并发。
该功能remarkable.
为什么pipeline 方式不同?
在客户端只追求速度而不在乎命令执行结果时,可以采用pipeline方式;在客户端采用pipeline方式时,write(2)与fsync(2)在事件循环返回之前总是会执行。
AOF 和 Redis 事务
AOF为Redis事务提供了保证,Redis拒绝加载包含损坏事务的AOF文件。Redis提供了修复工具,会将AOF中不完整的(损坏)的事务删除。
Redis快的原因?
Redis fsync(2)在后台执行,且Redis仅以追加的模式对文件写入,不存在随机写入。
启动
Redis 在加载RDB文件时,以10-20秒/G的速度加载。