Logo
活死人の行知路

Redis持久化


📅 | 📝 930 字
#redis #DBA

1持久化

1.1什么是持久化?

对 Redis 来说,Redis 是将所有数据保存在内存中,如果服务突然崩溃,没有将数据保存在磁盘中,数据将会丢失,Redis的持久化是指对数据的更新会异步的保存到磁盘上。磁盘中的数据在关机重启时数据仍然存在。当需要恢复数据时就可以将完整数据加载到内存中。

1.2主流数据库持久化方式

1、快照:可以理解为在某时间点对数据的完整备份。比如MySQL的Dump,Redis的RDB。

2、日志:当数据库发生更新时将对应操作记录到日志,当需要数据恢复时只需将日志进行回放就能获取完整数据的变化。比如MySQL的Binlog,Redis的AOF、HBase HLog。

2RDB

2.1什么是RDB?

Redis 数据是保存在内存中,通过一条命令将内存中的Redis的数据生成一个快照,保存到一个硬盘中的二进制文件,这个二进制文件就是RDB文件。 当需要对Redis数据进行恢复时,比如机器重启,就可以将RDB文件加载到内存。

2.2RDB的三种触发方式

  1. save:同步命令,在执行该命令之前,其他所有命令都需要排队,等待该命令执行之后再执行其他的命令。当数据量过大时该命令会造成Redis阻塞。

  • 策略:如果存在老的RDB文件,新文件会替换旧文件。
  • 复杂度:O(n)
  1. bgsave:异步命令,执行完后会立即返回OK,会在后台以单独的进程执行,背后使用了Linux的fork函数,生成了主进程的子进程,让子进程去生成RDB文件。当RDB生成完成后也会告诉主进程RDB生成成功。

  • 策略:如果存在老的RDB文件,新文件会替换旧文件。
  • 复杂度:O(n)

save VS bgsave

命令savebgsave
IO类型同步异步
是否阻塞是是(阻塞发生在for)
复杂度O(n)O(n)
优点不会消耗额外内存不阻塞客户端命令
缺点阻塞客户端命令需要fork,消耗内存
  1. 自动生成:在某些条件达到时,自动生成RDB文件。

Redis 提供了自动生成RDB的配置,只要满足下面任一个条件就会触发自动RDB生成,其背后实际也是执行的 bgsave 命令,比如:

配置secondschangeschanges
save9001900秒钟变更了1条数据则自动执行RDB生成
save30010300秒钟变更了10条数据则自动执行RDB生成
save601000060秒钟变更了10000条数据则自动执行RDB生成

缺点:频率过于频繁,我们无法控制。也可能造成对应硬盘的压力。

2.3配置

  • 一般将自动生成配置关闭。
  • rdb 文件名:dbfilename dump-${port}.rdb
  • 设置较大的硬盘路径,如dir /bigdiskpath
  • bgsave发生错误则停止写入,stop-writes-on-bgsave-error yes
  • 采用压缩,rdbcompression yes
  • 采用校验 rdbchecksum yes

2.4注意

  • 全量复制。有时候会发现我们没有执行save、bgsave,也没有配置自动生成,但也能看到rdb文件,这是因为主从复制时主节点会自动生成rdb文件
  • debug reload。 可以看做是不将内存数据清空的一次重启。这也会触发RDB文件的生成。
  • shutdown。这也会触发RDB文件的生成。

2.5实验

修改配置如下:

# cat redis-6379.conf | grep -v "#" | grep -v "^$"

# 1. 设置后台守护进程启动
daemonize yes
# 设置启动文件
pidfile /var/run/redis-6379.pid
# 2. 配置端口
port 6379
tcp-backlog 511
timeout 0
tcp-keepalive 0
loglevel notice
# 3. 配置日志文件
logfile "6379.log"
databases 16
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
# 4. 配置dbfilename
dbfilename dump-6379.rdb
# 5. 设置数据目录
dir /opt/redis/data
slave-serve-stale-data yes
slave-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
slave-priority 100
appendonly no
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
aof-rewrite-incremental-fsync yes


...
# 6. 另外注释3行自动生成RDB配置:
#save 900 1
#save 300 10
#save 60 10000
...

**启动redis-server: **

# redis-server redis-6379.conf
# ps -ef | grep redis | grep -v grep
root         451       1  0 10:32 ?        00:00:00 redis-server *:6379
# redis-cli
127.0.0.1:6379> dbsize
(integer) 0

使用bash命令中执行下面命令插入20000000条测试数据:

for ((i=0; i<20000000; i+=10000)); do
    redis-cli EVAL "
    for j = $i, $((i+9999)) do
        redis.call('SET', 'key' .. j, 'value' .. j)
    end
    " 0
done

执行命令:

# redis-cli
127.0.0.1:6379> dbsize
(integer) 10000000

# 查看内存
127.0.0.1:6379> info memory
# Memory 已使用1.17G内存
used_memory:1255165992
used_memory_human:1.17G
used_memory_rss:1288101888
used_memory_peak:1316694544
used_memory_peak_human:1.23G
used_memory_lua:791552
mem_fragmentation_ratio:1.03
mem_allocator:jemalloc-3.6.0
127.0.0.1:6379>

准备两个终端,一个终端用户执行save命令,第二个终端先设置一条数据,然后在第一个终端执行RDB生成期间去第二个终端查询来查询这条数据,会发现执行get命令阻塞了,因为它是在save之后去执行的,它必须在save完成之后才能被执行:

data 目录查看 rdb 文件:

# pwd
/opt/redis/data
# ll -lh
total 237M
-rw-r--r-- 1 root root 1.8K 11月 12 15:19 6379.log
-rw-r--r-- 1 root root 237M 11月 12 15:19 dump-6379.rdb

当在第一个终端执行 bgsave 时,在另一个终端指定命令会发现并不会像执行 save 那样阻塞。

查看下执行 bgsave 后日志:

...
451:M 12 Nov 15:22:58.686 * Background saving started by pid 2393083
2393083:C 12 Nov 15:23:02.671 * DB saved on disk
2393083:C 12 Nov 15:23:02.676 * RDB: 0 MB of memory used by copy-on-write
451:M 12 Nov 15:23:02.688 * Background saving terminated with succes

同时查看下是否存在子进程:

# ps -ef | grep redis- | grep -v redis-cli | grep -v grep
root         451       1  1 13:21 ?        00:01:49 redis-server *:6379
root     2393094     451 76 15:26 ?        00:00:00 redis-rdb-bgsave *:6379

查看文件生成,会看到有个临时文件 temp-2393102.rdb:

# ll -lh
total 292M
-rw-r--r-- 1 root root 2.9K 11月 12 15:28 6379.log
-rw-r--r-- 1 root root 237M 11月 12 15:27 dump-6379.rdb
-rw-r--r-- 1 root root  56M 11月 12 15:28 temp-2393102.rdb

接着修改配置文件,配置自动触发RDB生成,只设置60秒内有5个数据更新操作即自动触发:

...
save 60 5

执行重启操作:

redis-cli shutdown
redis-server redis/config/redis-6379.conf

执行一分钟修改5次的操作:

set hello world
set a b
set c d
set e f
set g h
set i j

查看日志:

...
2393125:M 12 Nov 15:34:17.496 * 5 changes in 60 seconds. Saving...
2393125:M 12 Nov 15:34:17.509 * Background saving started by pid 2393137
2393137:C 12 Nov 15:34:21.197 * DB saved on disk
2393137:C 12 Nov 15:34:21.201 * RDB: 0 MB of memory used by copy-on-write
2393125:M 12 Nov 15:34:21.282 * Background saving terminated with success

2.6小结

  1. RDB 是Redis内存到硬盘的快照,用于持久化;
  2. save 通常会阻塞Redis;
  3. bgsave 不会阻塞Redis,但会fork新的进程;
  4. save 自动配置满足任一条件就会被执行;
  5. 需要了解RDB的触发机制。

3AOF

3.1RDB 存在的问题

  • 耗时、耗性能:因为需要将内存中的全部数据dump到硬盘生成RDB文件,这是全量操作,O(n)的过程,另外写也要消耗CPU。如果是bgsave,还需要fork进程,也会消耗内存。另外将数据写入到rdb文件也会消耗磁盘IO性能。
  • 不可控、易丢失数据。比如下表:
时间点save
T1执行多个写命令
T2满足RDB自动创建条件
T3再次执行多个写命令
T4宕机

上面情况在 T3 ~ T4 时间的数据就可能会丢失。

3.2什么是AOF?

原理

客户端执行很多写操作的命令,redis会将写命令记录到AOF日志文件中。当Redis宕机需要恢复时,就可以将AOF中记录的数据进行完整恢复,恢复操作基本是实时的。

3.3AOF三种策略

redis 执行写命令实际并不是直接将写命令记录到文件系统(硬盘)中,而是写在硬盘的缓冲区中,缓冲区会根据配置的策略刷新到磁盘中。这么做的目的是为了提高写的效率,因为直接写入到磁盘会非常慢,而是通过缓冲区并根据配置的策略写入到AOF中。

  • always

always 是指写入每条命令都会 fsync 到硬盘,这样redis写入的数据就不会丢失。

  • everysec

redis 将命令写入到缓冲区中,每秒都会刷新到磁盘AOF文件中,缺点是如果出现故障,可能会丢失数据。

  • no

将缓冲区刷新到硬盘是由操作系统特性决定。由操作系统决定何时刷新到磁盘AOF文件中。


命令alwayseveryseceverysec
优点不丢失数据每秒一次fsync,可能会丢失1秒的数据无需我们关心,又操作系统管理
缺点IO开销大,一般的磁盘只有几百TPS丢1秒数据不可控

3.4AOF重写

AOF策略可以将命令写入到AOF文件中,随着时间的推移、写入命令的增加、并发量的逐步增加,AOF文件也会逐渐增大,此时如果使用AOF进行恢复可能会很慢,而且随着文件的增加对硬盘的管理写入速度也会有影响,所以redis提供了AOF重写的机制来解决文件增大的问题。

比如左侧有3条命令,按照always策略会将所有的命令的都写入到AOF文件中,但实际上真正有效的是最后一条命令,其他命令都是无效的,因为最终的值设置为“redis”,所以最终有价值的命令是最后一条;

比如对于自增命令也是一样,最终有价值的命令是直接设置counter为最后的值。

AOF重写作用

  • 减少磁盘占用量
  • 加速恢复速度

3.5AOF重写的两种方式

  • bgreswriteaof:类似bgsave,它也是利用fork子进程来完成AOF重写的过程。
  • AOF重写配置

配置:

配置名含义
auto-aof-rewrite-min-sizeAOF 文件重写需要的尺寸,即当AOF多大是才进行AOF的重写
auto-aof-rewrite-percentageAOF 文件增长率

统计项:

统计名含义
aof_current_sizeAOF当前尺寸(单位:字节)
aof_base_sizeAOF上次启动和重写的尺寸(单位:字节)

注意:当两个条件 同时满足 时才会进行重写:

  • aof_current_size > auto-aof-rewrite-min-size
  • aof_current_size - aof-base_size / aof_base_size > auto-aof-rewrite-percentage

流程

3.6AOF演示

# 查看配置
127.0.0.1:6379> config get appendonly
1) "appendonly"
2) "no"

# 动态修改配置
127.0.0.1:6379> config set appendonly yes
OK

# 配置重启
127.0.0.1:6379> config rewrite
OK
127.0.0.1:6379> exit

# redis-cli
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> set hello redis
OK
127.0.0.1:6379> set hello python
OK
127.0.0.1:6379> incr counter
(integer) 1
127.0.0.1:6379> incr counter
(integer) 2
127.0.0.1:6379> rpush list a
(integer) 1
127.0.0.1:6379> rpush list b
(integer) 2
127.0.0.1:6379> rpush list c
(integer) 3

查看AOF文件

root@vm-1:/opt/redis/data# ll
total 505668
drwxr-xr-x  3 root   root        4096 Jul  1 15:17 ./
drwxrwxr-x 11 ubuntu ubuntu      4096 Jul  1 11:38 ../
-rw-r--r--  1 root   root        4762 Jul  1 15:17 6379.log
drwxr-xr-x  2 root   root        4096 Jul  1 15:15 appendonlydir/
-rw-r--r--  1 root   root   517777890 Jul  1 15:17 dump-6379.rdb
root@vm-1:/opt/redis/data# ll appendonlydir/
total 505664
drwxr-xr-x 2 root root      4096 Jul  1 15:15 ./
drwxr-xr-x 3 root root      4096 Jul  1 15:17 ../
-rw-r--r-- 1 root root 517777890 Jul  1 15:15 appendonly.aof.1.base.rdb
-rw-r--r-- 1 root root       279 Jul  1 15:17 appendonly.aof.1.incr.aof
-rw-r--r-- 1 root root       102 Jul  1 15:15 appendonly.aof.manifest
root@vm-1:/opt/redis/data#

可以看到appendonly文件已经生成了。

下面执行重写:

# AOF 重写
127.0.0.1:6379> bgrewriteaof
Background append only file rewriting started

查看文件

root@vm-1:/opt/redis/data/appendonlydir# ll
total 505660
drwxr-xr-x 2 root root      4096 Jul  1 15:21 ./
drwxr-xr-x 3 root root      4096 Jul  1 15:21 ../
-rw-r--r-- 1 root root 517777927 Jul  1 15:21 appendonly.aof.2.base.rdb
-rw-r--r-- 1 root root         0 Jul  1 15:21 appendonly.aof.2.incr.aof
-rw-r--r-- 1 root root       102 Jul  1 15:21 appendonly.aof.manifest

RDB 和 AOF 的选择

对比

alt text

RDB最佳策略

  1. 推荐 关闭RDB
  2. 集中管理:如果是按天或按小时来备份数据,RDB是个不错的选择
  3. 主从模式,从节点推荐开启

AOF最佳策略

  1. 推荐打开
  2. AOF重写集中管理
  3. 推荐设置everysec每秒刷盘

最佳策略

  1. 小分片
  2. 缓存或存储
  3. 监控(硬盘、内存、负载、网络)
  4. 内存足够

其他

fork操作

  1. 它是一个同步操作,它只是做内存页的拷贝,而不是做整个内存的拷贝。大部分情况是它的速度是非常快的。但如果fork本身的操作非常慢,比如卡在某个地方,那么它也会阻塞Redis的主线程。
  2. fork 的执行时间和内存量息息相关,内存,内存页的数据也会越大,耗时越长,另外也和机器类型有关。
  3. 通过 info:latest_fork_usec 查看fork执行的时间。

改善fork

  1. 优先使用物理机或高效支持fork操作的虚拟化技术。
  2. 控制Redis实例最大可用内存:maxmemory
  3. 合理配置Linux内存分配策略:vm.overcommit_memory=1
  4. 降低fork频率:例如放宽AOF重写自动触发时机,不必要的全量复制

子进程的开销与优化

  1. CPU:
  • 开销:来自RDB和AOF文件生成(内存到磁盘),属于CPU密集型。
  • 优化:不要把Redis进程绑定到一个CPU上,不要和CPU密集型应用一起部署。
  1. 内存:
  • 开销:来自fork内存的开销,copy-on-write
  • 优化:echo never > /sys/kernel/mm/trasparent_hugepage/enabled
  1. 硬盘:
  • 开销:AOF和RDB文件写入,可以结合iostat, iotop 分析
  • 优化:不要和高硬盘负载服务器一起部署存储服务、消息队列等;设置 no-appendfsync-on-rewrite = yes;可以根据写入量决定磁盘类型,如ssd。

AOF追加阻塞

alt text