Redis学习笔记 - 持久化

参考:

  • <>:这本书是基于Redis3.0版本写的,和后面的版本有点差异
  • Redis持久化:http://www.redis.cn/topics/persistence.html

Redis是一个内存数据库,它将所有的数据都存储在内存中,所以一旦服务器进程退出,那么这些数据都将丢失。因此,需要将数据持久化到文件中,便于下次启动Redis服务时进行恢复。Redis提供了两种方式持久化:

  • 全量写入RDB:
    • 阻塞式 - SAVE
    • 非阻塞式 - BGSAVE
  • 增量写入AOF

一、全量写入RDB

RDB是默认的持久化方式。我们可以通过命令形式手动执行持久化,也可以通过配置文件方式配置触发条件自动执行持久化。默认情况下,redis.conf配置文件中设置了RDB持久化方式的触发条件以及其他相关配置。

RDB持久化生成的RDB文件是一个经过压缩的二进制文件,通过该文件可以还原生成RDB文件时的数据库状态。

1.1 手动执行持久化

这里主要用到两个命令: SAVEBGSAVE

  • SAVE:阻塞式,当执行这个命令时把redis内存里的数据库状态写入到RDB文件中,直到该文件创建完毕的这段时间内阻塞redis服务器进程,Redis服务器将不能处理任何命令请求。
  • BGSAVE:非阻塞式,它会创建一个子进程专门去把内存中的数据库状态写入RDB文件里,同时主进程还可以处理来自客户端的命令请求。但子进程基本是复制的父进程,这等于两个相同大小的redis进程在系统上运行,会造成内存使用率的大幅增加。

注:

  • 执行完SAVEBGSAVE命令后,可以看到在redis目录下生成了dump.rdb文件,在服务器重启后将会使用这个文件进行恢复。对于这个文件的配置方式在1.2节有说明。
  • BGSAVE命令执行期间,客户端若发送SAVEBGSAVE命令会被服务器拒绝,防止产生竞争条件。

1.2 自动持久化

Redis允许通过配置 save 选项,让服务器在满足一定条件下自动执行一次BGSAVE命令。

(1)具体配置在redis.conf文件中有详细说明,默认的RDB配置如下:

# save  
# 配置触发条件
save 900 1
save 300 10
save 60 10000
# yes,当bgsave失败时,Redis服务器将停止接受写入操作
stop-writes-on-bgsave-error yes
# yes,启动压缩
rdbcompression yes
# yes,在文件末尾创建一个CRC64校验和
rdbchecksum yes
# 文件名称
dbfilename dump.rdb
# 文件写入目录
dir ./

参数说明:

  • 前3行是触发RDB的条件:
    • 第一行:每900秒redis有一条数据被修改则触发RDB持久化
    • 第二三行类似
  • rdbcompression:yes,当bgsave失败时,Redis服务器将停止接受写入操作。压缩可以减小文件大小,但会在LZF压缩时消耗更多的CPU
  • rdbchecksum:yes,在文件末尾创建一个CRC64校验和。使用该选项在保存和加载文件时额外消耗约10%。设置为no可以获得最大性能,同时也会降低对数据损坏的抵抗力

注:修改这些配置后需重启redis服务使其生效。

1.3 RDB文件载入

RDB文件的载入是在服务器启动时自动执行的,Redis中没有专门用于载入RDB文件的命令。只要Redis服务器启动时检测到RDB文件存在,它就会自动载入RDB文件。

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

Redis服务器载入文件判断流程如下:
Redis学习笔记 - 持久化_第1张图片

(1)示例:

连接redis客户端,执行SAVEBGSAVE命令后,放入数据,此时关闭redis服务器,再重新启动,可以看到会有从磁盘加载数据的日志。启动后连接服务器查看数据是否存在。

redis> save
OK
redis> bgsave
Background saving started
redis> set num 1
OK
redis> shutdown
not connected>

# 重启
$ ./redis-server ./redis.conf
# 日志
5434:M 11 Jul 2019 18:49:32.933 # Server initialized
5434:M 11 Jul 2019 18:49:32.934 * DB loaded from disk: 0.001 seconds
5434:M 11 Jul 2019 18:49:32.934 * Ready to accept connections

# ...
redis> get num 
"1"

1.4 save命令的实现

(1)设置保存条件

默认情况下,save命令配置如下:

save 900 1
save 300 10
save 60 10000

这些保存条件由设置服务器状态的redisServer结构的saveparams属性保存:

struct redisServer {
	// ...
	// 记录了保存条件的数组
	struct saveparam *saveparams;
	// ...
}

saveparams属性是一个数组,数组中每个元素是一个saveparam结构,每个saveparam结构都保存了一个save选项设置的保存条件:

struct saveparam {
	// 秒数
	time_t seconds;
	// 修改数
	int changes;
}

默认情况save条件下服务器状态的saveparams属性如下图所示:
Redis学习笔记 - 持久化_第2张图片

服务器状态中保存的条件

(2)dirty计数器(属性)和lastsave属性

除了saveparams属性外,服务器状态还维护还维持着一个dirty计数器、lastsave属性:

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

服务器状态的redisServer结构的dirty、lastsave属性如下:

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

当服务器成功执行一个数据库修改命令之后,程序就会对dirty计数器(属性)进行更新:命令修改了多少次数据库,dirty计数器的值就会增加多少。

(3)检查条件是否满足

Redis服务器的周期性操作函数serverCron默认每隔100ms就会执行一次,这个函数用于对正在运行的服务器进行维护,其中一项工作就是检查save选项所设置的保存条件是否满足,满足的话就执行BGSAVE命令。

serverCron函数检查保存条件过程如下:

  • 遍历所有保存条件(redisServer结构的saveparams属性)
  • 计算距离上次保存操作过去多少秒,判断修改次数是否超过设置的次数、距离上次操作的描述是否超过设置的时间,如果都满足,则执行保存操作
  • 执行保存操作后,dirty属性重置为0,lastsave属性更新为当前时间。

相关流程图如下:
Redis学习笔记 - 持久化_第3张图片

二、增量写入AOF(Append Only File)

AOF持久化方式默认不开启;它是保存对Redis数据库的写操作(如set、sadd、rpush等命令)来记录数据库状态,并追加到AOF文件中;Redis 重启时,通过载入和执行AOF文件中保存的命令来还原服务器的数据库状态。

AOF持久化方式的数据一致性会比RDB持久化方式要高。

2.1 AOF配置

AOF相关配置参数如下:

# 持久化文件保存目录
dir ./ 
# 开启AOF持久化,默认no关闭,yes开启
appendonly no 
# AOF持久化文件名
appendfilename "appendonly.aof" 
# AOF持久化触发条件
appendfsync no 
# AOF重写触发条件
auto-aof-rewrite-percentage 100 
# AOF重写触发条件
auto-aof-rewrite-min-size 64mb 

参数说明:

(1)AOF持久化触发条件(控制何时将aof缓冲区内容写入AOF文件)

appendfsync三个可选值:

  • always:redis每执行一次写操作,将该命令追加到一个单独的AOF缓冲区的末尾,同时 立即把AOF缓冲区的内容强制写入AOF持久化文件中;效率慢、安全性高
  • everysec:redis每执行一次写操作,将该命令追加到一个单独的AOF缓冲区的末尾,之后 每隔一秒将缓冲区内容同步到文件中;这种是效率和安全性的折中
  • no:它也是将命令添加到缓冲区末尾,但是 由系统决定何时将缓冲区同步到持久化文件中(Linux系统一般是30s)。效率最快,安全最差,若是数据丢失无所谓那么可以设置auto-aof-rewrite-percentage、auto-aof-rewrite-min-size

注:Redis服务器接收到写入命令时,Redis会将该命令追加到AOF文件中。实际上,操作系统维护了一个缓冲区,Redis的命令首先会被写入到这个缓冲区中,而缓冲区中的数据必须被刷新到磁盘中才能被永久保存。这个过程是通过Linux系统调用 fsync() 完成的。这是一个阻塞调用,只有磁盘报告设备缓冲区中的数据写入完成之后才会返回。

  • 上面的appendfsync参数可选值也就是控制fsync()调用的频率。
  • 当Redis服务器被关闭时,fsync()会被显示调用,以确保写入缓冲区的所有数据都会被刷新到磁盘中。

2.2 AOF文件

(1)AOF文件

被写入AOF文件的所有命令都是以Redis的命令请求协议格式(纯文本格式)保存的。

  • 执行以下命令:
redis> set num 1
OK
redis> get num
"1"
redis> set num 2
OK
  • 打开appendonly.aof文件
    可以看到里面就是刚刚上面所执行的修改命令,首先是select 0:选择0号数据库(16个数据库,0-15);之后是两个修改命令set num 1 set num 2
*2\r\n$6\r\nSELECT\r\n$1\r\n0\r\n
*3\r\n$3\r\nset\r\n$3\r\nnum\r\n$1\r\n1\r\n
*3\r\n$3\r\nset\r\n$3\r\nnum\r\n$1\r\n2\r\n

(2)AOF文件的载入与数据还原

AOF文件里包含了重建数据库状态所需的所有写命令,所以服务器只要读入并重新执行一遍AOF文件里保存的写命令,就可以还原服务器关闭之前的数据库状态。

Redis读取AOF文件并还原数据库状态的步骤如下:

  • 1.创建一个不带网络连接的伪客户端(fake client)
  • 2.从AOF文件中分析并读取出一条写命令
  • 3.使用伪客户端执行被读出的写命令
  • 一直执行步骤2和步骤3,直到AOF文件中的所有写命令都被处理完毕为止

上述步骤流程图如下:
Redis学习笔记 - 持久化_第4张图片
(3)修复损坏的AOF文件工具

如果操作系统崩溃,那么AOF文件最后可能会损坏或截断。Redis提供了一个工具,redis-check-aof,用于修复损坏的文件。例如修复一个损坏的AOF文件,可以执行以下命令:

$ bin/redis-check-aof --fix appendonly.aof

2.3 AOF文件重写(AOF Rewrite)

Redis将命令不断追加到AOF文件中,这个文件的大小会显著增加,这就会使得重启时恢复数据库状态比较慢。Redis提供了一种机制,即AOF重写来压缩AOF文件。

通过该功能,Redis服务器可以创建一个新的AOF文件来替代现有的AOF文件,新旧两个AOF文件所保存的数据库状态相同,但新的AOF文件不会包含任何浪费空间的冗余命令,所以重写后的AOF文件通常比旧文件要小得多。

例如以下几种可能的情况:

  • Redis的某些键已经被删除或过期
  • 某些键盘已经被更新了多次,只有最新的值需要存储在AOF文件中

AOF就是利用这些情况,删除多余的键或只存储最新的键等操作对文件进行数据压缩。

(1)两种重写方式

  • 手动重写:可以使用 BGREWRITEAOF命令来启动重写过程。
  • 配置Redis自动执行AOF重写:主要涉及到以下两个参数:
    • auto-aof-rewrite-min-size:如果文件大小小于此值则AOF重写不会被触发。默认值64M。
    • auto-aof-rewrite-percentage:Redis会记录最后一次AOF重写操作的文件大小。如果当前的AOF文件大小增长超过了这个百分比,则触发一次重写。将此值设置为0将禁用自动AOF重写。默认值100。

(2)重写实现

Redis中重写程序是在子进程中实现,这样有2个目的:

  • 子进程进行AOF重写期间,服务器进程(父进程)可以继续处理客户端命令请求
  • 子进程带有服务器进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全性

AOF文件重写的问题

问题:子进程在进行AOF重写期间,服务器进程还需要继续处理命令请求,而新的命令可能会对现有的数据库状态进行修改,使得服务器当前的数据库状态和重写后的AOF文件所保存的数据库状态不一致。

解决方法:为了解决重写期间数据不一致情况,Redis服务器设置了一个AOF重写缓冲区,这个缓冲区在服务器创建子进程时开始使用,当Redis服务器执行完一个写命令后,它会同时将这个写命令发送给AOF缓冲区和AOF重写缓冲区。流程如下图所示:
Redis学习笔记 - 持久化_第5张图片
当子进程完成AOF重写工作后,它会向父进程发送一个信号,父进程在接收到该信号后,调用一个信号处理函数,执行以下工作:

  • 将AOF重写缓冲区中的所有内容写入到新的AOF文件中,这时新的AOF文件所保存的数据库状态将和服务器当前的数据库状态一致
  • 对新的AOF文件进行改名,原子地覆盖现有的AOF文件,完成新旧两个AOF文件的替换。

上述调用信号处理函数工程是阻塞的,会对服务器进程(父进程)造成阻塞,在重写期间的其他时候不会阻塞,结束后父进程正常接受客户端请求。

注:以上就是AOF重写的实现,也是BGREWRITEAOF命令的实现原理。

(3)重写后的AOF文件

对2.2节生成的AOF文件再手动执行下AOF重写:

redis> BGREWRITEAOF
Background append only file rewriting started

打开appendonly.aof文件,变成了以下内容:

REDIS0009ú      redis-ver^E5.0.3ú
redis-bitsÀ@ú^EctimeÂÑÍ/]ú^Hused-memÂ0Ø^O^@ú^Laof-preambleÀ^Aþ^@û^A^@^@^CnumÀ^Bÿ=òêú<88>)"G

三、RDB与AOF

3.1 RDB持久化方式的优缺点

RDB优点:

  • 适用于数据集的备份:RDB是一个非常紧凑的文件,它保存了某个时间点得数据集。比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题也可以根据需求恢复到不同版本的数据集。
  • 适合用于容灾恢复:RDB是一个紧凑的单一文件,可以很方便传送到另外一台服务器进行恢复数据集。
  • RDB在保存RDB文件时,父进程就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能。
  • 与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些。

RDB缺点:

  • 在redis意外停止工作(例如电源中断)的情况下,可能会丢失几分钟的数据,虽然可以配置不同的save时间点(例如每隔5分钟并且对数据集有100个写的操作)
  • RDB 需要经常fork子进程来保存数据集到磁盘上,当数据集比较大的时候,fork的过程非常耗时,可能会导致Redis在一些毫秒级内不能响应客户端的请求。如果数据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒。

3.2 AOF持久化方式的优缺点

AOF优点:

  • 使用AOF可以使得数据丢失更少: 使用不同的fsync策略:无fsync、每秒fsync、每次写的时候fsync。
    • 使用默认的每秒fsync策略,Redis的性能依然很好(fsync是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,最多丢失1秒的数据.
  • AOF文件是一个只进行追加的日志文件,所以不需要写入seek,即使由于某些原因(磁盘空间已满、写的过程中宕机等等)未执行完整的写入命令,可使用redis-check-aof工具修复。
  • Redis 可以在 AOF 文件过大时,自动地在后台对 AOF 进行重写:重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
  • AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析比较轻松 。导出 AOF 文件也非常简单: 举个例子, 如果不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写,那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令,并重启 Redis,就可以将数据集恢复到 FLUSHALL 执行之前的状态。

AOF缺点:

  • 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
  • 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快,即使在高负荷之下也是如此。不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。

3.3 如何选择使用哪种持久化方式

  • 如果既想快又想数据丢失少, 可以同时使用两种持久化功能。
  • 如果可以承受几分钟以内的数据丢失, 那么可以只使用 RDB 持久化。
  • 不推荐只使用 AOF 持久化: 因为定时生成 RDB 快照(snapshot)便于进行数据库备份,RDB 恢复数据集的速度也要比 AOF 恢复的速度要快,此外,使用 RDB 还可以避免之前提到的 AOF 程序的 bug(AOF文件损坏)。

注:

如果同时使用了RDB和AOF持久化方式,在启动服务器时:

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

你可能感兴趣的:(redis)