redis数据持久化与迁移

背景

最近在工作中需要迁移redis的数据,上网搜了一遍之后感觉资料特别零散而且讲的也不太清楚。所以就打算写一篇文章好好整理一下关于redis备份恢复方面的知识,希望能对大家有所帮助。
redis数据迁移主要有三种方式:

  • RDB
  • AOF
  • redis-dump

本文将针对每种方式介绍其操作方法及原理,并且分析比对每种方式的优缺点和实用场景。

RDB

RDB介绍及原理

RDB持久化是redis默认的一种持久化方案,持久化完成会生成.rdb文件。rdb文件全量保存了数据库的数据,不过它是以二进制压缩的方式保存的。可以简单看一下开头部分文件内容:

// 命令行使用od -c 以ascii编码方式查看
~ od -c dump.rdb
0000000    R   E   D   I   S   0   0   0   9 372  \t   r   e   d   i   s
0000020    -   v   e   r 005   5   .   0   .   3 372  \n   r   e   d   i
0000040    s   -   b   i   t   s 300   @ 372 005   c   t   i   m   e 007
0000060   \a   G 256   ` 372  \b   u   s   e   d   -   m   e   m 302   P
0000100  260   : 004 372  \f   a   o   f   -   p   r   e   a   m   b   l
0000120    e 300  \0 376  \0 373   W 330  \0 016   "   r   e   c   y   c
0000140    l   e   :   l   i   s   t   :   r   o   u   t   e   :   1   6
0000160    1   6   2   0   6   7   7   6   l   W   8   7   i 001 303   C
0000200    {   G   z 037   z  \a  \0  \0   R 006  \0  \0 016  \0  \0   @
复制代码

RDB模式使用SAVE命令或者BGSAVE命令来持久化,两个命令最终执行调用的都是rdbSave函数,两者的区别在于SAVE是阻塞主进程的;而BGSAVE是非阻塞的,通过调用fork产生子进程来操作。但是注意fork出子进程这一操作是会阻塞的。
image.png
由于大家都知道redis是单线程的,主线程一阻塞那性能大大下降。所以SAVE基本不用,最常用的还是BGSAVE

RDB备份配置及实践

配置

既然RDB是redis的持久化方式,那肯定不需要咱们自己去调用BGSAVE来保存数据。redis可以配置自动的定期去执行BGSAVE命令,我们找到redis.conf文件,可以看到里面有以下与RDB备份相关的配置:

# 持久化文件存放的地址,包括rdb、aof
dir /usr/local/var/db/redis/
 
# rdb持久化文件名称
dbfilename dump.rdb

# 是否压缩rdb文件
rdbcompression yes

# 通过rdb文件恢复时,是否启用check_sum来校验rdb文件是否缺失、是否完整等问题
rdbchecksum yes

# rdb默认备份策略
save 900 1  # 900秒内有至少1此数据操作,则执行bgsave
save 300 10  # 300秒内有至少10次数据操作,则执行bgsave
save 60 10000  # 60秒内有至少10000次数据操作,则执行bgsave

# rdb的备份策略只要满足其中一个,就会执行。另外如果想取消自动备份,可以在末尾添加save ""
复制代码

演示通过RDB文件恢复数据

  1. 制造数据
redis-cli

# 先清理下数据库
127.0.0.1:6379> FLUSHDB
OK

# 然后插入一些数据
127.0.0.1:6379> SET astr astring
OK
127.0.0.1:6379> RPUSH alist a b c d e f
(integer) 6
127.0.0.1:6379> HSET ahash 1 1
(integer) 1
127.0.0.1:6379> SADD aset 1 2 3 4
(integer) 4
127.0.0.1:6379> ZADD azset 1 a
(integer) 1
127.0.0.1:6379> keys *
1) "astr"
2) "aset"
3) "azset"
4) "alist"
5) "ahash"

# 使用save立即备份
127.0.0.1:6379> SAVE
OK
复制代码
  1. 查看生成的rdb文件,并备份到别的文件夹
# 查看备份文件夹
ls /usr/local/var/db/redis                                             
dump.rdb

# 保存到别的地方
cp dump.rdb ~/back_2021.rdb
复制代码
  1. 清空数据库
127.0.0.1:6379> FLUSHALL
OK
127.0.0.1:6379> keys *
(empty list or set)
复制代码
  1. 使用备份文件恢复数据
# 清空了redis数据之后,再删掉原先的dump.rdb文件
rm dump.rdb

# 停止redis服务
redis-cli shutdown

# 把备份的文件放在配置文件中指定的dir目录下
# 并重命名为dbfilename指定的名字
cp ~/back_2021.rdb dump.rdb

# 启动redis服务(注意指定配置文件,否则dir会变为启动时路径)
# 我的配置文件是在/usr/local/etc/redis.conf
redis-server /usr/local/etc/redis.conf
复制代码
  1. 查看服务启动时的rdb文件加载日志
16886:M 28 May 2021 14:19:56.230 # Server initialized
# 下面这行就是从rdb文件加载
16886:M 28 May 2021 14:19:56.231 * DB loaded from disk: 0.001 seconds
16886:M 28 May 2021 14:19:56.231 * Ready to accept connections
复制代码
  1. 检查备份完成后的数据
127.0.0.1:6379> keys *
1) "astr"
2) "alist"
3) "aset"
4) "azset"
5) "ahash"
复制代码

rdb备份的特点

  • 备份数据是压缩的,所以备份文件较小
  • 二进制文件,可读性极差
  • 备份数据恢复速度快
  • 由于每次BGSAVE要备份全量数据,所以一般配置的备份频率不高,如果出错那么就可能丢失一段时间的数据

AOF

AOF介绍及原理

AOF是append only file 的简写,与RDB每次备份全量的不同,AOF是增量往一个文件里添加每次操作的命令。简单看一下一个aof文件的开头部分:

# 注意要想看到这种格式的,需要把aof-use-rdb-preamble配置为no, 后面会讲为什么
*2
$6
SELECT  # 忽略
$1
0
*6
$4
SADD  # sadd
$4
aset  # aset
$1
1     # 1
$1
2     # 2
$1
3     # 3
$1
4     # 4
*8
$5
RPUSH  # rpush
$5
alist  # alist
$1
a      # a
$1
b      # b
$1
c      # c
$1
d      # d
$1
e      # e
$1
f      # f
复制代码

忽略一些其他数据,很明显就是我们上面演示rdb时插入键aset、alist...的那些命令。
AOF功能开启后,每次操作数据库之后,都会把操作命令写入aof_buf这样一个aof内存缓冲区域,每次事件循环结束,会把aof_buf的内容write进文件。然后根据redis.conf的同步策略,定时同步到硬盘中。配置参数appendfsync总共有三种策略:

  • always 每次事件循环结束都会同步到硬盘文件中
  • everysec 距离上次同步超过一秒,则再次同步至硬盘文件中 (默认配置)
  • no 永远不主动同步,而是由操作系统同步到硬盘文件中

文件系统知识点:为了提高文件的写入效率,当用户使用write函数写入数据到文件时,操作系统并不会直接写入到硬盘,而是提供一个文件缓冲区,等缓冲区满了再一起存入硬盘。但这种操作有丢失数据的风险,所以操作系统又提供了强制落盘命令,用户可以不等缓冲区满自由控制刷盘。redis中的appendfsync同步指的就是调用刷盘命令。

可以看出在默认配置下,AOF模式最多只会丢失一秒的数据。

AOF备份配置及实践

配置

AOF模式相关的配置主要有以下这些

# 是否开启AOF持久化,开启后,服务器启动时读取aof文件进行恢复
appendonly yes

# 持久化文件存放的地址,aof和rdb是在同一个路径下的
dir /usr/local/var/db/redis/

# AOF持久化文件保存的名称
appendfilename "appendonly.aof"

# 尾部有缺失的aof文件是否仍然继续加载
aof-load-truncated yes

# aof文件体积比上次重写后的体积大一倍,则立即重写
auto-aof-rewrite-percentage 100

# 低于64mb的aof文件不会进行重写
auto-aof-rewrite-min-size 64mb

# 开启后,aof重写时每32mb会同步刷到硬盘
aof-rewrite-incremental-fsync yes

# 是否开启aof混合模式
aof-use-rdb-preamble yes
复制代码

演示使用AOF恢复

数据的制造与备份与rdb的操作类似,不过最终生成的文件是appendonly.aof

  1. aof文件备份到其他路径
cp appendonly.aof ~/back_2021.aof
复制代码
  1. 清空数据库
127.0.0.1:6379> FLUSHALL
OK
复制代码
  1. 使用备份的aof文件恢复

# 停止服务
redis-cli shutdown 

# 删除原先的aof文件
rm appendonly.aof

# 备份的aof文件拷贝过来
cp ~/back_2021.aof ./appendonly.aof

# 指定配置文件启动redis-server
redis-server /usr/local/etc/redis.conf
复制代码
  1. 查看服务启动时载入aof文件的输出日志。
31343:M 28 May 2021 16:24:49.923 # Server initialized
# 下面的日志显示服务从aof恢复数据
31343:M 28 May 2021 16:24:49.923 * DB loaded from append only file: 0.001 seconds
31343:M 28 May 2021 16:24:49.923 * Ready to accept connections
复制代码
  1. 查看恢复后的数据
127.0.0.1:6379> keys *
1) "ahash"
2) "aset"
3) "astr"
4) "azset"
5) "alist"
复制代码

aof备份的特点

  • 数据完整,最多丢失一秒的数据
  • 由于保存的是原始命令,数据恢复需要重放命令,恢复速度会比较慢
  • 特殊格式的命令,可读性还不错

AOF重写

由于aof文件是增量式的,随着服务的运行,文件将变的越来越大,这显然是不合理的。于是redis实现了aof文件重写的功能,来对aof文件瘦身。

什么是重写?

命令执行越来越多,但实际上数据是可能有删除和过期的,删除及过期了的数据都不存在了,还保留他们的aof操作命令明显没有什么作用。所以重写其实就是根据当前数据库里保存的数据,依照aof文件格式,全量重新写一份aof,写完代替旧的aof。

重写怎么实现的?

全量把所有当前的key重新生成一份aof,那肯定比较耗时,所以不能在主进程进行,可以fork子进程来操作。但同时又有一个问题,子进程在重写的过程中,主进程还在处理业务,这期间产生的操作命令怎么办?
解决方式就是使用一个重写缓冲区,从生成子进程重写开始,后面的操作命令也往重写缓冲区里面写一份。等子进程重写完毕后,通知主进程,由主进程把剩下的命令从重写缓冲区写入aof文件,主进程把重写缓冲区的命令写入时是阻塞的,此时不会处理别的命令,处理完后用新生成的aof替代旧的aof文件。大致流程如下:
image.png

混合持久化(redis4.0引入)

配置参数 aof-use-rdb-preamble yes (redis 5.0 默认开启)
混合持久化指的是aof重写最终生成混合格式的aof文件,混合aof文件的格式为 [RDB file][AOF tail]。即子进程重写的全量数据不再以aof格式来存储,而是以rdb文件的格式来存储。重写完通知主进程后,主进程再把剩余的缓冲区命令添加到文件末尾,当然最终生成的文件依然还是.aof文件。我个人认为混合持久化并不能算是一种新的持久化方式,它只是对aof文件恢复速度慢的一种优化。一个混合aof文件如下:

0000000    R   E   D   I   S   0   0   0   9 372  \t   r   e   d   i   s
0000020    -   v   e   r 005   5   .   0   .   3 372  \n   r   e   d   i
0000040    s   -   b   i   t   s 300   @ 372 005   c   t   i   m   e 356
0000060  356   ذ  **   ` 372  \b   u   s   e   d   -   m   e   m 302   P
0000100   \t 020  \0 372  \f   a   o   f   -   p   r   e   a   m   b   l
0000120    e 300 001 376  \0 373 005  \0  \0 004   a   s   t   r  \a   a
0000140    s   t   r   i   n   g  \v 004   a   s   e   t 020 002  \0  \0
0000160   \0 004  \0  \0  \0 001  \0 002  \0 003  \0 004  \0  \r 005   a
0000200    h   a   s   h 017 017  \0  \0  \0  \f  \0  \0  \0 002  \0  \0
0000220  362 002 362 377  \f 005   a   z   s   e   t 020 020  \0  \0  \0
0000240   \r  \0  \0  \0 002  \0  \0 001   a 003 362 377 016 005   a   l
0000260    i   s   t 001 035 035  \0  \0  \0 031  \0  \0  \0 006  \0  \0
0000300  001   a 003 001   b 003 001   c 003 001   d 003 001   e 003 001
0000320    f 377 377 237 345 230   %    ̫  ** 220   r   *   2  \r  \n   $
0000340    6  \r  \n   S   E   L   E   C   T  \r  \n   $   1  \r  \n   0
0000360   \r  \n   *   3  \r  \n   $   3  \r  \n   S   E   T  \r  \n   $
0000400    2  \r  \n   k   3  \r  \n   $   2  \r  \n   k   3  \r  \n
0000417
复制代码

可以看到它的开头是R E D I S与rdb文件开头是一样的,而结尾是aof文件的格式。

redis-dump

介绍

redis-dump 是一个开源的第三方工具,支持以json的格式导入和导出redis数据。大致的原理就是导出时通过读取redis的数据转换为json格式存储,导入是把json数据转化为redis命令执行。

安装使用

redis-dump是ruby开发的工具,先需要安装ruby

# mac 安装ruby
brew install ruby
复制代码

替换ruby包管理源为国内

# 查看源
gem sources

# 删除旧的源
gem sources --remove  https://rubygems.org/ 

# 添加新的源
gem sources --add https://gems.ruby-china.org
复制代码

安装redis-dump

gem install redis-dump
复制代码

导出数据

redis-dump –u 127.0.0.1:6379 > data.json
# 导出指定数据库数据
redis-dump -u 127.0.0.1:6379 -d 15 > data.json
# 如果redis设有密码
redis-dump –u :password@127.0.0.1:6379 > data.json
# 指定key导出
redis-dump -u :xxxx@localhost:6379 -f adsl:* > data.json
复制代码

导入数据

> data.json redis-load -u 127.0.0.1:6379
# 带密码
> data.json redis-load -u 127.0.0.1:6379 -a password
复制代码

注意的问题

  1. redis-dump目前是beta版本,使用前请先测试。
  2. redis-dump导出的数据过期时间存在问题。如下是数据格式:
{"db":0,"key":"hashkey","ttl":-1,"type":"hash","value":{"field_a":"value_a","field_b":"value_b","field_c":"value_c"},"size":42}
{"db":0,"key":"listkey","ttl":-1,"type":"list","value":["value_0","value_1","value_2","value_0","value_1","value_2"],"size":42}
{"db":0,"key":"setkey","ttl":-1,"type":"set","value":["value_2","value_0","value_1","value_3"],"size":28}
{"db":0,"key":"zsetkey","ttl":-1,"type":"zset","value":[["value_0","100"],["value_1","100"],["value_2","200"],["value_3","300"],["value_4","400"]],"size":50}
{"db":0,"key":"stringkey","ttl":79,"type":"string","value":"stringvalue","size":11}
复制代码

过期时间不是具体的时间戳,导入后会和原来的过期时间不对应

三种方式对比

备份文件大小

rdb < aof < redis-dump

恢复速度

rdb > aof > redis-dump

文件可读性

redis-dump > aof > rdb

操作便捷性

aof = rdb > redis-dump

指定key导出

只有redis-dump支持按一定的key规则导出指定的数据

总结

三种方式都可以对数据进行备份迁移。RDB 和AOF 主要用在持久化和恢复方面, 当然也可以用于整库迁移。redis-dump是一个工具,它最主要的优点是可以根据指定的key规则来导出数据,可以批量导出和恢复部分的key,但是不适合用于持久化数据。

如果觉的本文对你有所帮助,欢迎点个赞👍

dianzan.gif

ps: 推荐一个商城项目: 体验, github, gitee