- 首先,我们应该明确持久化的数据有什么用,答案是用于重启后的数据恢复。
- Redis 是一个内存数据库,无论是 RDB 还是 AOF,都只是其保证数据恢复的措施。
- 所以 Redis 在利用 RDB 和 AOF 进行恢复的时候,都会读取 RDB 或 AOF 文件,重新加载到内存中。
RDB
AOF
- RDB 就是 Snapshot 快照存储,是默认的持久化方式。
可理解为半持久化模式,即按照一定的策略周期性的将数据保存到磁盘。
对应产生的数据文件为 dump.rdb,通过配置文件中的 save 参数来定义快照的周期。
-
下面是默认的快照设置:
- save 900 1 #当有一条 Keys 数据被改变时,900 秒刷新到 Disk 一次
- save 300 10 #当有 10 条 Keys 数据被改变时,300 秒刷新到 Disk 一次
- save 60 10000# 当有 10000 条 Keys 数据被改变时,60 秒刷新到 Disk 一次
Redis 的 RDB 文件不会坏掉,因为其写操作是在一个新进程中进行的。
当生成一个新的 RDB 文件时,Redis 生成的子进程会先将数据写到一个临时文件中,然后通过原子性 rename 系统调用将临时文件重命名为 RDB 文件。
这样在任何时候出现故障,Redis 的 RDB 文件都总是可用的。
同时,Redis 的 RDB 文件也是 Redis 主从同步内部实现中的一环。
第一次 Slave 向 Master 同步的实现是:
* Slave 向 Master 发出同步请求,Master 先 dump 出 rdb 文件,然后将 rdb 文件全量传输给 slave,然后 Master 把缓存的命令转发给 Slave,初次同步完成。
第二次以及以后的同步实现是:
* Master 将变量的快照直接实时依次发送给各个 Slave。但不管什么原因导致 Slave 和 Master 断开重连都会重复以上两个步骤的过程。
Redis 的主从复制是建立在内存快照的持久化基础上的,只要有 Slave 就一定会有内存快照发生。
可以很明显的看到,RDB 有它的不足,就是一旦数据库出现问题,那么我们的 RDB 文件中保存的数据并不是全新的。
从上次 RDB 文件生成到 Redis 停机这段时间的数据全部丢掉了。
AOF(Append-Only File) 比 RDB 方式有更好的持久化性。
由于在使用 AOF 持久化方式时,Redis 会将每一个收到的写命令都通过 Write 函数追加到文件中,类似于 MySQL 的 binlog。
当 Redis 重启是会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。
对应的设置参数为:
$ vim /opt/redis/etc/redis_6379.conf
-
appendonly yes
启用 AOF 持久化方式
-
appendfilename appendonly.aof
#AOF 文件的名称,默认为 appendonly.aof
-
appendfsync always
每次收到写命令就立即强制写入磁盘,是最有保证的完全的持久化,但速度也是最慢的,一般不推荐使用。
-
appendfsync everysec
每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,是受推荐的方式。
-
appendfsync no
#完全依赖 OS 的写入,一般为 30 秒左右一次,性能最好但是持久化最没有保证,不被推荐。
AOF 的完全持久化方式同时也带来了另一个问题,持久化文件会变得越来越大。
比如我们调用 INCR test 命令 100 次,文件中就必须保存全部的 100 条命令,但其实 99 条都是多余的。
因为要恢复数据库的状态其实文件中保存一条 SET test 100 就够了。
为了压缩 AOF 的持久化文件,Redis 提供了 bgrewriteaof 命令。
收到此命令后 Redis 将使用与快照类似的方式将内存中的数据以命令的方式保存到临时文件中,最后替换原来的文件,以此来实现控制 AOF 文件的增长。
由于是模拟快照的过程,因此在重写 AOF 文件时并没有读取旧的 AOF 文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的 AOF 文件。
对应的设置参数为:
$ vim /opt/redis/etc/redis_6379.conf
no-appendfsync-on-rewrite yes
在日志重写时,不进行命令追加操作,而只是将其放在缓冲区里,避免与命令的追加造成 DISK IO 上的冲突。
auto-aof-rewrite-percentage 100
当前 AOF 文件大小是上次日志重写得到 AOF 文件大小的二倍时,自动启动新的日志重写过程。
auto-aof-rewrite-min-size 64mb
#当前 AOF 文件启动新的日志重写过程的最小值,避免刚刚启动 Reids 时由于文件尺寸较小导致频繁的重写。
到底选择什么呢?下面是来自官方的建议:
- 通常,如果你要想提供很高的数据保障性,那么建议你同时使用两种持久化方式。
如果你可以接受灾难带来的几分钟的数据丢失,那么你可以仅使用 RDB。
很多用户仅使用了 AOF,但是我们建议,既然 RDB 可以时不时的给数据做个完整的快照,并且提供更快的重启,所以最好还是也使用 RDB。
因此,我们希望可以在未来(长远计划)统一 AOF 和 RDB 成一种持久化模式。
RDB 的启动时间会更短,原因有两个:
- 是 RDB 文件中每一条数据只有一条记录,不会像 AOF 日志那样可能有一条数据的多次操作记录。所以每条数据只需要写一次就行了。
另一个原因是 RDB 文件的存储格式和 Redis 数据在内存中的编码格式是一致的,不需要再进行数据编码工作,所以在 CPU 消耗上要远小于 AOF 日志的加载。
既然持久化的数据的作用是用于重启后的数据恢复,那么我们就非常有必要进行一次这样的灾难恢复模拟了。
据称如果数据要做持久化又想保证稳定性,则建议留空一半的物理内存。因为在进行快照的时候,fork 出来进行 dump 操作的子进程会占用与父进程一样的内存,真正的 copy-on-write,对性能的影响和内存的耗用都是比较大的。
目前,通常的设计思路是利用 Replication 机制来弥补 aof、snapshot 性能上的不足,达到了数据可持久化。
即 Master 上 Snapshot 和 AOF 都不做,来保证 Master 的读写性能,而 Slave 上则同时开启 Snapshot 和 AOF 来进行持久化,保证数据的安全性。
-
首先,修改 Master 上的如下配置:
- sudo vim /opt/redis/etc/redis_6379.conf
- save 900 1 #禁用 Snapshot
- save 300 10
- save 60 10000
- appendonly no #禁用 AOF
-
接着,修改 Slave 上的如下配置:
- sudo vim /opt/redis/etc/redis_6379.conf
- save 900 1 #启用 Snapshot
- save 300 10
- save 60 10000
appendonly yes #启用 AOF
appendfilename appendonly.aof #AOF 文件的名称
appendfsync always
appendfsync everysec #每秒钟强制写入磁盘一次
appendfsync no
no-appendfsync-on-rewrite yes #在日志重写时,不进行命令追加操作
auto-aof-rewrite-percentage 100 #自动启动新的日志重写过程
auto-aof-rewrite-min-size 64mb #启动新的日志重写过程的最小值
分别启动 Master 与 Slave
$ /etc/init.d/redis start
启动完成后在 Master 中确认未启动 Snapshot 参数
redis 127.0.0.1:6379> CONFIG GET save
1) “save”
2) “”
然后通过以下脚本在 Master 中生成 25 万条数据:
dongguo@redis:/opt/redis/data/6379$ cat redis-cli-generate.temp.sh
#!/bin/bash
REDISCLI="redis-cli -a slavepass -n 1 SET"
ID=1
while(($ID<50001))
do
INSTANCE_NAME="i-2-$ID-VM"
UUID=`cat /proc/sys/kernel/random/uuid`
PRIVATE_IP_ADDRESS=10.`echo "$RANDOM % 255 + 1" | bc`.`echo "$RANDOM % 255 + 1" | bc`.`echo "$RANDOM % 255 + 1" | bc`\
CREATED=`date "+%Y-%m-%d %H:%M:%S"`
$REDISCLI vm_instance:$ID:instance_name "$INSTANCE_NAME"
$REDISCLI vm_instance:$ID:uuid "$UUID"
$REDISCLI vm_instance:$ID:private_ip_address "$PRIVATE_IP_ADDRESS"
$REDISCLI vm_instance:$ID:created "$CREATED"
$REDISCLI vm_instance:$INSTANCE_NAME:id "$ID"
ID=$(($ID+1))
done
dongguo@redis:/opt/redis/data/6379$ ./redis-cli-generate.temp.sh
在数据的生成过程中,可以很清楚的看到 Master 上仅在第一次做 Slave 同步时创建了 dump.rdb 文件,之后就通过增量传输命令的方式给 Slave 了。
dump.rdb 文件没有再增大。
dongguo@redis:/opt/redis/data/6379$ ls -lh
total 4.0K
-rw-r–r– 1 root root 10 Sep 27 00:40 dump.rdb
而 Slave 上则可以看到 dump.rdb 文件和 AOF 文件在不断的增大,并且 AOF 文件的增长速度明显大于 dump.rdb 文件。
dongguo@redis-slave:/opt/redis/data/6379$ ls -lh
total 24M
-rw-r–r– 1 root root 15M Sep 27 12:06 appendonly.aof
-rw-r–r– 1 root root 9.2M Sep 27 12:06 dump.rdb
等待数据插入完成以后,首先确认当前的数据量。
redis 127.0.0.1:6379> info
redis_version:2.4.17
redis_git_sha1:00000000
redis_git_dirty:0
arch_bits:64
multiplexing_api:epoll
gcc_version:4.4.5
process_id:27623
run_id:e00757f7b2d6885fa9811540df9dfed39430b642
uptime_in_seconds:1541
uptime_in_days:0
lru_clock:650187
used_cpu_sys:69.28
used_cpu_user:7.67
used_cpu_sys_children:0.00
used_cpu_user_children:0.00
connected_clients:1
connected_slaves:1
client_longest_output_list:0
client_biggest_input_buf:0
blocked_clients:0
used_memory:33055824
used_memory_human:31.52M
used_memory_rss:34717696
used_memory_peak:33055800
used_memory_peak_human:31.52M
mem_fragmentation_ratio:1.05
mem_allocator:jemalloc-3.0.0
loading:0
aof_enabled:0
changes_since_last_save:250000
bgsave_in_progress:0
last_save_time:1348677645
bgrewriteaof_in_progress:0
total_connections_received:250007
total_commands_processed:750019
expired_keys:0
evicted_keys:0
keyspace_hits:0
keyspace_misses:0
pubsub_channels:0
pubsub_patterns:0
latest_fork_usec:246
vm_enabled:0
role:master
slave0:10.6.1.144,6379,online
db1:keys=250000,expires=0
当前的数据量为 25 万条 key,占用内存 31.52M。
然后我们直接 Kill 掉 Master 的 Redis 进程,模拟灾难。
dongguo@redis:/opt/redis/data/6379$ sudo killall -9 redis-server
我们到 Slave 中查看状态:
redis 127.0.0.1:6379> info
redis_version:2.4.17
redis_git_sha1:00000000
redis_git_dirty:0
arch_bits:64
multiplexing_api:epoll
gcc_version:4.4.5
process_id:13003
run_id:9b8b398fc63a26d160bf58df90cf437acce1d364
uptime_in_seconds:1627
uptime_in_days:0
lru_clock:654181
used_cpu_sys:29.69
used_cpu_user:1.21
used_cpu_sys_children:1.70
used_cpu_user_children:1.23
connected_clients:1
connected_slaves:0
client_longest_output_list:0
client_biggest_input_buf:0
blocked_clients:0
used_memory:33047696
used_memory_human:31.52M
used_memory_rss:34775040
used_memory_peak:33064400
used_memory_peak_human:31.53M
mem_fragmentation_ratio:1.05
mem_allocator:jemalloc-3.0.0
loading:0
aof_enabled:1
changes_since_last_save:3308
bgsave_in_progress:0
last_save_time:1348718951
bgrewriteaof_in_progress:0
total_connections_received:4
total_commands_processed:250308
expired_keys:0
evicted_keys:0
keyspace_hits:0
keyspace_misses:0
pubsub_channels:0
pubsub_patterns:0
latest_fork_usec:694
vm_enabled:0
role:slave
aof_current_size:17908619
aof_base_size:16787337
aof_pending_rewrite:0
aof_buffer_length:0
aof_pending_bio_fsync:0
master_host:10.6.1.143
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
master_link_down_since_seconds:25
slave_priority:100
db1:keys=250000,expires=0
可以看到 master_link_status 的状态已经是 down 了,Master 已经不可访问了。
而此时,Slave 依然运行良好,并且保留有 AOF 与 RDB 文件。
下面我们将通过 Slave 上保存好的 AOF 与 RDB 文件来恢复 Master 上的数据。
首先,将 Slave 上的同步状态取消,避免主库在未完成数据恢复前就重启,进而直接覆盖掉从库上的数据,导致所有的数据丢失。
redis 127.0.0.1:6379> SLAVEOF NO ONE
OK
确认一下已经没有了 master 相关的配置信息:
redis 127.0.0.1:6379> INFO
redis_version:2.4.17
redis_git_sha1:00000000
redis_git_dirty:0
arch_bits:64
multiplexing_api:epoll
gcc_version:4.4.5
process_id:13003
run_id:9b8b398fc63a26d160bf58df90cf437acce1d364
uptime_in_seconds:1961
uptime_in_days:0
lru_clock:654215
used_cpu_sys:29.98
used_cpu_user:1.22
used_cpu_sys_children:1.76
used_cpu_user_children:1.42
connected_clients:1
connected_slaves:0
client_longest_output_list:0
client_biggest_input_buf:0
blocked_clients:0
used_memory:33047696
used_memory_human:31.52M
used_memory_rss:34779136
used_memory_peak:33064400
used_memory_peak_human:31.53M
mem_fragmentation_ratio:1.05
mem_allocator:jemalloc-3.0.0
loading:0
aof_enabled:1
changes_since_last_save:0
bgsave_in_progress:0
last_save_time:1348719252
bgrewriteaof_in_progress:0
total_connections_received:4
total_commands_processed:250311
expired_keys:0
evicted_keys:0
keyspace_hits:0
keyspace_misses:0
pubsub_channels:0
pubsub_patterns:0
latest_fork_usec:1119
vm_enabled:0
role:master
aof_current_size:17908619
aof_base_size:16787337
aof_pending_rewrite:0
aof_buffer_length:0
aof_pending_bio_fsync:0
db1:keys=250000,expires=0
在 Slave 上复制数据文件:
dongguo@redis-slave:/opt/redis/data/6379$ tar cvf /home/dongguo/data.tar *
appendonly.aof
dump.rdb
将 data.tar 上传到 Master 上,尝试恢复数据:
可以看到 Master 目录下有一个初始化 Slave 的数据文件,很小,将其删除。
dongguo@redis:/opt/redis/data/6379$ ls -l
total 4
-rw-r–r– 1 root root 10 Sep 27 00:40 dump.rdb
dongguo@redis:/opt/redis/data/6379$ sudo rm -f dump.rdb
然后解压缩数据文件:
dongguo@redis:/opt/redis/data/6379$ sudo tar xf /home/dongguo/data.tar
dongguo@redis:/opt/redis/data/6379$ ls -lh
total 29M
-rw-r–r– 1 root root 18M Sep 27 01:22 appendonly.aof
-rw-r–r– 1 root root 12M Sep 27 01:22 dump.rdb
启动 Master 上的 Redis;
dongguo@redis:/opt/redis/data/6379$ sudo /etc/init.d/redis start
Starting Redis server…
查看数据是否恢复:
redis 127.0.0.1:6379> INFO
redis_version:2.4.17
redis_git_sha1:00000000
redis_git_dirty:0
arch_bits:64
multiplexing_api:epoll
gcc_version:4.4.5
process_id:16959
run_id:6e5ba6c053583414e75353b283597ea404494926
uptime_in_seconds:22
uptime_in_days:0
lru_clock:650292
used_cpu_sys:0.18
used_cpu_user:0.20
used_cpu_sys_children:0.00
used_cpu_user_children:0.00
connected_clients:1
connected_slaves:0
client_longest_output_list:0
client_biggest_input_buf:0
blocked_clients:0
used_memory:33047216
used_memory_human:31.52M
used_memory_rss:34623488
used_memory_peak:33047192
used_memory_peak_human:31.52M
mem_fragmentation_ratio:1.05
mem_allocator:jemalloc-3.0.0
loading:0
aof_enabled:0
changes_since_last_save:0
bgsave_in_progress:0
last_save_time:1348680180
bgrewriteaof_in_progress:0
total_connections_received:1
total_commands_processed:1
expired_keys:0
evicted_keys:0
keyspace_hits:0
keyspace_misses:0
pubsub_channels:0
pubsub_patterns:0
latest_fork_usec:0
vm_enabled:0
role:master
db1:keys=250000,expires=0
可以看到 25 万条数据已经完整恢复到了 Master 上。
此时,可以放心的恢复 Slave 的同步设置了。
redis 127.0.0.1:6379> SLAVEOF 10.6.1.143 6379
OK
查看同步状态:
redis 127.0.0.1:6379> INFO
redis_version:2.4.17
redis_git_sha1:00000000
redis_git_dirty:0
arch_bits:64
multiplexing_api:epoll
gcc_version:4.4.5
process_id:13003
run_id:9b8b398fc63a26d160bf58df90cf437acce1d364
uptime_in_seconds:2652
uptime_in_days:0
lru_clock:654284
used_cpu_sys:30.01
used_cpu_user:2.12
used_cpu_sys_children:1.76
used_cpu_user_children:1.42
connected_clients:2
connected_slaves:0
client_longest_output_list:0
client_biggest_input_buf:0
blocked_clients:0
used_memory:33056288
used_memory_human:31.52M
used_memory_rss:34766848
used_memory_peak:33064400
used_memory_peak_human:31.53M
mem_fragmentation_ratio:1.05
mem_allocator:jemalloc-3.0.0
loading:0
aof_enabled:1
changes_since_last_save:0
bgsave_in_progress:0
last_save_time:1348719252
bgrewriteaof_in_progress:1
total_connections_received:6
total_commands_processed:250313
expired_keys:0
evicted_keys:0
keyspace_hits:0
keyspace_misses:0
pubsub_channels:0
pubsub_patterns:0
latest_fork_usec:12217
vm_enabled:0
role:slave
aof_current_size:17908619
aof_base_size:16787337
aof_pending_rewrite:0
aof_buffer_length:0
aof_pending_bio_fsync:0
master_host:10.6.1.143
master_port:6379
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_priority:100
db1:keys=250000,expires=0
master_link_status 显示为 up,同步状态正常。
在此次恢复的过程中,我们同时复制了 AOF 与 RDB 文件,那么到底是哪一个文件完成了数据的恢复呢?
实际上,当 Redis 服务器挂掉时,重启时将按照以下优先级恢复数据到内存:
- 如果只配置 AOF, 重启时加载 AOF 文件恢复数据;
- 如果同时 配置了 RDB 和 AOF, 启动是只加载 AOF 文件恢复数据;
- 如果只配置 RDB, 启动是将加载 dump 文件恢复数据。
也就是说,AOF 的优先级要高于 RDB,这也很好理解,因为 AOF 本身对数据的完整性保障要高于 RDB。
在此次的案例中,我们通过在 Slave 上启用了 AOF 与 RDB 来保障了数据,并恢复了 Master。
但在我们目前的线上环境中,由于数据都设置有过期时间,采用 AOF 的方式会不太实用,过于频繁的写操作会使 AOF 文件增长到异常的庞大,大大超过了我们实际的数据量,这也会导致在进行数据恢复时耗用大量的时间。
因此,可以在 Slave 上仅开启 Snapshot 来进行本地化,同时可以考虑将 save 中的频率调高一些或者调用一个计划任务来进行定期 bgsave 的快照存储,来尽可能的保障本地化数据的完整性。
在这样的架构下,如果仅仅是 Master 挂掉,Slave 完整,数据恢复可达到 100%。
如果 Master 与 Slave 同时挂掉的话,数据的恢复也可以达到一个可接受的程度。
如希望了解更多,请关注微信公众号