持久化
提供了多种不同级别的持久化方式:
-
RDB 持久化可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot)。
-
AOF 持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集。 AOF 文件中的命令全部以 Redis 协议的格式来保存,新命令会被追加到文件的末尾。 Redis 还可以在后台对 AOF 文件进行重写(rewrite),使得 AOF 文件的体积不会超出保存数据集状态所需的实际大小。Redis 还可以同时使用 AOF 持久化和 RDB 持久化。 在这种情况下, 当 Redis 重启时, 它会优先使用 AOF 文件来还原数据集, 因为 AOF 文件保存的数据集通常比 RDB 文件所保存的数据集更完整。你甚至可以关闭持久化功能,让数据只在服务器运行时存在。
t1 --> 3. t1—>4. t1–>5
了解 RDB 持久化和 AOF 持久化之间的异同是非常重要的, 以下几个小节将详细地介绍这这两种持久化功能, 并对它们的相同和不同之处进行说明
RDB 的优点
- RDB是一种表示某个即时点的Redis数据的紧凑文件。RDB文件适合用于备份。例如,你可能想要每小时归档最近24小时的RDB文件,每天保存近30天的RDB快照。这允许你很容易的恢复不同版本的数据集以容灾。
- RDB非常适合于灾难恢复,作为一个紧凑的单一文件,可以被传输到远程的数据中心,或者是Amazon S3(可能得加密)。
- RDB最大化了Redis的性能,因为Redis父进程持久化时唯一需要做的是启动(fork)一个子进程,由子进程完成所有剩余工作。父进程实例不需要执行像磁盘IO这样的操作。
- RDB在重启保存了大数据集的实例时比AOF要快。
RDB 的缺点
-
当你需要在Redis停止工作(例如停电)时最小化数据丢失,RDB可能不太好。你可以配置不同的保存点(save point)来保存RDB文件(例如,至少5分钟和对数据集100次写之后,但是你可以有多个保存点)。然而,你通常每隔5分钟或更久创建一个RDB快照,所以一旦Redis因为任何原因没有正确关闭而停止工作,你就得做好最近几分钟数据丢失的准备了。
save 300 100
save 90000 100
save 10 10
-
RDB需要经常调用fork()子进程来持久化到磁盘。如果数据集很大的话,fork()比较耗时,结果就是,当数据集非常大并且CPU性能不够强大的话,Redis会停止服务客户端几毫秒甚至一秒。AOF也需要fork(),但是你可以调整多久频率重写日志而不会有损(trade-off)持久性(durability)。
AOF 的优点
- 使用AOF Redis会更具有可持久性(durable):你可以有很多不同的fsync策略:没有fsync,每秒fsync,每次请求时fsync。使用默认的每秒fsync策略,写性能也仍然很不错(fsync是由后台线程完成的,主线程继续努力地执行写请求),即便你也就仅仅只损失一秒钟的写数据。
- AOF日志是一个追加文件,所以不需要定位,在断电时也没有损坏问题。即使由于某种原因文件末尾是一个写到一半的命令(磁盘满或者其他原因),redis-check-aof工具也可以很轻易的修复。
- 当AOF文件变得很大时,Redis会自动在后台进行重写。重写是绝对安全的,因为Redis继续往旧的文件中追加,使用创建当前数据集所需的最小操作集合来创建一个全新的文件,一旦第二个文件创建完毕,Redis就会切换这两个文件,并开始往新文件追加。
- AOF文件里面包含一个接一个的操作,以易于理解和解析的格式存储。你也可以轻易的导出一个AOF文件。例如,即使你不小心错误地使用FLUSHALL命令清空一切,如果此时并没有执行重写,你仍然可以保存你的数据集,你只要停止服务器,删除最后一条命令,然后重启Redis就可以。
AOF 的缺点
-
对同样的数据集,AOF文件通常要大于等价的RDB文件。
-
AOF可能比RDB慢,这取决于准确的fsync策略。通常fsync设置为每秒一次的话性能仍然很高,如果关闭fsync,即使在很高的负载下也和RDB一样的快。不过,即使在很大的写负载情况下,RDB还是能提供能好的最大延迟保证。
-
在过去,我们经历了一些针对特殊命令(例如,像BRPOPLPUSH这样的阻塞命令)的罕见bug,导致在数据加载时无法恢复到保存时的样子。这些bug很罕见,我们也在测试套件中进行了测试,自动随机创造复杂的数据集,然后加载它们以检查一切是否正常,但是,这类bug几乎不可能出现在RDB持久化中。为了说得更清楚一点:Redis AOF是通过递增地更新一个已经存在的状态,像MySQL或者MongoDB一样,而RDB快照是一次又一次地从头开始创造一切,概念上更健壮。但是,1)要注意Redis每次重写AOF时都是以当前数据集中的真实数据从头开始,相对于一直追加的AOF文件(或者一次重写读取老的AOF文件而不是读内存中的数据)对bug的免疫力更强。2)我们还没有收到一份用户在真实世界中检测到崩溃的报告。
RDB 和 AOF ,我应该用哪一个?
一般来说,如果想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能。
如果你非常关心你的数据,但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。
有很多用户单独使用AOF,但是我们并不鼓励这样,因为时常进行RDB快照非常方便于数据库备份,启动速度也较之快,还避免了AOF引擎的bug。
注意:基于这些原因,将来我们可能会统一AOF和RDB为一种单一的持久化模型(长远计划)。
下面的部分将介绍两种持久化模型等多的细节。
RDB 快照
默认情况下,Redis保存数据集快照到磁盘,名为dump.rdb的二进制文件。你可以设置让Redis在N秒内至少有M次数据集改动时保存数据集,或者你也可以手动调用SAVE或者BGSAVE命令。
例如,这个配置会让Redis在每个60秒内至少有1000次键改动时自动转储数据集到磁盘:
save 60 1000
save 900 100
save 10 10
这种策略被称为快照。
快照的运作方式
当 Redis 需要保存 dump.rdb 文件时, 服务器执行以下操作:
- Redis 调用 fork() ,同时拥有父进程和子进程。
- 子进程将数据集写入到一个临时 RDB 文件中。
- 当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。
这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益。
只追加文件 AOF
快照功能并不是非常耐久(durable): 如果 Redis 因为某些原因而造成故障停机, 那么服务器将丢失最近写入、且仍未保存到快照中的那些数据。尽管对于某些程序来说, 数据的耐久性并不是最重要的考虑因素, 但是对于那些追求完全耐久能力(full durability)的程序来说, 快照功能就不太适用了。
从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式: AOF 持久化。
你可以通过修改配置文件来打开 AOF 功能:
appendonly yes
从现在开始, 每当 Redis 执行一个改变数据集的命令时(比如 SET), 这个命令就会被追加到 AOF 文件的末尾。 当 Redis 重新启时, 程序就可以通过重新执行 AOF 文件中的命令来达到重建数据集的目的。
日志重写
你可以猜得到,写操作不断执行的时候AOF文件会越来越大。例如,如果你增加一个计数器100次,你的数据集里只会有一个键存储这最终值,但是却有100条记录在AOF中。其中99条记录在重建当前状态时是不需要的。
于是Redis支持一个有趣的特性:在后台重建AOF而不影响服务客户端。每当你发送BGREWRITEAOF时,Redis将会写入一个新的AOF文件,包含重建当前内存中数据集所需的最短命令序列。如果你使用的是Redis 2.2的AOF,你需要不时的运行BGREWRITEAOF命令。Redis 2.4可以自动触发日志重写(查看Redis 2.4中的示例配置文件以获得更多信息)。
AOF持久性如何?
你可以配置 Redis 多久才将数据 fsync 到磁盘一次。有三个选项:
- 每次有新命令追加到 AOF 文件时就执行一次 fsync :非常慢,也非常安全。
- 每秒 fsync 一次:足够快(和使用 RDB 持久化差不多),并且在故障时只会丢失 1 秒钟的数据。
- 从不 fsync :将数据交给操作系统来处理。更快,也更不安全的选择。
推荐(并且也是默认)的措施为每秒 fsync 一次, 这种 fsync 策略可以兼顾速度和安全性。 总是 fsync 的策略在实际使用中非常慢, 即使在 Redis 2.0 对相关的程序进行了改进之后仍是如此 ------ 频繁调用 fsync 注定了这种策略不可能快得起来。
AOF有3种方式将操作命令存入AOF文件
1. appendfsync no 不保存
只执行WHRITE操作,SAVE操作会被略过,只有在Redis被关闭、AOF功能被关闭、系统的写缓存被刷新(如缓存已被写满)这三种情况,SAVE操作会被执行,但是这三种情况都会引起Redis主进程阻塞
2. appendfsync everysec 每秒钟保存一次
这种模式中,SAVE原则上每隔一秒钟就会执行一次,具体的执行周期和文件写入、保存时,Redis所处的状态有关,此模式下SAVE操作由后台子线程调用,不会引起服务器主进程的阻塞
3. appendfsync always 每执行一个命令保存一次
在这种模式下,每执行一个命令,WRITE和SAVE都会被执行,且SAVE操作会阻塞主进程
| 模式 | WRITE阻塞 | SAVE阻塞 | 停机时丢失的数据量 |
|---|---|---|---|
| appendfsync no | 不阻塞 | 阻塞 | 操作系统最后一次对 AOF 文件触发 SAVE 操作之后的数据 |
| appendfsync everysec | 阻塞 | 不阻塞 | 一般情况下不超过 2 秒钟的数据 |
| appendfsync always | 阻塞 | 阻塞 | 最多只丢失一个命令的数据 |
*设置好AOF写入的模式之后,只要达到写入条件(比如一秒钟、执行一个命令),就会自动在指定路径下生成AOF文件,并往里面记录操作命令*
no-appendfsync-on-rewrite参数
bgrewriteaof机制,在一个子进程中进行aof的重写,从而不阻塞主进程对其余命令的处理,同时解决了aof文件过大问题。
现在问题出现了,同时在执行bgrewriteaof操作和主进程写aof文件的操作,两者都会操作磁盘,而bgrewriteaof往往会涉及大量磁盘操作,这样就会造成主进程在写aof文件的时候出现阻塞的情形,现在no-appendfsync-on-rewrite参数出场了。如果该参数设置为no,是最安全的方式,不会丢失数据,但是要忍受阻塞的问题。如果设置为yes呢?这就相当于将appendfsync设置为no,这说明并没有执行磁盘操作,只是写入了缓冲区,因此这样并不会造成阻塞(因为没有竞争磁盘),但是如果这个时候redis挂掉,就会丢失数据。
如果 AOF 文件出错了,怎么办?
服务器可能在程序正在对 AOF 文件进行写入时崩溃(这个不应该破坏数据的一致性), Redis不会装载已破坏的AOF文件。当发生这种情况时, 可以用以下方法来修复出错的 AOF 文件:
- 为现有的 AOF 文件创建一个备份。
- 使用 Redis 附带的 redis-check-aof 程序,对原来的 AOF 文件进行修复。
- $ redis-check-aof --fix
- (可选)使用 diff -u 对比修复后的 AOF 文件和原始 AOF 文件的备份,查看两个文件之间的不同之处。
- 重启 Redis 服务器,等待服务器载入修复后的 AOF 文件,并进行数据恢复。
如何工作
日志重写采用了和快照一样的写时复制机制。下面是过程:
- Redis调用fork()。于是我们有了父子两个进程。
- 子进程开始向一个临时文件中写AOF。
- 父进程在一个内存缓冲区中积累新的变更(同时将新的变更写入旧的AOF文件,所以即使重写失败我们也安全)。
- 当子进程完成重写文件,父进程收到一个信号,追加内存缓冲区到子进程创建的文件末尾。
- 搞定!现在Redis原子性地重命名旧文件为新的,然后开始追加新数据到新文件。
如何由RDB持久化转换到AOF持久化?
Redis 2.0 和 Redis 2.2 处理流程不一样,可以很简单猜测到 Redis 2.2 处理流程更简单,并且不需要重启。
Redis >=2.2 时
- 创建最近的RDB文件的备份。
- 将备份保存在安全的位置。
- 发起如下命令。
- $redis-cli config set appendonly yes。
- $redis-cli config set save “”。
- 确认数据库包含相同的keys。
- 确认write操作被正确追加到了AOF文件。
第一个CONFIG命令开启AOF。Redis会阻塞以生成初始转储文件,然后打开文件准备写,开始追加写操作。
第二个CONFIG命令用于关闭快照持久化。这一步是可选的,如果你想同时开启这两种持久化方法。
**重要:**记得编辑你的redis.conf文件来开启AOF,否则当你重启服务器时,你的配置修改将会丢失,服务器又会使用旧的配置。
Redis2.0时
- 创建最近的RDB文件的备份;
- 将备份存放在安全的位置;
- 停止数据库上的所有写操作;
- 发起 redis-cli bgrewriteaof命令创建AOF文件;
- 当AOF文件生成后停止Redis Server;
- 编辑redis.conf开启AOF持久化;
- 重启Redis Server;
- 确认数据库包含相同的keys;
- 确认write操作被正确追加到了AOF文件。
AOF与RDB之间的相互作用
Redis2.4以上的版本会确保在RDB快照创建时不触发AOF重写或者在AOF重写时不允许BGSAVE操作,以避免Redis后台进程同时做繁重的磁盘I/O操作。
当创建RDB快照时对于用户使用BGREWRITEAOF明确发起的日志重写操作server会立刻回应一个ok状态码告知用户操作将会被执行,当且仅当快照创建完成后重写操作开始被执行。
在同时使用了AOF和RDB方式的情况下,Redis重启后会优先使用AOF文件来重构原始数据集。
备份Redis 数据
开始这一部分之前,请务必牢记:一定要备份你的数据库。磁盘损坏,云中实例丢失,等等:没有备份意味着丢失数据的巨大风险。
Redis对数据备份非常友好,因为你可以在数据库运行时拷贝RDB文件:RDB文件一旦生成就不会被修改,文件生成到一个临时文件中,当新的快照完成后,将原子性地使用rename(2)修改文件名为目标文件。
这意味着,在服务器运行时拷贝RDB文件是完全安全的。以下是我们的建议:
- 创建一个定时任务(cron job),每隔一个小时创建一个RDB快照到一个目录,每天的快照放在不同目录。
- 每次定时脚本运行时,务必使用find命令来删除旧的快照:例如,你可以保存最近48小时内的每小时快照,一到两个月的内的每天快照。注意命名快照时加上日期时间信息。
- 至少每天一次将你的RDB快照传输到你的数据中心之外,或者至少传输到运行你的Redis实例的物理机之外。
灾难恢复
在Redis中灾难恢复和数据备份基本上是同样的过程,并且灾难恢复会将这些备份传输到外部的多个数据中心。这样即使一些灾难性的事件影响到运行Redis和生成快照的主数据中心,数据也是安全的。
由于许多Redis用户都处于启动阶段,没有太多预算,我们会介绍一些最有意思的灾难恢复技术,而不用太多的花销。
- Amazon S3和一些类似的服务是帮助你灾难恢复系统的一个好办法。很简单,只需要将你的每日或每小时的RDB快照以加密的方式传输到S3。你可以使用
gpg -c来加密你的数据(以对称加密模式)。确保将你的密码保存在不同的安全地方(例如给一份到你的组织中的最重要的人)。推荐使用多个存储服务以提升数据安全。 - 使用SCP(SSH的组成部分)来传输你的快照到远程服务器。这是一种相当简单和安全的方式:在远离你的位置获得一个小的VPS,安装ssh,生成一个无口令的ssh客户端key,并将其添加到你的VPS上的authorized_keys文件中(译者注:这是SSH互信,在Linux系统中可以使用ssh-keygen命令生成公私钥)。你就可以自动的传输备份文件了,无需输入密码。为了达到更好的效果,最好是至少从不同的提供商那搞两个VPS。
要知道这种系统如果没有正确的处理会很容易失败。至少一定要确保传输完成后验证文件的大小(要匹配你拷贝的文件),如果你使用VPS的话,可以使用 SHA1 数字签名。
你还需要一个独立的告警系统,在某些原因导致传输备份过程失败时告警。
持久化备份恢复实操
1,首先在6379端口进行save操作(并且开启了appendonly),会生成dump.rdb文件
root@VM-2-10-ubuntu:/usr/local/redis/db/db1# redis-cli -p 6379 127.0.0.1:6379> save OK 127.0.0.1:6379> exit root@VM-2-10-ubuntu:/usr/local/redis/db/db1# ll total 16 drwxr-xr-x 2 root root 4096 Aug 15 08:59 ./ drwxr-xr-x 8 root root 4096 Aug 12 16:31 ../ -rw-r--r-- 1 root root 270 Aug 12 18:47 appendonly.aof -rw-r--r-- 1 root root 287 Aug 15 08:59 dump.rdb root@VM-2-10-ubuntu:/usr/local/redis/db/db1# redis-cli -p 6379 127.0.0.1:6379> keys * 1) "email" 2) "t3" 3) "myt1" 4) "t2" 5) "listest" 6) "t4" 7) "t1" 8) "mynums" 9) "myt2" 127.0.0.1:6379> set t5 "8-15 09:00" OK 127.0.0.1:6379> get t5 "8-15 09:00" 127.0.0.1:6379> set t6 "8-15 09:10" OK 127.0.0.1:6379> exit root@VM-2-10-ubuntu:/usr/local/redis/db/db1# ll total 16 drwxr-xr-x 2 root root 4096 Aug 15 09:00 ./ drwxr-xr-x 8 root root 4096 Aug 12 16:31 ../ -rw-r--r-- 1 root root 346 Aug 15 09:02 appendonly.aof -rw-r--r-- 1 root root 302 Aug 15 09:00 dump.rdb
2,将dump.rdb另一个空白实例6333中,当我们需要只对dump.rdb进行恢复的时候,需要对appendonly设置为no,否则无法恢复;当我们需要对所有的keys进行恢复的时候需要对appendonly.aof进行拷贝已经设置目标端的appendonly为yes。
已知:
6379端口数据目录:/usr/local/redis/db/db1
6333端口数据目录:/usr/local/redis/db/db_6333
root@VM-2-10-ubuntu:/usr/local/redis/db# redis-cli -p 6379 127.0.0.1:6379> save OK 127.0.0.1:6379> keys * 1) "email" 2) "t3" 3) "myt1" 4) "t6" 5) "t2" 6) "listest" 7) "t4" 8) "t1" 9) "mynums" 10) "t5" 11) "myt2" 127.0.0.1:6379> exit root@VM-2-10-ubuntu:/usr/local/redis/db# ll db1 total 16 drwxr-xr-x 2 root root 4096 Aug 15 09:48 ./ drwxr-xr-x 7 root root 4096 Aug 15 09:47 ../ -rw-r--r-- 1 root root 346 Aug 15 09:02 appendonly.aof -rw-r--r-- 1 root root 317 Aug 15 09:48 dump.rdb root@VM-2-10-ubuntu:/usr/local/redis/db# redis-cli -p 6379 127.0.0.1:6379> set t6 "09:54" OK 127.0.0.1:6379> set t7 "09:57" OK 127.0.0.1:6379> exit ### t6是覆盖原来的key,所以现在是一共是12个key root@VM-2-10-ubuntu:/usr/local/redis/db# ll db1 total 16 drwxr-xr-x 2 root root 4096 Aug 15 09:48 ./ drwxr-xr-x 7 root root 4096 Aug 15 09:47 ../ -rw-r--r-- 1 root root 410 Aug 15 09:54 appendonly.aof ##包括后面两次更改的操作 -rw-r--r-- 1 root root 317 Aug 15 09:48 dump.rdb ##更改前的数据存储。 root@VM-2-10-ubuntu:/usr/local/redis/db# ll db_6333/ total 8 drwxr-xr-x 2 root root 4096 Aug 15 09:54 ./ drwxr-xr-x 8 root root 4096 Aug 15 09:54 ../ root@VM-2-10-ubuntu:/usr/local/redis/db# cp db1/dump.rdb db_6333/ root@VM-2-10-ubuntu:/usr/local/redis/db# cat /usr/local/redis/etc/redis_6333.conf |grep ^appendonly appendonly no root@VM-2-10-ubuntu:/usr/local/redis/db# redis-server /usr/local/redis/etc/redis_6333.conf root@VM-2-10-ubuntu:/usr/local/redis/db# redis-cli -p 6333 127.0.0.1:6333> keys * 1) "t5" 2) "t4" 3) "t6" 4) "t1" 5) "t2" 6) "myt1" 7) "myt2" 8) "listest" 9) "t3" 10) "mynums" 11) "email" 127.0.0.1:6333>
这种只恢复了dump.rdb文件,恢复dump.rdb和appendonly.aof文件:
root@VM-2-10-ubuntu:/usr/local/redis/db# redis-cli -p 6333 shutdown root@VM-2-10-ubuntu:/usr/local/redis/db# cp db1/appendonly.aof db_6333/ root@VM-2-10-ubuntu:/usr/local/redis/db# cat /usr/local/redis/etc/redis_6333.conf |grep ^appendonly appendonly yes root@VM-2-10-ubuntu:/usr/local/redis/db# redis-server /usr/local/redis/etc/redis_6333.conf root@VM-2-10-ubuntu:/usr/local/redis/db# redis-cli -p 6333 127.0.0.1:6333> keys * 1) "email" 2) "t1" 3) "myt2" 4) "t2" 5) "myt1" 6) "t5" 7) "t7" 8) "listest" 9) "mynums" 10) "t6" 11) "t3" 12) "t4" 127.0.0.1:6333> get t6 "09:54" 127.0.0.1:6333> get t7 "09:57" 127.0.0.1:6333> exit




