redis两种持久化方案的区别

1,967 阅读8分钟

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

引言

redis因为是内存数据库,数据的状态在内存里,所以只要服务重启,那么内存的数据全部丢失,为了解决这个问题,redis提供了两种持久化方案rdb和aof。

RDB

RDB的持久化既可以手动执行,也可以根据配置定期执行。不管是手动还是自动都是在某一时间把数据存到一份rdb文件中去,rdb文件本身是一个二进制压缩的文件,redis启动的时候通过这个文件就可以恢复数据。

截屏2021-07-31 下午2.58.55.png redis通过这种方式,即使redis挂了,因为dump.rdb也是保存在硬盘的,所以数据并没有丢失。前面说了目前有两种方式来生成rdb文件,一个是SAVE,另一个是BASAVE。

SAVE

SAVE是手动保存方式,它会使redis进程阻塞,直至RDB文件创建完毕,创建期间所有的命令都不能处理。

127.0.0.1:6379> save
OK
27004:M 31 Jul 15:06:11.761 * DB saved on disk

BGSAVE

与SAVE命令不同的是BGSAVE,BGSAVE可以不阻塞redis进程,通过BGSAVE redis会fork一个子进程去执行rdb的保存工作,主进程继续执行命令。

127.0.0.1:6379> BGSAVE
Background saving started
27004:M 31 Jul 15:07:08.665 * Background saving terminated with success

BGSAVE执行期间与其他一些io命令会存在一些互斥:

  1. BGSAVE期间,所有的SAVE命令会被拒绝执行,避免父子进程同时执行,造成一些竞争问题。
  2. BGSAVE期间,如果有新的BGSAVE那么也就被拒绝,也是竞争问题。
  3. BGSAVE期间,如果来了个BGREWRITEAOF,那么BGREWRITEAOF会被延迟到BGSAVE之后再执行。
  4. 如果BGREWRITEAOF在执行,那么BGSAVE命令会被拒绝。 BGSAVE与BGREWRITEAOF都是由两个子进程处理,目标也是不同的文件,本身没什么冲突,主要是两个都可能要大量的IO,这对服务本身来说不是很友好。

BGSAVE的实现

用户可以通过配置,让每隔一段时间来执行bgsave

save 900 1 
save 300 10
save 60 10000
  1. 900s内至少修改了1次
  2. 300s内至少修改了10次
  3. 60s内至少修改了10000次 以上条件只要满足了一个就可以执行bgsave。
    这里涉及到两个参数来记录次数和时间。分别是dirty计数器和lastsave。
  • dirty计数器记录距离上一次成功执行SAVE命令或者BGSAVE命令之后,服务器对数据库状态(服务器中的所有数据库)进行了多少次修改(包括写入、删除、更新等操作)。
  • lastsave属性是一个UNIX时间戳,记录了服务器上一次成功执行SAVE命令或者BGSAVE命令的时间。 以上两个指标是基于redis的serverCron来完成的,serverCron是一个定期执行的程序,默认每隔100ms执行一次。每次serverCron执行的时候会遍历所有的条件,然后检查计数是否ok,时间是否ok,都ok的话就执行一次bgsave,并且记录最新的lastsave时间,重制dirty为0。

RDB文件结构

redis文件保存的是二进制格式,大致存储的数据如下:

截屏2021-07-31 下午5.00.04.png

  1. REDIS为固定开头
  2. db_version为rdb的版本(注意不是redis版本)
  3. database即为我们存储的数据
  4. 结束符,载入的时候读到结束符那么就代表读完了
  5. check_sum校验和,检查rdb文件是否被破坏 database是我们重点关心的:

截屏2021-07-31 下午5.09.28.png 由于key分为过期的和不过期的,所以结构也不太一样。带过期时间的要多个过期时间。

导入

与两个保存命令不同的是redis没有专门的用户导入的命令,redis在启动的时候会检测是否有RDB文件,有的话,就自动导入。

27004:M 31 Jul 14:46:51.793 # Server started, Redis version 3.2.12
27004:M 31 Jul 14:46:51.793 * DB loaded from disk: 0.000 seconds
27004:M 31 Jul 14:46:51.793 * The server is now ready to accept connections on port 6379

DB loaded from disk就是载入rdb的描述。
服务在载入RDB期间是阻塞的。
当然如果也开启了AOF,那么就会优先使用AOF来恢复,只有在服务器未开启AOF的时候,才会选择RDB来恢复数据。导入的时候也会自动过滤过期的key。

image.png

AOF

原理

不同于RDB持久化,redis还提供一个AOF持久化,AOF就是一个命令追加的模式。
假设执行了:

RPUSH list 1 2 3 4
RPOP list
LPOP list
LPUSH list 1

最终以redis协议方式存储: *2$6SELECT$10*6$5RPUSH$4list$11$12$13$14*2$4RPOP$4list*2$4LPOP$4list*3$5LPUSH$4list$11

策略

aof先是写到aof_buf的缓冲区中,redis提供三种方案将buf的缓冲区的数据刷到磁盘,当然也是serverCron来根据策略处理的。

appendfsync always
appendfsync everysec
appendfsync no
  1. always:将aof_buf缓冲区所有的内容写入并同步到AOF文件
  2. everysec:将aof_buf缓冲区所有的内容写入AOF文件,如果上次同步的时间和这次的超过1s,那么再次执行同步,并且这个同步是由一个线程完成的。
  3. no:将aof_buf写入AOF文件,但是不执行同步,何时同步由操作系统决定。 这里解释下写入AOF文件 和 同步AOF

在现代操作系统中,为了提高文件写入的效率,当我们调用write写入一个数据的时候,操作系统并不会立刻写入磁盘,而是放在一个缓冲区里,当缓冲区满了或者到了一定时间后,才会真正的刷入到磁盘中。这样存在一定风险,就是内存的数据没等到刷入磁盘的时候,机器宕机了,那么数据就丢失了,于是操作系统也提供了同步函数fsync,让用户可以自己决定什么时候同步。

  1. always就是每次serverCron执行的时候,立刻fsync,那么效率肯定是最低的。优点就是如果宕机了,最多丢失一个循环(100ms)的数据。
  2. no的效率肯定是最高的,因为每次都不主动fsync,都是等待操作系统自己同步,那么如果服务宕机,丢失的数据肯定是最多的
  3. everysec是个折中的做法,既保证了较高的效率,也保证了较低的数据丢失(最多丢失1s的数据)

AOF重写

随着命令越来越多,aof的体积会越来越大,举个例子

incr num
incr num
incr num
incr num

执行4条incr num,num的最终的值是4,然后可以直接用一条set num 4 代替,这样存储就节省了很多。

重写也不是分析现有aof 重写就是从数据库读取现有的key,然后尽量用一条命令代替。

  1. 创建新的aof
  2. 遍历数据库
  3. 遍历所有的key
  4. 忽略过期的
  5. 写入aof 并不是所有的都可以用一条命令代替:例如sadd 每次最多只能add 64个,如果超过64个就要分批了。
sadd key 1 2 ... 64
sadd key 64 66 ...
...

子进程重写 aof的重写涉及大量的io,在当前进程里去做肯定不合适,理所当然也是fork一个子进程来做,不使子线程的原因是避免一些锁的问题。
使用子进程需要考虑的问题就是在子进程写入的时候,主进程还在源源不断的接收新的请求,那么针对这种情况redis设置了一个aof重写缓冲区,缓冲区在子进程创建的时候开始使用,那么在新的请求来的时候,除了写入aof缓冲区外,还要写入aof重写缓冲区,此过程不阻塞。
那么在子进程重写完了之后,会发信号给主进程,主进程收到信号后,会把重写缓冲区的数据再次同步给新的aof文件,然后rename新的aof,原子的覆盖老的aof,完成重写,这个过程是阻塞的。 重写时机

  1. 手动执行bgrewriteaof
  2. 通过配置,自动触发:
auto-aof-rewrite-percentage 100 //100代表当前AOF文件是上次重写的两倍时候才重写
auto-aof-rewrite-min-size 64mb //文件最小重写大小 默认64mb

导入

redis启动的时候,会创建一个伪客户端,然后执行aof文件里面的命令。

总结

  1. redis的持久化方案目前就两种rdb和aof
  2. rdb的存储的二进制格式数据
  3. aof存储的是执行的命令
  4. 如果出现意外,aof会让数据损失更小
  5. redis启动的时候,如果开启aof,优先从aof恢复,否则rdb恢复
  6. 一般生产环境两种都会开启

参考《redis的设计与实现》