Jamzy Wang

life is a struggle,be willing to do,be happy to bear~~~

redis持久化——RDB vs AOF

2014-05-21 21:49

原创声明:本作品采用知识共享署名-非商业性使用 3.0 版本许可协议进行许可,欢迎转载,演绎,但是必须保留本文的署名(包含链接),且不得用于商业目的。

在介绍redis持久化之前,再强调一下与redis持久化相关的几个key features。

  • 所有的数据都存在内存(memory)中。这保证了数据极快的读写操作。

  • 内存中的数据可以被保存到磁盘(disk)中。将内存中的数据保存到磁盘中又被称为持久化(persistence)。

  • redis forks。redis对于耗时的任务会创建子进程(child processes)进行处理。

redis在运行时所有的数据都被加载到内存中,因此redis也被称为内存数据库。我们知道内存中的数据在断电或者redis进程关闭之后就不再存在,如果redis不提供数据的持久化方式,那么redis最适合的使用场景是用作缓存(cache),redis数据清空就相当于缓存清空。把redis作为数据库的缓存现在也被广泛的应用到实际生产中。

说到缓存,不得不提到 memcached, 相比于memcached,redis提供了额外的持久化机制,正是因为这个持久化机制的存在使得redis不仅可以向memcached那样被用来当做缓存也可以被用来当做数据库(数据库中的数据显然是不能丢失的)。redis支持的两种持久化方式:

1)RDB:持久化可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot)。

2)AOF:持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集。

本文接下来就将具体讲解redis的两种持久化机制。

RDB

RDB 持久化就是在指定的时间间隔内生成redis内存中的整个数据集的时间点快照(point-in-time snapshot),一个.rdb文件(如 dump.rdb)。

RDB 是一个非常紧凑(compact)的文件,它保存了 Redis 在某个时间点上的数据集。 这种持久化方法非常适合用于定期备份redis数据库,如每天备份一次,一般推荐定期将rdb文件备份到远程物理机上(不同于redis server所在机器),当redis server所在的机器发生灾难时可以使用远程备份的rdb文件进行恢复。

RDB持久化实现

rdb持久化方式被称为快照(snapshot),它是某个时刻数据库的完整复制。redis执行rdb持久化时服务器执行以下操作:

(1) redis 调用 fork() ,同时拥有父进程和子进程。

(2) 子进程将数据集写入到一个临时 rdb 文件中。

(3) 当子进程完成对新 rdb 文件的写入时,redis 用新 rdb 文件替换原来的 rdb 文件,并删除旧的 rdb 文件。

RDB持久化就是把整个键值空间保存到文件中,加载时就是重建整个键值空间。

那么如何启动rdb持久化呢?

RDB持久化配置

开启rdb持久化只需要在redis配置文件中设置一些配置项并在redis server启动时加载此配置文件。

1
2
3
4
5
save <seconds> <changes>
dbfilename <filename>.rdb
stop-writes-on-bgsave-error <yes/no>
rdbcompression <yes/no>
rdbchecksum <yes/no>
  • save <seconds> <changes>

当配置成 save N K 时,redis会在经过N秒且发生了K次写操作时执行一次rdb持久化操作。这里需要强调的是 “N秒” 和 “K次写操作” 必须同时发生时才会进行rdb持久化操作。redis会每隔N秒检查者段时间内发生的写操作的数量,当写操作的数量超过K时,redis就会做一次持久化操作,当写操作次数没达到K时,redis不会做持久化操作。当在短时间(时间间隔小于N)内发生大于K次操作时,redis也不会做持久化操作。

那么如何关闭rdb持久化呢? 设置为 save 即可。

  • dbfilename <filename>.rdb

rdb持久化操作会产生一个.rdb文件,此配置项用于设置 .rdb 文件的名称,如 “backup.rdb”。当不设置此项时,redis会默认生成 “dump.rdb” 文件。

  • stop-writes-on-bgsave-error <yes/no>

当此项被设置成 yes 时,redis会在进行rdb持久化操作时暂停执行写操作,在持久化操作 fail 时不再进行写操作。当此项被设置成 no 时,redis会在持久化的同时执行写操作,在持久化操作失败时也继续执行写操作。

  • rdbcompression <yes/no>

此项用于设置是否对rdb文件进行压缩(Compress string objects)。

  • rdbchecksum <yes/no>

此项用于设置是否对rdb文件增加 checksum,当选择 yes 时,rdb文件末尾会增加一个用 CRC64 算法生成的 checksum 。需要指出的是开启此项会影响redis的性能,对于redis的持久化和redis server启动时加载此rdb文件增加大概10%左右的时间消耗。

除了在配置文件中设置上述这些项,还可以在redis客户端中用下述几个命令进行持久化操作。

  • BGSAVE

在后台执行持久化操作(Save the DB in background)。redis主进程fork一个子进程进行持久化操作把内存中的数据保存到磁盘中,redis将继续对客户端提供服务。

  • SAVE

进行异步的持久化操作(synchronous save)。所有客户端都将被阻塞直到完成持久化操作,一般不推荐在真实生产环境中采用。

  • LASTSAVE

返回最近一次成功的持久化操作的Unix时间戳。

此处输入图片的描述

RDB持久化优缺点

RDB优点

  • rdb文件是一个非常紧凑(compact)的文件,它保存了 redis 在某个时间点上的数据集。这种文件非常适合用于进行备份。
  • RDB 可以最大化 redis 的性能。父进程在保存 rdb 文件时唯一要做的就是 fork 出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘 I/O 操作。
  • 在redis启动加载数据库时,RDB 方式比 AOF 的速度要快很多(RDB比AOF快近2倍)。

RDB缺点

  • 每次执行 RDB 持久化时,redis 都会 fork() 出一个子进程,并由子进程来进行实际的持久化工作。 在数据集比较庞大时,fork() 可能会非常耗时,造成服务器在某某毫秒内停止处理客户端; 如果数据集非常巨大,并且 CPU 时间非常紧张的话,那么这种停止时间甚至可能会长达整整一秒。

  • RDB持久化方式不能保证不丢失数据。RDB方式是每隔一段时间(比如5分钟)才会将内存中的数据保存到磁盘中,如果在两次时间间隔中发生故障宕机,那么redis会丢失几分钟的数据。如果redis被用作缓存,那么几分钟时间内数据的丢失不会产生严重的影响,如果redis被用作数据库,那么不推荐在实际生产中使用RDB方式持久化。

  • RDB方式需要大量额外的内存消耗。RDB方式通过fork()一个子进程的方式使得redis可以进行copy-on-write操作。RDB方式需要的额外的内存大小正比于在snapshoting过程中数据库发生的写操作的数量。在一个写繁忙(write-heavy)的redis服务中,redis所需要的内存可能会接近数据库大小的两倍(此时可能会存在繁忙的memory swapping)。

  • RDB方式完整的复制了内存中的redis数据,因此需要额外的磁盘空间来存储.rd文件。

Real Case Study

问题背景:

1
2
1) 不能对redis server进行写操作
2) sentinel没有发现redis server failure

不能对server进行写操作,说明server发生了故障,最常见的故障就是宕机了。但是sentinel并没有发现server failure,说明server并没有宕机,那么可能是什么原因呢?

一种可能的原因是redis被设置为了用RDB方式进行持久化,stop-writes-on-bgsave-error被设置成了yes, 但是RDB持久化操作发生了故障,redis拒绝了所有来自客户端的写操作。redis中控制这种状况的代码片段如下:

1
2
3
4
5
6
7
8
9
if ( server.stop_writes_on_bgsave_err
     && server.saveparamslen > 0
     && server.lastbgsave_status == REDIS_ERR
     && c->cmd->flags & REDIS_CMD_WRITE )
{
	flagTransaction( c );
	addReply( c, shared.bgsaveerr );
	return(REDIS_OK);
}

那么如何解决这个问题呢?

1
2
方法1:将`stop-writes-on-bgsave-error`设置为no
方法2:将RDB持久化关闭。

AOF

RDB持久化方式的一个缺点是在两次snapshot之间的数据存在丢失的可能性,对不允许丢失数据的服务中(就像绝大多数关系型数据库一样),RDB持久化方式是不适用的。redis从1.1版本开始增加了另一种持久化方式——AOF 持久化。

AOF, Append only file。AOF 持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集。 AOF 文件中的命令全部以 redis 协议的格式来保存,新命令会被追加到文件的末尾。 此处输入图片的描述

AOF实现

AOF持久化方式在redis发生写操作(如set,lpush)时,会将这次写操作命令追加到AOF文件的末尾。那么,随着写入命令的不断增加,AOF的体积会变得越来越大。

AOF体积越来越大会发生什么问题呢?redis重启载入会非常耗时。redis重启时重建数据集的方式是重新执行一次AOF中的所有命令。当AOF体积越来越大时,redis重启载入时可能需要执行几千万或者上亿次写操作,这需要一段较长的时间来完成(几分钟或者更久),当redis发生故障需要重启时,重建数据集的这段时间内将不能对外提供服务,对于高可用(HA,High Availity)服务来说是不可接受的。

那么如何来减小AOF文件的体积呢?

我们来看一下这样一个场景,对键 mykey 调用100次 INCR操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
redis> SET mykey "10"
OK
redis> INCR mykey
(integer) 11
redis> INCR mykey
(integer) 12
redis> INCR mykey
(integer) 13
redis> INCR mykey
(integer) 14
redis> INCR mykey
(integer) 15
...

按照AOF持久化方式,需要在AOF文件中添加100条 INCR mykey 命令。然而,执行100次 INCR mykey 操作完全等价于 SET mykey "110",也就是执行一次SET操作就可以代替100次 INCR 操作。也就是说在AOF文件中加100条INCR mykey是一种“冗余”的行为,因为可以用一条SET操作代替。在重启redis服务时,执行更少的写命令也将更快的完成数据集的重建。为了处理这种情况, Redis 支持一种有趣的特性: 可以在不打断服务客户端的情况下, 对 AOF 文件进行重写。

redis用命令 BGREWRITEAOF 执行AOF文件的重写。通过AOF文件的重写,redis 将生成一个新的 AOF 文件, 这个文件包含重建当前数据集所需的最少命令。redis 2.2之前的版本需要手动执行BGREWRITEAOF命令,在redis2.4版本后可以通过设置配置文件自动触发AOF重写。

在AOF持久化方式中,可以配置 redis 多久才将数据 fsync 到磁盘一次。有三个选项:

1
2
3
1) 每次有新命令追加到 AOF 文件时就执行一次 fsync :非常慢,也非常安全。
2) 每秒 fsync 一次:足够快,并且在故障时只会丢失 1 秒钟的数据。
3) 从不 fsync :将数据交给操作系统来处理。更快,也更不安全的选择。

redis默认是每秒 fsync 一次,这种方式可以兼顾速度和安全性,最多丢失1秒钟的数据。一般也推荐每秒 fsync 一次的方式,实际应用场景下可以适当调整。

AOF 文件重写和 RDB 创建快照类似,都巧妙地利用了写时复制(copy-on-write)机制。以下是 AOF 重写的执行步骤:

1) redis 执行 fork()创建一个子进程

2) 子进程开始将新 AOF 文件的内容写入到临时文件

3) 对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件的末尾: 这样即使在重写的中途发生停机,现有的 AOF 文件也还是安全的。

4) 当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有数据追加到新 AOF 文件的末尾。

5)redis 原子地用新文件替换旧文件,之后所有命令都会直接追加到新 AOF 文件的末尾。

redis服务器如果在对 AOF 文件进行写入操作时发生宕机,那么AOF文件可能发生错误,redis会在重启时拒绝载入这个AOF文件,从而确保数据的一致性不会被破坏。当发生这种情况时, 可以用 redis-check-aof [--fix] <file.aof> 命令来修复出错的 AOF 文件。

此处输入图片的描述

AOF持久化配置

AOF持久化的配置和RDF类似,只需要在redis的配置文件中设置相关配置项即可。AOF相关的配置项有如下几项:

1
2
3
4
5
6
7
8
9
10
appendonly <yes/no>

appendfilename <filename>.aof

auto-aof-rewrite-percentage <value>
auto-aof-rewrite-min-size <size>

appendfsync <always/everysec/no>

no-appendfsync-on-rewrite <yes/no>
  • appendonly <yes/no>

开启或关闭AOF持久化。

  • appendfilename <filename>.aof

设置.aof文件的名称。

  • auto-aof-rewrite-percentage <value>, auto-aof-rewrite-min-size <size>

设置AOF文件自动重写,当AOF增长超过一定比例时触发(比如AOF文件体积增大1%)。将auto-aof-rewrite-percentage设置为 0 时表示关闭AOF自动重写,这是可以用 BGREWRITEAOF 命令手动执行重写。

  • appendfsync <always/everysec/no> 设置fsync的频率。
1
2
3
4
no: don't fsync, let the OS flush the data when it wants
(usually every 30 sec.). Faster.
always: fsync after every write. Slow, Safest.
everysec: fsync one time every second. Compromise.
  • no-appendfsync-on-rewrite <yes/no> 设置在BGSAVE或者BGREWRITEAOF执行时禁止或允许执行fsync操作。

AOF持久化优缺点

  • 使用 AOF 持久化会让 Redis 变得非常耐久(much more durable),不会丢失太多数据。

  • AOF 文件是一个只进行追加操作的日志文件(append only log),是一种顺序写,写入速度非常快。

  • AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很方便。

AOF缺点

  • 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。

  • 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。

选择RDB还是AOF?

redis默认通过RDB方式进行持久化操作。如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化,RDB在redis发生故障重启时的速度非常快。如果你不能承受几分钟的数据丢失,那么你必须使用AOF方式进行持久化。

redis 还可以同时使用 AOF 持久化和 RDB 持久化。 在这种情况下, 当 Redis 重启时, 它会优先使用 AOF 文件来还原数据集, 因为 AOF 文件保存的数据集通常比 RDB 文件所保存的数据集更完整。一般来说, 如果想达到足以媲美关系型数据库的数据安全性, 你应该同时使用两种持久化功能。

怎么从 RDB 持久化切换到 AOF 持久化?在不重启情况下,从 RDB 切换到 AOF 方法:

(1) 为最新的 dump.rdb 文件创建一个备份。

(2) 将备份放到一个安全的地方。

(3) 执行以下两条命令:

1
2
redis-cli> CONFIG SET appendonly yes
redis-cli> CONFIG SET save ""

redis数据备份

  • 创建一个定期任务(cron job), 每小时将一个 RDB 文件备份到一个文件夹, 并且每天将一个 RDB 文件备份到另一个文件夹。
  • 确保快照的备份都带有相应的日期和时间信息, 每次执行定期任务脚本时, 使用 find 命令来删除过期的快照: 比如说, 你可以保留最近 48 小时内的每小时快照, 还可以保留最近一两个月的每日快照。
  • 至少每天一次, 将 RDB 备份到你的数据中心之外, 或者至少是备份到你运行 Redis 服务器的物理机器之外。

Ref

Comments