Redis持久化介绍

1.RDB & AOF 简介

Redis 提供了两种持久化策略

  • RDB 持久化机制,会在一段时间内生成指定时间点的数据集快照(snapshot)

  • AOF 持久化机制,记录 server 端收到的每一条写命令,当 server 重启时会进行重放以此来重建之前的数据集。AOF 文件中的命令全部以 Redis 协议的格式来保存,新命令会被追加(append)到文件的末尾。 Redis 还可以在后台对 AOF 文件进行重写(rewrite) ,使得 AOF 文件的体积不会超出保存数据集状态所需的实际大小。

  • 如果你仅使用 Redis 作为缓存加速访问,你可以关闭这两个持久化设置

  • 你也可以同时开启这两个持久化设置,但是在这种情况下,Redis 重启时会使用 AOF 文件来重建数据集,因为 AOF 文件保存的数据往往更加完整

2. 详解 RDB

2.1 RDB 创建与载入

Redis 提供了 SAVEBGSAVE 两个命令来生成 RDB 文件,区别是前者是阻塞的,后者是后台 fork 子进程进行不会阻塞主进程处理命令请求。载入 RDB 文件不需要手工运行,而是 server 端自动进行,只要启动时检测到 RDB 文件存在 server 端便会载入 RDB 文件重建数据集。当然上面简介中已经提到,如果 同时存在 AOF 的话会优先使用 AOF 重建数据集因为其保存的数据更完整。

SAVE 和 BGSAVE 命令实现

 

void saveCommand(client *c) {
    // BGSAVE执行时不能执行SAVE
    if (server.rdb_child_pid != -1) {
        addReplyError(c,"Background save already in progress");
        return;
    }
    rdbSaveInfo rsi, *rsiptr;
    rsiptr = rdbPopulateSaveInfo(&rsi);
    // 调用rdbSave函数执行备份(阻塞当前客户端)
    if (rdbSave(server.rdb_filename,rsiptr) == C_OK) {
        addReply(c,shared.ok);
    } else {
        addReply(c,shared.err);
    }
}

/*
* BGSAVE 命令实现 [可选参数"schedule"]
*/
void bgsaveCommand(client *c) {
    int schedule = 0;

    /* 当AOF正在执行时,SCHEDULE参数修改BGSAVE的效果
    * BGSAVE会在之后执行,而不是报错
    * 可以理解为:BGSAVE被提上日程
    */
    if (c->argc > 1) {
        // 参数只能是"schedule"
        if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"schedule")) {
            schedule = 1;
        } else {
            addReply(c,shared.syntaxerr);
            return;
        }
    }

    // BGSAVE正在执行,不操作
    if (server.rdb_child_pid != -1) {
        addReplyError(c,"Background save already in progress");
    } else if (server.aof_child_pid != -1) {
        // aof正在执行,如果schedule==1,BGSAVE被提上日程
        if (schedule) {
            server.rdb_bgsave_scheduled = 1;
            addReplyStatus(c,"Background saving scheduled");
        } else {
            addReplyError(c,
            "An AOF log rewriting in progress: can't BGSAVE right now. "
            "Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever "
            "possible.");
        }
    } else if (rdbSaveBackground(server.rdb_filename,NULL) == C_OK) {// 否则调用rdbSaveBackground执行备份操作
        addReplyStatus(c,"Background saving started");
    } else {
        addReply(c,shared.err);
    }
}

2.2 RDB 相关配置

SAVE POINT save <seconds> <changes>

你可以配置保存点(save point),Redis 如果每 N 秒数据发生了 M 次改变就保存快照文件,例如下面:

Save Point 配置

 

# 这个保存点配置表示每60秒,如果数据发生了1000次以上的变动,Redis就会自动保存快照文件
save 60 1000
# 保存点可以设置多个,Redis的配置文件就默认设置了3个保存点
# 格式为:save <seconds> <changes>
# 可以设置多个。
save 900 1 #900秒后至少1个key有变动
save 300 10 #300秒后至少10个key有变动
save 60 10000 #60秒后至少10000个key有变动

stop-writes-on-bgsave-error yes | no

如果 Redis 执行 RDB 持久化失败(常见于操作系统内存不足),那么 Redis 将不再接受 client 写入数据的请求。当然在实践中,我们通常会将 stop-writes-on-bgsave-error 设置为 false,同时让监控系统在 Redis 执行 RDB 持久化失败时发送告警,以便介入解决,而不是粗暴地拒绝 client 的写入请求。

rdbcompression yes | no

当生成 RDB 文件时,Redis 会判断字符串长度 >=20字节则压缩,否则不压缩存储,默认 Redis 会采用 LZF 算法进行数据压缩。

rdbchecksum yes | no

从版本5的 RDB 的开始,一个 CRC64 的校验码会放在文件的末尾。这样更能保证文件的完整性,但是在保存或者加载文件时会损失一定的性能(大概10%)。如果想追求更高的性能,可以把它禁用掉,这样文件在写入校验码时会用 0 替代,加载的时候看到 0 就会直接跳过校验。

2.3 RDB 的优点

  • RDB文件是一个很简洁的单文件,它保存了某个时间点的 Redis 数据集,很适合用于做备份。你可以设定一个时间点对 RDB 文件进行归档,这样就能在需要的时候很轻易的把数据恢复到不同的版本。

  • 基于上面所描述的特性,RDB 文件很适合用于灾备,因为单文件可以很方便地传输到另外的数据中心。

  • RDB的性能很好,需要进行持久化时,主进程会 fork 一个子进程出来,然后把持久化的工作交给子进程,自己不会有相关的 I/O 操作。

  • 比起 AOF,在数据量比较大的情况下,RDB的启动速度更快。

2.4 RDB 的缺点

  • RD B容易造成数据的丢失,当你希望在 Redis 停止工作时尽量减少数据丢失的话,那 RDB 不适用。假设每5分钟保存一次快照,如果Redis因为某些原因不能正常工作,那么从上次产生快照到 Redis 出现问题这段时间的数据就会丢失了。你可以通过配置不同的 save point 来减轻数据丢失的程度,但是越紧凑的 save point 会越频繁地触发 RDB 生成操作,从而对 Redis 性能产生影响

  • RDB 使用 fork 子进程进行数据的持久化,如果数据比较大的话可能就会花费点时间,造成 Redis 停止服务几毫秒,如果数据量很大且 CPU 性能不是很好的时候,停止服务的时间甚至会到一秒。AOF 也需要 fork 但是你可以自己调整 rewrite 的频率,它不会造成数据丢失。在 Linux 系统中,fork 会拷贝进程的 page table。随着进程占用的内存越大,进程的 page table 也会越大,那么 fork 也会占用更多的时间。 如果 Redis 占用的内存很大 (例如 20 GB),那么在 fork 子进程时,会出现明显的停顿现象(无法处理 client 的请求)。另外,在不同机器上,fork 的性能是不同的,可以参见 Fork time in different systems

  • Linux fork 子进程采用的是 copy-on-write 的方式。在 Redis 执行 RDB 持久化期间,如果 client 写入数据很频繁,那么将增加 Redis 占用的内存,最坏情况下,内存的占用将达到原先的两倍。

3. 详解 AOF

3.1 AOF 实现

和 RDB 持久化数据库键值对来记录数据库状态不同,AOF 是通过保存对数据库的写命令集来记录数据库状态的。AOF 持久化实现可以分为命令追加(append)、文件写入(write)、文件同步(fsync) 三个步骤。Append 追加命令到 AOF 缓冲区,Write 将缓冲区的内容写入到程序缓冲区,Fsync 将程序缓冲区的内容写入到文件。 

命令追加

当 AOF 持久化功能打开时,server 端每执行完一个写命令,会以协议格式将被执行的写命令追加到 serverredisServer 结构体中的 aof_buf 缓冲区末尾。

文件写入与同步

Redis server 进程是一个事件循环(event loop)server 每结束一个事件循环之前都会调用 flushAppendOnlyFile 函数,考虑是否将 aof_buf 缓冲区中的内容吸入和保存到 AOF 文件,而 flushAppendOnlyFile 函数的行为由 appendfsync 选项来控制

appendfsync 值

flushAppendOnlyFile 行为

always

每个事件循环都将 aof_buf 缓冲区中的内容写入 AOF 文件,并且调用 fsync() 将其同步到磁盘。这可以保证最好的数据持久性,但却会给系统带来极大的开销,其效率是三者中最慢的,但同时安全性也是最高的,即使宕机也只丢失一个事件循环中的数据。

no

每个事件循环都将 aof_buf 缓冲区中的内容写入 AOF 文件,但不对其进行同步,何时同步至磁盘会让操作系统决定。这种模式下 AOF 的写入速度最快,不过因其会在系统缓存中积累一段时间的数据,所以同步时间为三者最长。一旦宕机将会丢失自上一次同步 AOF 文件起所有的数据。

everysec

每个事件循环都将 aof_buf 缓冲区中的内容写入 AOF 文件,Redis 还会每秒在子线程中执行一次 fsync()。在实践中,推荐使用这种设置,一定程度上可以保证数据持久性,又不会明显降低 Redis 性能。

3.2 AOF 重写

AOF 持久化并不是没有缺点的,Redis 会不断将接收到的写命令追加到 AOF 文件中,导致 AOF 文件越来越大。过大的 AOF 文件会消耗磁盘空间,并且导致 Redis 重启时更加缓慢。为了解决这个问题,在适当情况下,Redis 会对 AOF 文件进行重写,去除文件中冗余的命令,以减小 AOF 文件的体积。

AOF的重写会执行大量的写入操作,Redis是单线程的,所以如果有服务器直接调用重写,服务器就不能处理其他命令了,因此Redis服务器新起了单独一个进程来执行AOF重写。当然也可以通过 BGREWRITEAOF 命令手动重写 AOF 文件。重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。

在子进程执行AOF重写时,服务端接收到客户端的命令之后,先执行客户端发来的命令,然后将执行后的写命令追加到AOF缓冲区中,同时将执行后的写命令追加到AOF重写缓冲区中。 等到子进程完成了重写工作后,会发一个完成的信号给服务器,服务器就将AOF重写缓冲区中的所有内容追加到AOF文件中,然后原子性地覆盖现有的AOF文件。

3.3 AOF 相关配置

aof 相关配置

Shell

# 你可以在 redis.conf 中通过以下配置开启 AOF 功能
appendonly yes
​
# 文件存放目录,与RDB共用。默认为当前工作目录。
dir ./
​
# 默认文件名为appendonly.aof
appendfilename "appendonly.aof"
​
# fsync 相关配置
# appendfsync always
appendfsync everysec
# appendfsync no
​
# Redis会记住自从上一次重写后AOF文件的大小(如果自Redis启动后还没重写过,则记住启动时使用的AOF文件的大小)。
# 如果当前的文件大小比起记住的那个大小超过指定的百分比,则会触发重写。
# 同时需要设置一个文件大小最小值,只有大于这个值文件才会重写,以防文件很小,但是已经达到百分比的情况。
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 上面两个配置的作用:当 AOF 文件的体积大于 64MB,并且 AOF 文件的体积比上一次重写之后的体积大了至少一倍,那么 Redis 就会执行 AOF 重写。
​
# 要禁用自动的日志重写功能,我们可以把百分比设置为0:
auto-aof-rewrite-percentage 0

3.4 AOF 的优点

  • 比RDB可靠。你可以制定不同的 fsync 策略:noeverysecalways。默认是 everysec。这意味着你最多丢失一秒钟的数据。

  • AOF日志文件是一个纯追加的文件。就算是遇到突然停电的情况,也不会出现日志的定位或者损坏问题。甚至如果因为某些原因(例如磁盘满了)命令只写了一半到日志文件里,我们也可以用 redis-check-aof 这个工具很简单的进行修复。

  • 当AOF文件太大时,Redis 会自动在后台进行重写。重写很安全,因为重写是在一个新的文件上进行,同时 Redis 会继续往旧的文件追加数据。新文件上会写入能重建当前数据集的最小操作命令的集合。当新文件重写完,Redis 会把新旧文件进行切换,然后开始把数据写到新文件上。

  • AOF 把操作命令以简单易懂的格式一条接一条的保存在文件里,很容易导出来用于恢复数据。例如我们不小心用 FLUSHALL 命令把所有数据刷掉了,只要文件没有被重写,我们可以把服务停掉,把最后那条命令删掉,然后重启服务,这样就能把被刷掉的数据恢复回来。

3.5 AOF 的缺点

  • 在相同的数据集下,AOF 文件的大小一般会比 RDB 文件大。

  • 在某些 fsync 策略下,AOF 的速度会比 RDB 慢。通常 fsync 设置为每秒一次就能获得比较高的性能,而在禁止 fsync 的情况下速度可以达到 RDB 的水平。

  • 在过去曾经发现一些很罕见的BUG导致使用AOF重建的数据跟原数据不一致的问题。

4. Redis 4.0 混合持久化

Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分就是压缩格式不再是 AOF 格式,可读性较差。

5. Squirrel 在 RDB & AOF上的源码优化

待补充

6. 写在最后

为什么持久化机制是 Redis 最令人困惑的特性之一? —— antirez,Redis 作者

Redis 的作者认为 Redis 的持久化机制是最令使用者误解和困惑的概念,困惑的来源是 Redis 的使用者对持久化机制的理解往往太想当然了。

原文地址:http://oldblog.antirez.com/post/redis-persistence-demystified.html

7. 参考资料

  1. Redis persistence demystified

  2. Redis Persistence From Redis.io

  3. 《Redis 设计与实现》黄建宏著

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值