Redis 持久化:RDB 快照 和 AOF 日志

摘要:Redis 之所以速度这么快,是因为 Redis 是基于内存的数据库,进行读写操作时,redis 都会现在内存中完成,然后定时的刷新到磁盘中去,RDB 和 AOF 就是两种持久化内存中数据的方式。本文主要介绍 RDB 持久化的原理和机制,下篇文章介绍 AOF。

Redis 之所以速度这么快,是因为 Redis 是基于内存的数据库,进行读写操作时,redis 都会现在内存中完成,然后定时的刷新到磁盘中去,RDB 和 AOF 就是两种持久化内存中数据的方式。

  • 笼统来讲,RDB 可以将某一时刻的所有数据写入硬盘中,相当于复制了一份数据;
  • 而 AOF 会在 Redis 执行写命令时,将被执行的写命令复制到磁盘中。恢复数据的时候,Redis 会在原有基础上依次执行 AOF 文件中的写命令,从而恢复数据。

本文主要介绍 RDB 持久化的原理和机制,下篇文章介绍 AOF

RDB 快照

简介

Redis 将某一时刻的快照(内存数据)存储在一个 RDB 格式的文件中,这种格式文件是经过压缩的二进制文件,在 Redis 服务重新启动的时候,会加载 RDB 文件中的数据。

工作机制

RDB 快照工作机制

  • 主线程 fork 生成一个子进程,该子进程可以共享主线程内存里面的代码段和数据段数据。
  • 进程分离的一瞬间,内存的增长几乎没有明显变化。子进程能看到的数据,在进程产生的一瞬间就再也不会改变了,所以把 RDB 这种持久化称做快照
  • bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件,这一过程称为全量快照(图 ①②)。
  • 如果主线程修改了数据(如图中的键值对 C),此时 Redis 会借助操作系统提供的写时拷贝(Copy-On-Write),把这块数据拷贝一份,生成该数据的副本。
  • 然后,bgsave 子进程会把这个副本数据写入 RDB 文件,这一过程称为增量快照(如图 ①②③④)。而在这个过程中,主进程仍然可以处理数据操作。
  • 随着主线程修改操作的持续进行,越来越多的共享页面被分离出来,内存就会持续增长,但是不会超过内存的两倍大小。

创建与载入

Redis 有两个命令可以用于生成 RDB 文件,一个是 save,一个是 bgsave,一般用的都是 bgsave 命令。

  • save 命令会阻塞 Redis 服务器主线程,在阻塞期间,服务器不能处理任何命令请求,直到 RDB 文件创建完毕为止;
  • bgsave 命令会 fork(派生)出一个子进程,由子进程负责创建 RDB 文件,避免了主线程的阻塞(默认配置)。

Fork 的作用是复制一个与当前进程一样的进程,新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一样,但是是一个全新的进程,并作为原进程的子进程。

因为 AOF 文件的更新频率通常比 RDB 文件的更新频率高,所以:

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

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

配置文件

除了特殊情况外,我们一般是不会直接使用命令来生成 RDB 文件的,Redis 提供了自动生成 RDB 文件的功能,配置在 redis.conf 中。

默认配置如下:

save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir ./
  • save:save 配置项可以同时存在多个,配置时后面需要加上两个数字,分别代表秒数和更改数。
    • save m n 表示 Redis 服务器在 m 秒之内,对数据进行了至少 n 次更改,Redis 就会自动执行一次 bgsave 命令;
    • 如果 save 太过频繁,会影响到 Redis 的性能,频率太低则会增加数据丢失的风险,所以建议根据需要对 save 项进行配置;
    • 如果移除所有的 save 配置项或者使用 save "" 空字符串,则持久化功能会被关闭。
  • stop-writes-on-bgsave-error:bgsave 错误时停止写入,默认为 yes,即停止写入。
  • rdbcompression:是否对 RDB 文件进行压缩。
  • rdbchecksum:是否对 RDB 文件进行校验和检验。
    • 如果启用,可以保证文件的完整性,以此来检查 RDB 文件是否有出错或者损坏的情况出现;
    • 但是进行文件存储和加载时会损失 10% 左右的性能,如果追求最大性能可以考虑把这一项关闭。
  • dbfilename:配置 RDB 文件名称,一般叫 dump.rdb。
  • dir:配置 RDB 文件存储的路径,AOF 产生的文件也会被存储到这个路径里。

校验和(checksum),在数据处理和数据通信领域中,用于校验目的地一组数据项的和,它通常是以十六进制为数制表示的形式。

如果校验和的数值超过十六进制的 FF,也就是 255,就要求其补码作为校验和。通常用来在通信中,尤其是远距离通信中保证数据的完整性和准确性。

快照修改

增量快照

做了一次全量快照后,后续的快照只对修改的数据进行快照记录,这样可以避免每次全量快照的开销。

和 AOF 相比,快照的恢复速度快,但快照的频率不好把握。

  • 如果频率太低,两次快照间一旦宕机,就可能有比较多的数据丢失;
  • 如果频率太高,又会产生额外开销。

在第一次做完全量快照后,T1 和 T2 时刻如果再做快照,我们只需要将被修改的数据写入快照文件就行。但是,这么做的前提是,我们需要记住哪些数据被修改了。

增量快照

混合持久化

Redis 4.0 中提出了一个混合使用 AOF 日志和内存快照的方法,它融合了 RDB 恢复速度快和 AOF 丢失数据少的优点。内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。

如果想要开启混合持久化功能,需要在 Redis 配置文件将下面这个配置项设置成 yes:

aof-use-rdb-preamble yes

混合持久化

  • 当开启了混合持久化时,在 AOF 重写日志时,fork 出来的重写子进程,会先将与主线程共享的内存数据,以 RDB 方式写入到 AOF 文件;
  • 然后主线程处理的操作命令,被记录在重写缓冲区里,重写缓冲区里的增量命令,会以 AOF 方式写入到 AOF 文件;
  • 写入完成后,通知主线程,将新的含有 RDB 格式和 AOF 格式的 AOF 文件,替换旧的的 AOF 文件。

使用混合持久化,AOF 文件的前半部分是 RDB 格式的全量数据,后半部分是 AOF 格式的增量数据。这样的好处在于:

  • 重启 Redis 加载数据的时候,由于前半部分是 RDB 内容,这样加载的时候速度会很快;
  • 加载完 RDB 的内容后,才会加载后半部分的 AOF 内容,这里的内容是 Redis 后台子进程重写 AOF 期间,主线程处理的操作命令,可以使得数据更少的丢失。

文件结构

基本结构

RDB 文件为二进制格式存储,下面我们为了演示效果,采用字符串的形式演示。

    RDB 基本结构

    REDIS(常量)

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

    db_version(变量)

    长度为 4 字节,它的值是一个字符串表示的整数,这个整数记录了 RDB 文件的版本号,比如 "0006" 就代表 RDB 文件的版本为第六版。

    databases(变量)

    databases 部分包含着 0 个或多个数据库,以及各个数据库中的键值对数据。

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

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

    check_sum(变量)

    check_sum 是一个 8 字节长的无符号整数,存储着一个校验和,这个校验和是程序通过对 REDIS、db_version、databases、EOF 四个部分的内容进行计算得出的。

    服务器在载入 RDB 文件时,会将载入数据所计算出的校验和,与 check_sum 所记录的校验和进行对比,以此来检查 RDB 文件是否有出错或者损坏的情况出现。

    作为例子,下图展示了一个 databases 部分为空的 RDB 文件:

    databases 部分为空的 RDB 文件

    文件开头的 "REDIS" 表示这是一个 RDB 文件,之后的 "0006" 表示这是第六版的 RDB 文件,因为 databases 为空,所以版本号之后直接跟着 EOF 常量,最后的 6265312314761917404 是文件的校验和。

databases 结构

一个 RDB 文件的 databases 部分可以存储任意多个非空数据库。

  • 例如,如果服务器的 0 号数据库和 3 号数据库非空,那么服务器将创建一个如下图所示的 RDB 文件。
  • 0 号数据库和 3 号数据库非空

  • 每个非空数据库的结构可分为三个部分,如下图所示。
  • databases 结构

    SELECTDB(常量)

    长度为 1 字节,当程序读入到这个值的时候,它知道接下来要读入的将是一个数据库号码。

    db_number(变量)

    db_number 存储着一个数据库号码,根据号码的大小不同,这个部分的长度可以是 1 字节、2 字节或 5 字节。

    当程序读入 db_number 部分之后,服务器会调用 SELECT 命令,根据读入的数据库号码进行数据库切换,使得之后读入的键值对可以载入到正确的数据库中。

    key_value_pairs(数据)

    key_value_pairs 存储了数据库中的所有键值对数据,如果键值对带有过期时间,那么过期时间会和键值对存储在一起。

    根据键值对的数量、类型、内容以及是否有过期时间等条件的不同,key_value_pairs 部分的长度也会有所不同。

key_value_pairs 结构

不带过期时间的键值对在 RDB 文件中由 TYPE、key、value 三部分组成。

不带过期时间的键值对结构

  • TYPE(常量):记录了 value 的类型,长度为 1 字节,值可以是以下常量的其中一个。
    • REDIS_RDB_TYPE_STRING(字符串)
    • REDIS_RDB_TYPE_LIST(列表)
    • REDIS_RDB_TYPE_HASH(哈希表)
    • REDIS_RDB_TYPE_SET(集合)
    • REDIS_RDB_TYPE_ZSET(有序集合)
    • REDIS_RDB_TYPE_SET_INTSET(整数集合)
    • REDIS_RDB_TYPE_LIST_ZIPLIST(压缩列表)
    • REDIS_RDB_TYPE_HASH_ZIPLIST(哈希表压缩列表)
    • REDIS_RDB_TYPE_ZSET_ZIPLIST(有序集合压缩列表)

    以上列出的每个 TYPE 常量都代表了一种对象类型或者底层编码,它们之间的关系如下图。

    对象类型和底层编码

    当程序读入 RDB 文件中的键值对数据时,会根据 TYPE 的值来决定如何读入和解释 value 的数据。

  • key:键对象,字符串类型。
  • value:值对象。根据 TYPE 类型的不同,以及存储内容长度的不同,存储 value 的结构和长度也会有所不同。

带有过期时间的键值对在 RDB 文件中由 EXPIRETIME_MS、ms、TYPE、key、value 五部分组成。

    带有过期时间的键值对结构

  • EXPIRETIME_MS(常量):长度为 1 字节,它告知读入程序,接下来要读入的将是一个以毫秒为单位的过期时间。
  • ms(变量):是一个 8 字节长的带符号整数,记录着一个以毫秒为单位的 UNIX 时间戳,这个时间戳就是键值对的过期时间。
  • TYPE、key、value:和前面不带有过期时间的键值对结构意义一样。

总结

优势

  • RDB 是在一定间隔时间做一次备份,可以很方便地将数据还原到特定的时间点;
  • RDB 文件是一种压缩的二进制文件,可以方便的在网络中传输和存储;
  • RDB 文件相比 AOF 占用的空间更小,恢复数据的速度也更快;
  • 采用子进程创建 RDB 文件,不会对 Redis 服务器性能造成大的影响;
  • 创建 RDB 文件时出现了错误,Redis 不会将它用于替换原来的文件,所以出错时不会影响到之前保存的版本。

劣势

  • RDB 是在一定间隔时间做一次备份,如果 Redis 服务意外停止的话,那么就会丢失最后一次快照后的数据;
  • Redis 内存的数据量比较大时,fork 创建子进程和生成 RDB 文件会占用更多系统资源和处理时间;
  • Redis 版本演进过程中有多个格式的 RDB 版本,存在老版本 Redis 服务无法兼容新版 RDB 格式的问题。
版权声明:本文为博主原创文章,未经博主允许不得转载。http://www.dedenotes.com/html/Redis-RDB.html
(1)
打赏 微信扫一扫 微信 支付宝 QQ 扫码打赏

meta

Dedenotes 赞(3)

meta 是 html 语言 head 区的一个辅助性标签,位于文档的头部,不包含任何内容,标签的属性定义了与文档相关联的名称/值对。meta 标签可提供相关页面的元信息(meta-information),比如针对搜索引擎和更新频度的描述和关键词。

防止表单重复提交的 4 种方法

Dedenotes 赞(3)

平时开发的项目中可能会出现下面这些情况:由于用户误操作,多次点击表单提交按钮;由于网速等原因造成页面卡顿,用户重复刷新提交页面;黑客或恶意用户使用 Postman 等工具重复恶意提交表单(攻击网站)。

HTTP消息结构 HTTP请求报文和响应报文的格式

Dedenotes 赞(3)

HTTP 协议(HyperText Transfer Protocol,超文本传输协议)是因特网上应用最为广泛的一种网络传输协议,基于 TCP/IP 通信协议来传递数据(HTML 文件, 图片文件, 查询结果等),所有的 WWW(World Wide Web)文件都必须遵守这个标准。