Redis持久化 - RDB & AOF

156 阅读7分钟

面试官:请说下 Redis 如何保证宕机后数据不丢失的 ?

我:主从复制

image.png

面试官: ....

为何持久化

Redis是内存数据库,停电或宕机数据内存数据就没了,所以需要将内存的数据持久化到磁盘上,这样重启后通过磁盘文件恢复到内存中!

验证持久化

Ok, 首先带着面试官的问题,模拟一下Redis宕机, 然后再查询一下数据是否存在

  1. 首先清空所有 db 数据, 并存入一些数据
127.0.0.1:6379> flushdb
OK

127.0.0.1:6379> set k1 1
OK

127.0.0.1:6379> set k2 2
OK

127.0.0.1:6379> set k3 3
OK
  1. 模拟Redis宕机
kill -9 3285 //杀死进程
  1. 重启Redis
redis-server /etc/redis.conf
  1. 查询一下数据是否还在
127.0.0.1:6379> keys *
1) "k2"
2) "k1"

结论:  可以看到数据确实还在,但是貌似缺了一个?

RDB

定义

RDB 即 Redis Database, 就是把某个时间点下的内存数据备份到一个 RDB 文件中[经过压缩的二进制文件]

image.png

两种方式

  1. SAVE: 会阻塞 Redis 服务器,期间服务器不能处理任何命令请求。
  2. BGSAVE:fork 一个子进程负责创建 RDB文件,服务器进程继续处理命令。

通过以下伪代码可以明显看出这两个命令之间的区别:

def SAVE():
    #创建RDB文件
    rdbSave()

def BGSAVE():
    #创建子进程
    pid = fork()
    if pid == 0:
        #子进程负责创建RDB文件
        rdbSave()
        #完成之后向父进程发出信号
        signal_parent()
    elif pid > 0:
        #父进程继续处理命令请求,并通过轮询等待子进程信号
        handle_request_and_wait_signal()
    else:
        #处理出错情况
        handle_fork_error()

BGSAVE的自动间隔性保存

因为BGSAVE命令可以在不阻塞服务器的情况下执行,所以Redis允许用户通过配置文件或者启动传参的方式去设置的save选项,让服务器每隔一段时间自动执行一次BGSAVE命令。用户可以通过save选项设置多个保存条件,但只要其中一个条件被满足,服务器就会执行BGSAVE命令。举个🌰,如果我们向服务器提供以下配置:

save   900    1
save   300    10
save   60     10000

那么只要满足以下三个条件中的任意一个,BGSAVE命令就会被执行:

  • 服务器在900秒内对数据库进行了至少一次的修改
  • 服务器在300秒内对数据库进行了至少十次的修改
  • 服务器在60秒内对数据库进行了至少一万次的修改

看到这里应该就知道开头为什么缺了一个数据了, k1 和 k2是在一个周期内存到 RDB的, k3恰好是下一个周期,因为宕机了所以这个周期的数据没有刷盘,因此数据恢复后只有 k1 和 k2, 这也是 RDB 方式会丢数据的原因。

实现原理

  1. 服务器状态还维持着一个dirty计数器,以及一个lastsave属性:
  • dirty计数器记录距离上一次成功执行SAVE命令或者BGSAVE命令之后,服务器对数据库状态(服务器中的所有数据库)进行了多少次修改(包括写入、删除、更新等操作)
  • lastsave属性是一个Unix时间戳,记录了服务器上一次成功执行SAVE命令或BGSAVE命令的时间
  1. Redis的服务器周期性操作函数serverCron默认每隔100毫秒就会执行一次,  该函数用于对正在运行的服务进行维护,它的其中一项工作就是检查save选项所设置的保存条件是否满足,如果满足,就执行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();
     # ...

RDB文件载入

RDB文件的载入工作是在服务器启动时自动执行的,所以Redis并没有专门用于载入RDB文件的命令,只要Redis服务器在启动时检测到 RDB 文件的存在,它就会自动载入RDB文件。并且以一直处于阻塞状态,直到载入工作完成为止。

以下是Redis服务器启动时打印的日志记录其中第二条DB loaded from disk:……就是服务器成功载入RDB文件之后打印的:

17859:S 18 Aug 11:26:16.363 
17859:S 18 Aug 11:26:56.802 * DB loaded from disk: 36.336 seconds
17859:S 18 Aug 11:26:56.802 * The server is now ready to accept connections on port 6379

RDB文件结构

有兴趣的同学可以看一下,这是<<Redis设计与实现>>的第二部分第 11 章 RDB 文件结构 ‒ Redis 设计与实现

AOF

定义

AOF即Append Only File,每当 Redis 接收到修改命令时,就会把命令追加到 AOF 文件里。

image.png

其中④刷盘是由配置文件的三种策略来控制:

  • always: 同步刷盘,将所有缓冲区内容刷到磁盘里。
  • everysec: 有个专门的线程,每秒写一次。
  • no: 由操作系统决定,缓冲区满了就去刷盘。
命令alwayseverysecno
优点不丢失数据每秒一次 fsync,丢1 秒数据不用管
缺点IO 开销较大,一般的 sata 盘只有几百 TPS丢1秒数据不可控

ReWrite

为啥要有ReWrite?

举个🌰, 某业务会不断的 set 和 delete某一个相同key, 这种操作持续了 10 年, 那么aof文件将记录无数个delete key ,set key。其实数据库里还是那么一个 key,但是来回操作日志追加到 aof 文件里就是好几个 T,恢复起来就累死了,所以干了个事情, 将上面的一堆命令压缩成几个命令。

早期ReWrite实现

Redis4.0之前和Redis4.0的rewrite(重写)方式不一样,Redis4.0之前就是将aof文件中重复的命令给去掉,保留最新的命令,进而减少aof文件大小,比如

set k1 123
set k1 345
del k1
set k1 789

经过ReWrite后(Redis4.0之前),只会变成如下

set k1 789

总结:逐条对比,效率低下。

4.0版本以及之后的ReWrite

4.0开始的ReWrite支持混合模式(也是就是rdb和aof一起用),直接将rdb内容覆盖到aof文件中(rdb是二进制,所以很小),然后再有写入的话还是继续append追加到文件原始命令,等下次文件过大的时候再次ReWrite[重复上面步骤]。但是这种模式也是配置的,默认是开,也可以关闭。

image.png

说白了就是一个文件写了两种格式。

ReWrite触发条件

1、手动触发

  • 客户端执行bgrewriteaof命令

2、自动触发

通过以下两个配置协作触发

  • auto-aof-rewrite-min-size

AOF文件最小重写大小,只有当AOF文件大小大于该值时候才可能重写,4.0默认配置64mb。

  • auto-aof-rewrite-percentage

当前AOF文件大小和最后一次重写后的大小之间的比率等于或者等于指定的增长百分比,如100代表当前AOF文件是上次重写的两倍时候才重写。

ReWrite原理

此人讲得甚好,有兴趣去了解 🚄【Redis 干货领域】从底层彻底吃透 AOF 重写 (源码篇)

AOF文件载入 

首先创建一个不带网络连接的伪客户端(fake client),然后客户端读入并执行AOF 里面保存的所有写命令即可。

另外值得注意的是,当AOF和RDB同时开启的时候,Redis会优先载入AOF文件

AOF文件结构

以下是执行的命令:

redis> SET msg "hello"
OK
redis> SADD fruits "apple" "banana" "cherry"
(integer)3
redis> RPUSH numbers 128 256 512 
(integer)3

这个是AOF文件存储的东西

*2\r\n$6\r\nSELECT\r\n$1\r\n0\r\n
*3\r\n$3\r\nSET\r\n$3\r\nmsg\r\n$5\r\nhello\r\n
*5\r\n$4\r\nSADD\r\n$6\r\nfruits\r\n$5\r\napple\r\n$6\r\nbanana\r\n$6\r\ncherry\r\n
*5\r\n$5\r\nRPUSH\r\n$7\r\nnumbers\r\n$3\r\n128\r\n$3\r\n256\r\n$3\r\n512\r\n

总结

命令RDBAOF
启动优先级
体积
恢复速度
数据安全性丢数据根据策略决定