00 前言
很多小伙伴都用 Redis 做缓存,那如果 Redis 服务器宕机,内存中数据全部丢失,应该如何做数据恢复呢?有人说很简单呀,直接从 MySQL 数据库再读回来就得了。
这种方式存在两个问题:一是频繁访问 MySQL 数据库,有一定的风险;二是慢,从界面上来看,从 MySQL 读就不如从 Redis 快。
远哥远哥,那咋办呀?教教我吧。
我用中指抵着小胖的下吧,说到:傻瓜,我们可以做持久化呀。Redis 的持久化分两种,一种是 AOF,另一种是 RDB。来,坐哥哥腿上,我给你好好说道说道。
老规矩,先上张脑图:
0.1 什么是持久化?
持久化(Persistence),即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中、XML 数据文件中等等。持久化是将程序数据在持久状态和瞬时状态间转换的机制。
01 怎么理解 Redis 的单线程?
必须声明一点:Redis 的单线程,是指 Redis 的网络 IO 和键值对读写是由一个线程(主线程)完成的,这也是 Redis 对外提供键值存储服务的主要流程。但 Redis 的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。
1.0 Redis 快的原因?
基于内存
- 数据都存储在内存里,减少了一些不必要的 I/O 操作,操作速率很快。
高效的数据结构
- 底层多种数据结构支持不同的数据类型,支持 Redis 存储不同的数据;
- 不同数据结构的设计,使得数据存储时间复杂度降到最低。
合理的线程模型
- I/O 多路复用模型同时监听多个客户端连接;
- 单线程在执行过程中不需要进行上下文切换,减少了耗时。
02 AOF 持久化
AOF(Append Only File) 持久化是通过保存 Redis 服务器所执行的写命令来记录数据库状态,也就是每当 Redis 执行一个改变数据集的命令时(比如 SET), 这个命令就会被追加到 AOF 文件的末尾。
修改 redis.conf 配置文件,默认是 appendonly no(关闭状态),将 no 改为 yes 即可开启 AOF 持久化:
appendonly yes
在客户端输入如下命令也可,但是 Redis 服务器重启后会失效。
192.168.17.101:6379> config set appendonly yes OK
AOF 持久化功能的实现可以分为命令追加(append)、文件写回磁盘两个步骤。
2.0 命令追加
AOF 持久化功能开启时,Redis 在执行完一个写命令之后,会将被执行的写命令追加到服务器状态的 aof_buf 缓冲区的末尾,此时缓冲区的记录还没有写入到 appendonly.aof 文件中。
2.0.1 AOF 的格式
AOF 保存的是 Redis 的写命令,比如:执行命令 set testkey testvalue,它存储的内容如下图所示:
其中,“*3” 表示当前命令有三个部分,每部分都是由 $+ 数字开头,后面紧跟着具体的命令、键或值。这里,数字表示这部分中的命令、键或值一共有多少字节。例如, $3 set 表示这部分有 3 个字节,也就是 set 命令。
2.0.2 写后日志有啥优缺点?
AOF 记录日志的方式被称为写后日志,也就是先执行命令再记录,而 MySQL 中的 redo log、binlog 等都是写前日志。它的写入流程是下图这样的:
写后有什么优点?
- 记录 AOF 时不会对命令进行语法检查 ,写后就只记录了执行成功的命令。(避免保存的错误的命令,恢复的时候就完犊子了)
- 执行完之后再记录,不会阻塞当前的写操作
写后有什么缺陷?
- 如果执行完一个命令还没来得及写日志就宕机了会造成响应数据丢失。
- AOF 的写入由主线程处理,如果写入时出现较长耗时,那就会影响主线程处理后续的请求。
你发现没有?写后的两个缺陷都是 AOF 的写入磁盘时相发生的,我们来看看它是怎么写入的呢?
2.1 AOF 写入磁盘
AOF 提供了三个选择,也就是 AOF 配置项 appendfsync 的三个可选值。
- Always,同步写回:每个写命令执行完,立马同步地将日志写回磁盘;
- Everysec(默认),每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;
- No,操作系统控制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。
2.1.0 三种策略的优缺点
针对避免主线程阻塞和减少数据丢失问题,这三种写回策略都无法做到两全其美。主要原因是:
- Always(同步写回) 基本不丢数据,但是它在每一个写命令后都有一个慢速的落盘操作,影响主线程性能;
- No(操作系统控制的写回)在写完缓冲区后,继续执行后续的命令,但是落盘的时机已经不在 Redis 手中了,只要 AOF 记录没有写回磁盘,一旦宕机对应的数据就丢失了;
- Everysec(每秒写回)采用一秒写回一次的频率,避免了 Always 的性能开销,虽然减少了对系统性能的影响,但是如果发生宕机,上一秒内未落盘的命令操作仍然会丢失。
总结一下就是:想高性能,选择 No 策略;想高可靠性,选择 Always 策略;允许数据有一点丢失,又希望性能别受太大影响,选择 Everysec 策略。
2.2 AOF 恢复数据
不说了,看图:
2.3 AOF 重写
我不知道你发现没有?AOF 文件是不断地将写命令追加到文件的末尾来记录数据库状态的。写命令不断增加,AOF 体积也越来越大。
有些命令是执行多次更新同一条数据,但其实它是可以合并成同一条命令的。比如:LPUSH 对列表数据做了 6 次更改,但 AOF 只需要记录最后一次更改。因为日志恢复时,只需要执行最后一次更改的命令即可。
为了处理这种情况,Redis 提供了 AOF 的重写机制。它的多变一功能,把 6 条写命令合并成一条。如下所示:
如果你的某些键有成百上千次的修改,重写机制节约的空间就很可观了。
2.3.1 触发重写
有两种触发的方法,一个是调用命令 BGREWRITEAOF;一个是修改配置文件参数。
# 方式一 192.168.17.101:6379> BGREWRITEAOF Background append only file rewriting started # 方式二 auto-aof-rewrite-percentage 100 #当前AOF文件大小和上一次重写时AOF文件大小的比值 auto-aof-rewrite-min-size 64mb #文件的最小体积
2.3.2 重写步骤
- 创建子进程进行 AOF 重写
- 将客户端的写命令追加到 AOF 重写缓冲区
- 子进程完成 AOF 重写工作后,会向父进程发送一个信号
- 父进程接收到信号后,将 AOF 重写缓冲区的所有内容写入到新 AOF 文件中
- 对新的 AOF 文件进行改名,覆盖现有的 AOF 文件
2.4 相关配置
# 是否开启AOF功能 appendonly no # AOF文件件名称 appendfilename "appendonly.aof" # 写入AOF文件的三种方式 appendfsync always appendfsync everysec appendfsync no # 重写AOF时,是否继续写AOF文件 no-appendfsync-on-rewrite no # 自动重写AOF文件的条件 auto-aof-rewrite-percentage 100 #百分比 auto-aof-rewrite-min-size 64mb #大小 # 是否忽略最后一条可能存在问题的指令 aof-load-truncated yes
2.5 优缺点
优点
- AOF 文件可读性高,分析容易
- AOF 文件过大时,自动进行重写
- 追加形式,写入时不需要再次读取文件,直接加到末尾
缺点
- 相同数据量下,AOF 一般比 RDB 大
- AOF 恢复时需要重放命令,恢复速度慢
- 根据 fsync 策略,AOF 的速度可能慢于 RDB