【Redis】回炉重学 RDB

491 阅读8分钟

这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

思维导图:

RDB-思维导图.png

零、前言

RDB(Redis DataBase):内存快照,即使宕机,快照文件也不会丢失。

优点:

  • RDB 是二进制压缩文件, 占用空间小, 便于传输(传给 slaver )

  • 主进程 fork 子进程, 可以最大化 Redis 性能, 主进程不能太大, Redis 的数据量不能太大, 复制过程中主进程阻塞

缺点: 不保证数据完整性, 会丢失最后一次快照以后更改的所有数据

考虑三个关键问题:

  1. 对哪些数据做快照? 这关系到快照的执行效率问题;

    Redis 的数据都在内存中, 为了提供所有数据的可靠性保证, 它执行的是全量快照, 也就是说, 把内存中的所有数据都记录到磁盘中。

  2. 做快照时, 数据还能被增删改吗? 这关系到 Redis 是否被阻塞, 能否同时正常处理请求。

  3. 多久做一次快照?

1. 对哪些数据做快照?

Redis 的数据都在内存中,为了提供所有数据的可靠性保证,它执行的是全量快照

也就是说,把内存中的所有数据都记录到磁盘中。

Redis 提供了两个命令来生成 RDB 文件:

  • save :在主线程中执行,会导致阻塞。
  • bgsave:创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是 Redis RDB 文件生成的默认配置。

那当然使用 bgsave 命令来执行全量快照,这既提供了数据的可靠性保证,也避免了对 Redis 的性能影响。


2. 做快照时,数据还能被增删改吗?

那么当然了,做快照时,Redis 中数据还能被增删了,要不然这技术就白瞎了。

先来了解下 Redis 中的快照:在某个时间点,保存内存中的数据到磁盘。

那么再来看下,数据操作有哪些:

  • 读操作:只是读数据,不会影响内存中数据,就不会影响快照。
  • 写操作:会影响,为了快照而暂停写操作,肯定是不能接受的。

那么是什么技术保障了快照一致性?

Redis 借助操作系统提供的 写时复制技术(Copy-On-Write, COW,在执行快照的同时,正常处理写操作。

说白了,就是数据要被修改了的时候,会被复制一份,生成该数据的副本。

讲深一点:

子进程复制了主进程的页表,所以通过页表映射,能读到主线程的原始数据,而当有新数据写入或数据修改时,主线程会把新数据或修改后的数据写到一个新的物理内存地址上,并修改主线程自己的页表映射。所以,子进程读到的类似于原始数据的一个副本,而主线程也可以正常进行修改。

举个栗子:

2021-04-0721-10-52.png

  1. 主进程:修改 键值对C ,会新开辟内存生成 键值对C',再把引用指向 键值对C' 的内存地址。
  2. 子进程:fork 主进程,会复制一份主进程里的 页表 (可以通过页表映射找到数据)
  3. 主进程:修改 键值对C ,同时其 页表 的映射也会相应改变,变为 页表'
  4. 子进程:还保持原有的 页表 ,就能找到原先的数据。所以不会被影响到。

3. 多久做一次快照?

这个考察方面比较多,多久同步一次得根据实际的情况:

  • 读写请求比例
  • 机器内存大小
  • 实际每分钟写请求次数

可以每秒做一次快照吗?不可以。原因如下:

  1. 频繁地执行全量快照,也会带来两方面的开销:
    • 磁盘压力过大:频繁将全量数据写入磁盘
    • 阻塞主进程:bgsave 子进程需要通过 fork 操作从主线程创建出来,这个过程会阻塞。

那如何做呢?

  1. 配置参数定期执行:比如 15分钟 内至少有1个键被修改,那就生成快照
  2. 定时:用脚本触发命令

Redis 4.0 中提出了一个混合使用 AOF 日志和 RDB内存快照的方法:

  1. 内存快照以一定的频率执行。
  2. 在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。



一、触发快照的方式

Redis 提供了两个命令来生成 RDB 文件, 分别是 savebgsave

触发方式:

  1. 符合自定义配置的快照规则
  2. 执行 save 或者 bgsave 命令
  3. 执行 flushall 命令
  4. 执行主从复制操作 (第一次)

(1)配置参数定期执行

save : 在主线程中执行, 会导致阻塞;

redis.conf 中配置: save 多少秒内,数据变了多少

漏斗设计,提供性能

save ""        # 不使用RDB存储,不能主从
save 900 1     # 表示15分钟(900秒钟)内至少1个键被更改则进行快照。
save 300 10    # 表示5分钟(300秒)内至少10个键被更改则进行快照。
save 60 10000  # 表示1分钟内至少10000个键被更改则进行快照。

(2)命令显式触发

bgsave : 创建一个子进程, 专门用于写入 RDB 文件, 避免了主线程的阻塞, 这也是 Redis RDB 文件生成的默认配置。

bgsave 子进程是由主线程 fork 生成的, 可以共享主线程的所有内存数据。 bgsave 子进程运行后, 开始读取主线程的内存数据, 并把它们写入 RDB 文件。

Redis 就会借助操作系统提供的写时复制技术(Copy-On-Write, COW), 在执行快照的同时, 正常处理写操作。

在客户端输入 bgsave 命令

127.0.0.1:6379> bgsave
Background saving started



二、RDB 执行流程

执行流程如图:

rdb执行流程.png

  1. Redis 父进程首先判断: 当前是否在执行 save, 或 bgsave/bgrewriteaof (aof 文件重写命令)的子进程, 如果在执行则 bgsave 命令直接返回。

  2. 父进程执行 fork (调用OS函数复制主进程)操作创建子进程, 这个复制过程中父进程是阻塞的, Redis 不能执行来自客户端的任何命令。

  3. 父进程 fork 后, bgsave 命令返回 “Background saving started” 信息并不再阻塞父进程,并可以响应其他命令。

  4. 子进程创建 RDB 文件, 根据父进程内存快照生成临时快照文件, 完成后对原有文件进行原子替换。(RDB 始终完整)

  5. 子进程发送信号给父进程表示完成, 父进程更新统计信息。

  6. 父进程 fork 子进程后, 继续工作。



三、RDB 文件结构

文件结构如图: 2020-09-0419:54.png

文件结构字段说明:

  1. 头部 5字节 固定为 REDIS 字符串
  2. 4字节 “RDB” 版本号(不是 Redis 版本号), 当前为9, 填充后为0009
  3. 辅助字段, 以 key-value 的形式,如下图:

2020-09-0419:56.png

  1. 存储数据库号码
  2. 字典大小
  3. 过期 key
  4. 主要数据, 以 key-value 的形式存储
  5. 结束标志
  6. 校验和, 就是看文件是否损坏, 或者是否被修改。

查看 dump.rdb 文件查看(可以用 winhex 工具查看)

这里用 vi 打开,输入命令 :%!xxd -g 1 切换到十六进制模式显示

00000000: 52 45 44 49 53 30 30 30 39 ef bf bd 09 72 65 64  REDIS0009....red
00000010: 69 73 2d 76 65 72 35 2e 30 2e 35 ef bf bd 0a 72  is-ver5.0.5....r
00000020: 65 64 69 73 2d 62 69 74 73 ef bf bd 40 ef bf bd  edis-bits...@...
00000030: 63 74 69 6d 65 ef bf bd 01 ef bf bd 5d 75 73 65  ctime.......]use
00000040: 64 2d 6d 65 6d ef bf bd ef bf bd 18 0a 20 20 20  d-mem........   
00000050: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20                  
00000060: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 ef bf                ..
00000070: bd 0a 20 20 20 20 20 20 20 20 20 20 20 20 20 20  ..              
00000080: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20                  
00000090: 20 20 20 20 61 6f 66 2d 70 72 65 61 6d 62 6c 65      aof-preamble
000000a0: ef bf bd ef bf bd ef bf bd 01 70 72 6f 64 75 63  ..........produc
000000b0: 74 5f 73 6b 75 5f 79 79 ef bf bd ef bf bd 34 ef  t_sku_yy......4.
000000c0: bf bd 25 ef bf bd 40 ef bf bd 37 ef bf bd 0a     ..%...@...7....