redis两种持久化方案的区别
这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战
引言
redis因为是内存数据库,数据的状态在内存里,所以只要服务重启,那么内存的数据全部丢失,为了解决这个问题,redis提供了两种持久化方案rdb和aof。
RDB
RDB的持久化既可以手动执行,也可以根据配置定期执行。不管是手动还是自动都是在某一时间把数据存到一份rdb文件中去,rdb文件本身是一个二进制压缩的文件,redis启动的时候通过这个文件就可以恢复数据。
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命令会存在一些互斥:
- BGSAVE期间,所有的SAVE命令会被拒绝执行,避免父子进程同时执行,造成一些竞争问题。
- BGSAVE期间,如果有新的BGSAVE那么也就被拒绝,也是竞争问题。
- BGSAVE期间,如果来了个BGREWRITEAOF,那么BGREWRITEAOF会被延迟到BGSAVE之后再执行。
- 如果BGREWRITEAOF在执行,那么BGSAVE命令会被拒绝。 BGSAVE与BGREWRITEAOF都是由两个子进程处理,目标也是不同的文件,本身没什么冲突,主要是两个都可能要大量的IO,这对服务本身来说不是很友好。
BGSAVE的实现
用户可以通过配置,让每隔一段时间来执行bgsave
save 900 1
save 300 10
save 60 10000
- 900s内至少修改了1次
- 300s内至少修改了10次
- 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文件保存的是二进制格式,大致存储的数据如下:
- REDIS为固定开头
- db_version为rdb的版本(注意不是redis版本)
- database即为我们存储的数据
- 结束符,载入的时候读到结束符那么就代表读完了
- check_sum校验和,检查rdb文件是否被破坏 database是我们重点关心的:
由于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。
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
- always:将aof_buf缓冲区所有的内容写入并同步到AOF文件
- everysec:将aof_buf缓冲区所有的内容写入AOF文件,如果上次同步的时间和这次的超过1s,那么再次执行同步,并且这个同步是由一个线程完成的。
- no:将aof_buf写入AOF文件,但是不执行同步,何时同步由操作系统决定。
这里解释下
写入AOF
文件 和同步AOF
。
在现代操作系统中,为了提高文件写入的效率,当我们调用write写入一个数据的时候,操作系统并不会立刻写入磁盘,而是放在一个缓冲区里,当缓冲区满了或者到了一定时间后,才会真正的刷入到磁盘中。这样存在一定风险,就是内存的数据没等到刷入磁盘的时候,机器宕机了,那么数据就丢失了,于是操作系统也提供了同步函数fsync,让用户可以自己决定什么时候同步。
- always就是每次serverCron执行的时候,立刻fsync,那么效率肯定是最低的。优点就是如果宕机了,最多丢失一个循环(100ms)的数据。
- no的效率肯定是最高的,因为每次都不主动fsync,都是等待操作系统自己同步,那么如果服务宕机,丢失的数据肯定是最多的
- everysec是个折中的做法,既保证了较高的效率,也保证了较低的数据丢失(最多丢失1s的数据)
AOF重写
随着命令越来越多,aof的体积会越来越大,举个例子
incr num
incr num
incr num
incr num
执行4条incr num
,num的最终的值是4,然后可以直接用一条set num 4
代替,这样存储就节省了很多。
重写也不是分析现有aof 重写就是从数据库读取现有的key,然后尽量用一条命令代替。
- 创建新的aof
- 遍历数据库
- 遍历所有的key
- 忽略过期的
- 写入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,完成重写,这个过程是阻塞的。 重写时机
- 手动执行
bgrewriteaof
- 通过配置,自动触发:
auto-aof-rewrite-percentage 100 //100代表当前AOF文件是上次重写的两倍时候才重写
auto-aof-rewrite-min-size 64mb //文件最小重写大小 默认64mb
导入
redis启动的时候,会创建一个伪客户端,然后执行aof文件里面的命令。
总结
- redis的持久化方案目前就两种rdb和aof
- rdb的存储的二进制格式数据
- aof存储的是执行的命令
- 如果出现意外,aof会让数据损失更小
- redis启动的时候,如果开启aof,优先从aof恢复,否则rdb恢复
- 一般生产环境两种都会开启
参考《redis的设计与实现》
转载自:https://juejin.cn/post/6994808352437977095