Redis持久化RDB原理+伪代码实现

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: Redis持久化RDB原理+伪代码实现

Redis持久化RDB原理+伪代码实现

Redis 分别提供了 RDBAOF 两种持久化机制, 本章首先介绍 Redis 服务器保存和载入 RDB 文件的方法,重点说明 SVAE 命令和 BGSAVE 命令的实现方式。之后,本章会继续介绍 Redis 服务器自动保存功能的实现原理。各个组成部分,并说明这些部分的结构和含义。在本章的最后,我们将对实际的 RDB 文件进行分析和解读,将之前学到的关于 RDB 文件的知识投人到实际应用中。其中还会查看有些伪代码方便理解,本文来源 redis设计与实现,关于 redis 持久化知识比较重要,所以直接看的书,避免走弯路,以这篇文章记录一下。

基本介绍

RDB 持久化既可以手动执行,也可以根据服务器配置选项定期执行,该功能可以将某个时间点上的数据库状态保存到一个 RDB 文件中。所生成的 RDB 文件是一个经过压缩的二进制文件,通过该文件可以还

原生成RDB文件时的数据库状态。

RDB文件的创建与载入

有两个 Redis 命令可以用于生成 RDB 文件,一个是 SAVE ,另一个是 BGSAVE

  • SAVE 命令会阻塞 Redis 服务器进程,直到 RDB 文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求
  • BGSAVE 命令会派生一个子进程,然后由子进程负责创建 RDB 文件,服务器进程(父进程)继续处理命令请求

创建 RDB 文件的实际工作由 rdb.c/rdbsave 函数完成, SAVE 命令和 BGSAVE 命令会以不同的方式调用这个函数,通过以下伪代码可以明显地看出这两个命令之间的区别:

def SAVE():
    rdbSave()
def BGSAVE():
    pid = fork()
    if pid == 0:
        # 子进程保存 RDB
        rdbSave()
    elif pid > 0:
        # 父进程继续处理请求,并等待子进程的完成信号
        handle_request()
    else:
        # pid == -1
        # 处理 fork 错误
        handle_fork_error()

RDB 文件的载入工作是在服务器启动时自动执行的,载入 RDB 文件的实际工作由 rdb.c/rdbLoad 函教完成,所以 Redis 并没有专门用于载人 RDB 文件的命令,只要 Redis 服务器在启动时检测到 RDB 文件存在,它就会自动载入 RDB 文件。

image.png

看到以上输出就是在成功载入 RDB 文件打印的,另外值得一提的是,因为 AOF 文件的更新频率通常比 RDB 文件的更新频率高,所以:

  • 如果服务器开启了 AOF 持久化功能,那么服务器会优先使用 AOF 文件来还原数据库状态。
  • 只有在 AOF 持久化功能处于关闭状态时,服务器才会使用 RDB 文件来还原数据库状态。

下图是服务器载入文件时的判断流程:

image.png

SAVE和BGSAVE命令执行时服务器的不同状态

SAVE

前面提到过,当 SAVE 命令执行时, Redis 服务器会被阻塞,所以当 SAVE 命令正在执行时,客户端发送的所有命令请求都会被拒绝。

只有在服务器执行完 SAVE 命令、重新开始接受命令请求后,客户端发送的命令才会被处理。

BGSAVE

因为 BGSAVE 命令的保存工作是由子进程执行的,所以在子进程创建 RDB 文件的过程中, Redis 服务器仍然可以继续处理客户端的命令请求,但是,在 BGSAVE 命令执行期间,服务器处理 SAVEBGSAVEBGREWRITEAOF 三个命令的方式会和平时有所不同。

首先,在 BGSAVE 命令执行期间,客户端发送的 SAVE 命令会被服务器拒绝,服务器禁止 SAVE 命令和 BGSAVE 命令同时执行是为了避免父进程(服务器进程)和子进程同时执行两个 rdbSave函数 调用,防止产生竞争条件。

其次,在 BGSAVE 命令执行期间,客户端发送的 BGSAVE 命令会被服务器拒绝,因为同时执行两个 BGSAVE 命令也会产生竞争条件

最后, BGREWRITEAOFBGSAVE 两个命令不能同时执行:

  • 如果 BGSAVE 命令正在执行,那么客户端发送的 BGREWRITEAOF 命令会被延迟到 BGSAVE 命令执行完毕之后执行。
  • 如果 BGREWRITEAOF 命令正在执行,那么客户端发送的 BGSAVE 命令会被服务器拒绝。

因为 BGSAVEBGREWRITEAOF 两个命令的实际工作都由子进程执行,所以这两个命令在操作方面并没有什么冲突的地方,不能同时执行它们只是一个性能方面的考虑。并发出两个子进程,并且这两个子进程都同时执行大量的磁盘写人操作,这怎么想都不会是一个好主意。

服务器在载入RDB文件期间,会一直处于阻塞状态,直到载入工作完成

自动间隔性保存

这个就是利用 BGSAVE 命令,设置相关条件执行命令,例如我们 redis 一般有如下配置:

save 900 1
save 300 10
save 60 10000

以上配置的解释

  • 服务器在 900 秒之内,对数据库进行了至少 1 次修改
  • 服务器在 300 秒之内,对数据库进行了至少 10 次修改
  • 服务器在 60 秒之内,对数据库进行了至少 10000 次修改

自动保存伪代码

struct redisServer {
    // 记录保存条件的数组
    struct saveparam *saveparams;
    // 修改计数器
    long long dirty;
    // 上一次执行保存的时间
    time_t lastsave;
    // ....
}
struct saveparam {
    // 秒数
    time_t seconds;
    // 修改数
    int changes;
}

大概用图表示是这样

image.png

除了 saveparams 数组之外,服务器状态还维持著一个 dirty 计数器,以及一个 lastsave 属性

  • dirty 计数器记录距离上一次成功执行 SAVE 命令或者 BGSAVE 命令之后,服务器对数据库状态(服务器中的所有数据库)进行了多少次修改(包括写入、删除、更新等操作)。
  • lastsave 属性是一个 UNIX 时间戳,记录了服务器上一次成功执行 SAVE 命令或者 BGSAVE 命令的时间。

例如:

SET message "hello" # 程序此时将 dirty计数器增加1
SADD database Redis MongoDB MariaDB # 程序此时将 dirty计数器增加3

image.png

上图就展示了服务器状态中包含的 dirty 计数器和 lastsave 属性,说明如下:

  • dirty 计数器的值为 123 ,表示服务器在上次保存之后,对数据库状态进行了 123 次修改
  • lastsave 属性则记录了服务器上次执行保存操作的时间戳

检查保存条件是否满足

Redis 的服务器周期性操作函数 servercron 默认每隔100毫秒就会执行一次,该函条件是否已经满足,如果满足的话,就执行 BGSAVE 命令。

以下伪代码展示了 servercron 函教检查保存条件的过程:

def serverCron():
    # 遍历所有条件
    for saveparam in server.saveparams:
        # 计算距离上次执行保存操作有多少秒
        save_interval = unixtime_now() - server.lastsave
        # 如果数据库状态的修改次数超过条件所设置的次数 并且距离上次保存的时间超过条件所设置的时间 那么执行保存操作
        if server.dirty >= saveparam.changes and save_interval > saveparam.seconds:
            BGSAVE()

通过以上代码可以得知,程序会遍历并检查 saveparams 数组中所有保存条件,只要有任意一个条件被满足,那么服务器就会执行 BGSAVE 命令

RDB文件结构

以下展示了一个完整 RDB 文件所包含的各个部分

image.png

REDIS

RDB 文件的最开头是 REDIS 部分,这个部分的长度为5字节,保存著"REDIS"五个字符。通过这五个字符,程序可以在载入文件时,快速检查所载入的文件是否 RDB 文件。

db_version

db_version 一个四字节长的以字符表示的整数,记录了该文件所使用的 RDB 版本号。目前的 RDB 文件版本为 0009 。因为不同版本的 RDB 文件互不兼容,所以在读入程序时,需要根据版本来选择不同的读入方式。

databases

databases 部分包含着零个或任意多个数据库,以及各个数据库中的键值对数据:

  • 如果服务器的数据库状态为空(所有教据库都是空的),那么这个部分也为空,长度为0字节
  • 如果服务器的数据库状态为非空(有至少一个数据库非空),那么这个部分也为非空,根据数据库所保存键值对的数量、类型和内容不同,这个部分的长度也会有所不同。

EOF

EOF 常量的长度为1字节,这个常量标志着 RDB 文件正文内容的结束,当读入程序遇到这个值的时候,它知道所有数据库的所有键值对都已经载入完毕了

CheckSum

check_sum 是一个8字节长的无符号整数,保存着一个校验和,这个校验和是程序通过对 REDISdb_versiondatabasesEOF四个部分的内容进行计算得出的。服务器在载人RDB文件时,会将载人数据所计算出的校验和与 check_sum 所记录的校验和进行对比,以此来检查 RDB 文件是否有出错或者损坏的情况出现。

Version 5 开始,如果在配置文件中开启 rdbchecksum yes ,会在 RDB 文件的结尾处,用 8 个字节, CRC64 计算整个文件内容的检验和。

举个例子

这个是我最新拉的 redis ,数据为空,我们依次进行分析: od -c rdb.rdb

0000000   R   E   D   I   S   0   0   0   9 372  \t   r   e   d   i   s
0000020   -   v   e   r 005   6   .   0   .   9 372  \n   r   e   d   i
0000040   s   -   b   i   t   s 300   @ 372 005   c   t   i   m   e 302
0000060   M 301 264   _ 372  \b   u   s   e   d   -   m   e   m 302   @
0000100 345  \f  \0 372  \f   a   o   f   -   p   r   e   a   m   b   l
0000120   e 300  \0 377   g 311 203 274 200   T 211 376
0000134

实际上这些字段是 AOFRDB 通用部分的文件头内容:

  1. 头5字节固定为REDIS
  2. 第6~9共四字节为 RDB 版本号
  3. 接下来为 redis-ver 和它的值,即 redis 版本
  4. 接着 redis-bits 和它的值,即 redis 的位数,值为32或64
  5. 接着为 ctime 和它的值, RDB 文件创建时间
  6. 接着为 used-mem 使用内存大小
  7. 最后是 aof-preamble 和它的值,值为0或1,1表示RDB有效。

RDB 文件头在 aof-preamble 之前多了如下三项:

  • repl-stream-dbserver.master 客户端中选择的数据库
  • repl-id 当前实例 replication ID
  • repl-offset 当前实例复制的偏移量

你学会了

  • RDB 文件用于保存和还原 Redis 服务器所有数据库中的所有键值对教据。
  • SAVE 命令由服务器进程直接执行保存操作,所以该命令会阻塞服务器。
  • BGSAVE 令由子进程执行保存操作,所以该命令不会阻塞服务器。
  • 服务器状态中会保存所有用 save 选项设置的保存条件,当任意一个保存条件被潮足时,服务器会自动执行 BGSAVE 命令。
  • RDB 文件是一个经过压缩的二进制文件,由多个部分组成。
  • 对于不同类型的键值对, RDB 文件会使用不同的方式来保存它们。
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
1天前
|
存储 NoSQL Redis
Redis 持久化揭秘:选择 RDB、AOF 还是混合持久化?
Redis 是一个内存数据库,意味着它主要将数据存储在内存中,从而能够提供极高的性能。然而,作为内存数据库,Redis 默认情况下的数据不会永久保存。为了确保数据在重启或故障后能够恢复,Redis 提供了几种 **持久化机制**。这些机制允许 Redis 将内存中的数据保存到硬盘上,从而实现数据持久化。
40 22
Redis 持久化揭秘:选择 RDB、AOF 还是混合持久化?
|
16天前
|
NoSQL 安全 Redis
redis持久化策略
Redis 提供了两种主要的持久化策略:RDB(Redis DataBase)和AOF(Append Only File)。RDB通过定期快照将内存数据保存为二进制文件,适用于快速备份与恢复,但可能因定期保存导致数据丢失。AOF则通过记录所有写操作来确保数据安全性,适合频繁写入场景,但文件较大且恢复速度较慢。两者结合使用可增强数据持久性和恢复能力,同时Redis还支持复制功能提升数据可用性和容错性。
37 5
|
29天前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
39 5
|
1月前
|
监控 NoSQL 测试技术
【赵渝强老师】Redis的AOF数据持久化
Redis 是内存数据库,提供数据持久化功能,支持 RDB 和 AOF 两种方式。AOF 以日志形式记录每个写操作,支持定期重写以压缩文件。默认情况下,AOF 功能关闭,需在 `redis.conf` 中启用。通过 `info` 命令可监控 AOF 状态。AOF 重写功能可有效控制文件大小,避免性能下降。
|
1月前
|
存储 监控 NoSQL
【赵渝强老师】Redis的RDB数据持久化
Redis 是内存数据库,提供数据持久化功能以防止服务器进程退出导致数据丢失。Redis 支持 RDB 和 AOF 两种持久化方式,其中 RDB 是默认的持久化方式。RDB 通过在指定时间间隔内将内存中的数据快照写入磁盘,确保数据的安全性和恢复能力。RDB 持久化机制包括创建子进程、将数据写入临时文件并替换旧文件等步骤。优点包括适合大规模数据恢复和低数据完整性要求的场景,但也有数据完整性和一致性较低及备份时占用内存的缺点。
|
7月前
|
NoSQL Redis
03- Redis的数据持久化策略有哪些 ?
Redis的数据持久化包括两种策略:RDB(全量快照)和AOF(增量日志)。RDB在指定时间间隔将内存数据集保存到磁盘,而AOF记录所有写操作形成日志。从Redis 4.0开始,支持RDB和AOF的混合持久化,通过设置`aof-use-rdb-preamble yes`。
61 1
|
5月前
|
canal 缓存 NoSQL
Redis常见面试题(一):Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;先删除缓存还是先修改数据库,双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
Redis常见面试题(一):Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
|
6月前
|
存储 缓存 JSON
Redis-持久化-淘汰机制-IO策略
Redis-持久化-淘汰机制-IO策略
|
7月前
|
存储 NoSQL 关系型数据库
Redis持久化策略AOF、RDB详解及源码分析
Redis持久化策略AOF、RDB详解及源码分析
|
存储 NoSQL 关系型数据库
Redis的持久化策略(RDB、AOF、RDB-AOF混合持久化)
Redis的持久化策略(RDB、AOF、RDB-AOF混合持久化)
173 0