0%

redis持久化

redis持久化

redis持久化有两种方式RDB和AOF

注意:我使用的版本是6.0.10,不同版本可能略有差别

RDB

RDB是指Redis DataBase,在指定的时间间隔内,将内存中数据的快照写入到磁盘中,恢复时将快照读取到内存。

rdb文件是一个经过压缩的二进制文件

redis会单独创建(fork)一个子线程来进行持久化,会先将数据写入到临时文件,持久化结束之后,将临时文件替换上次持久化好的文件(保存在dump.rdb文件中),RDB方式比AOF更加高效,但是可能会丢失最后一次持久化的数据

在这里fork的线程是根据当前线程fork的,包含了当前线程的所有数据、变量等

关于RDB的配置是在redis.conf中的快照(SNAPSHOTTING)模块中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#
# Save the DB on disk:
#
# save <seconds> <changes>
#
# 保存触发条件
save 900 1
save 300 10
save 60 10000

#后台保存出错,停止写入
stop-writes-on-bgsave-error yes

#使用LZF压缩算法压缩
rdbcompression yes

#存储快照后,redis使用CRC64算法进行数据检验,会增加性能损耗
rdbchecksum yes

#持久化文件的名称
# The filename where to dump the DB
dbfilename dump.rdb


rdb-del-sync-files no

# 持久化文件的目录
dir /usr/local/var/db/redis/

rdb文件会存储在执行redis-cli的当前目录下的dump.rdb文件中,默认触发条件是

save 900 1 #15分钟内修改了1次
save 300 10 #5分钟内修改了10次
save 60 10000 #1分钟内修改了10000次

当达到条件时会触发bgsave命令

也可以使用save或者bgsave命令来直接立即备份。

save命令是由主线程来做的,其他操作都会阻塞,直到rdb文件创建完毕

bgsave是后台线程做的,其他操作可以正常执行

可以使用命令来检测rdb文件是否存在问题

1
redis-check-rdb

重启之后会直接加载本目录下的dump.rdb文件

bgsave命令

bgsave命令在执行时,服务器处理save、bgsave、bgrewriteaof这三个命令会与平时不同

  • save命令不可执行,避免父子进程同时执行rdbsave,防止产生竞争条件
  • bgsave命令不可执行,防止两个bgsave产生竞争条件
  • 如果此时bgsave正在执行,bgrewriteaof会被延迟到bgsave执行完之后执行;如果bgrewriteaof正在执行,则bgsave命令会被拒绝, 为了防止两个子进程同时执行造成大量的磁盘写入操作
原理

使用fork()+copyonwrite

fork()

fork()是linux和unix的底层api,会fork出来一个子进程,共享那一刻的内存数据,且修改互相不可见

copyonwrite

主进程fork子进程之后,内核会把主进程中所有的内存页权限设置为read-only,当主进程写数据时,read-only会发生中断,将触发中断的内存页复制一份主进程来操作修改,其余的还是在共享内存内

优点

  • 适合大规模的数据恢复
  • 对数据完整性和一致性要求不高

缺点

  • 由于是每隔一段时间去备份的,如果redis意外挂掉的话,可能会丢失最后一次快照的所有修改
  • fork的时候,内存中的数据被克隆一份,内存会是原来的两倍
  • 每次快照持久化都是将内存数据完整写入到磁盘一次,并不是增量的只同步脏数据。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘io操作,可能会严重影响性能

停止RDB持久化的方式

1
redis-cli config set save ""

在进行rdb持久化是将内存数据完整的写入磁盘,并不是使用增量的方式,如果数据量大的话可能会有大量的磁盘IO操作

AOF

AOF是指Append Only File,以日志的形式记录写操作,只许追加文件内容,不许修改,redis启动时会读取该文件来根据日志文件内容重新将写指令执行一次以完成数据恢复(日志文件名称为appendonly.aof)

默认该方式没有开启

关于AOF的配置是在redis.conf中的APPEND ONLY MODE模块中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
############################## APPEND ONLY MODE ###############################


# 默认使用的是RDB,所以aof默认是关掉的
# 如果AOF和RDB并存的话,优先加载AOF
appendonly no

# The name of the append only file (default: "appendonly.aof")
# 持久化文件名称
appendfilename "appendonly.aof"


# 同步写入日志文件 有三个策略 always、everysec、no
# appendfsync always
appendfsync everysec
# appendfsync no

#重写时是否同步追加日志文件
no-appendfsync-on-rewrite no

#重写触发机制 比上次rewrite的aof文件大小扩增一倍且重写的最小大小为64m
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb


aof-load-truncated yes


aof-use-rdb-preamble yes

如果aof文件中存在一些错误的指令(由于网络原因等问题导致命令没有写完),可以使用命令进行修复aof文件

1
redis-check-aof --fix

aof比rdb具有更好的持久化性,在使用aof持久化方式时,redis会将每一个收到的写命令通过write函数追加到文件中,在redis重启时会将aof文件中的命令重新执行一遍

AOF实现

AOF持久化方式打开之后,服务器执行写命令会写在缓冲区的末尾(server.c中的redisServer中有一个aof_buf字段来作为缓冲区),serverCron函数会定时将aof_buf缓冲区中的数据写入到文件

重写机制

由于AOF采用的是日志追加,可能会导致日志文件越来越大,为了避免此情况,增加了重写机制,当AOF文件超过所设定的阈值之后,redis会启动AOF文件的压缩,只保留可以恢复数据的最小指令集,可以使用命令bgrewriteaof来执行

redis在进行重写时会占用大量的cpu和内存资源

no-appendfsync-on-rewrite no 这个配置是指是否在每次fsync的时候都进行重写,但是这样会造成大量的磁盘IO,还有可能会造成对写操作的阻塞,所以一般该值不推荐修改为yes

重写步骤:

  • redis调用fork,创建一个重写子进程,子进程将整个内存中的数据库内容用命令的方式重写了一个新的aof文件写入到一个临时文件中
  • 父进程在处理client的时候,除了将写命令写入原来的aof文件中,还需要同时将写命令缓存起来(aof_rewrite_buf_blocks),写入原来的aof文件中是为了保证如果子进程重写失败不会出问题
  • 当子进程把快照内容写完之后通知父进程,父进程将缓存的写命令追加写入临时文件
  • 追加结束后父进程将临时文件替换老的aof文件,之后收到的命令也将写入新的aof文件中

优点

写入日志的策略分为always、everysec、no

缺点

aof文件远大于rdb文件,恢复速度慢

由于aof方式比rdb方式的更新频率高,所以如果开启了aof,那么在启动时会优先使用aof文件来还原数据库状态

混合模式

在redis4.0之后出现了rdb和aof混合模式,而且默认开模式是开启的

1
aof-use-rdb-preamble yes

开启后,AOF在重写时会直接读取RDB中的内容
运行过程:
通过bgrwriteaof完成,不同的是当开启混合持久化后,子进程会把内存中的数据以RDB的方式写入aof中,把重写缓冲区中的增量命令以AOF方式写入到文件,将含有RDB格式和AOF格式的AOF数据覆盖旧的AOF文件
新的AOF文件中,一部分数据来自RDB文件,一部分来自Redis运行过程时的增量数据

定时任务

在server.c中的serverCron函数是redis的一个定时任务,该函数处理了很多定时处理的命令。可以在配置文件中配置hz的值来配置每秒执行多少次,默认是10,即1秒执行10次,表示100ms一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* This is our timer interrupt, called server.hz times per second.
* Here is where we do a number of things that need to be done asynchronously.
* For instance:
*
* - Active expired keys collection (it is also performed in a lazy way on
* lookup).
* - Software watchdog.
* - Update some statistic.
* - Incremental rehashing of the DBs hash tables.
* - Triggering BGSAVE / AOF rewrite, and handling of terminated children.
* - Clients timeout of different kinds.
* - Replication reconnection.
* - Many more...
*
* Everything directly called here will be called server.hz times per second,
* so in order to throttle execution of things we want to do less frequently
* a macro is used: run_with_period(milliseconds) { .... }
*/