我的文章会先发布到个人博客后,再更新到简书,可以到个人博客或者gzh获取更多内容。
背景
当我们需要面对数据迁移(部分数据导出导入、redis集群迁移、云上redis与云下redis之间迁移)、架构变更(单点到集群、m分片到n分片)、redis版本升级或者异地多活容灾等情况时,我们就需要将redis单点或集群中的数据迁移到另一个redis集群或实例中。
迁移工具
redis自带的dump与restore
redis-cli -h localhost -a passwd -n 0 keys "*" | while read key
do
redis-cli -h localhost -a passwd -n 0 --raw dump $key | perl -pe 'chomp if eof' | redis-cli -h localhost -a passwd -n 12 -x restore $key 0
done
缺点:每次只能处理单个key,大量数据迁移和集群相关需要写复杂脚本或程序
redis-cli后续新增的集群操作
手动备份集群数据
#redis-cli --cluster backup host:port backup_directory
redis-cli --cluster backup 192.168.60.200:6101 ./
ls -a
#. nodes.json redis-node-192.168.60.200-6102-c91ee22d2a365dc0db65385f0d4f81c39b2690f7.rdb
#.. redis-node-192.168.60.200-6101-52cf8d763cc554a20126488beaf2c03febfd490f.rdb redis-node-192.168.60.200-6103-b0e053f116dd121bfe23ca7464608b2584f42328.rdb
#会生成节点相关信息和对应节点的rdb文件
import 迁移
#import host:port --cluster-from host:port [--cluster-copy | --cluster-replace]
#从一个redis单例192.168.60.200:6101导入到192.168.60.200:6104所在集群
redis-cli --cluster import 192.168.60.200:6104 --cluster-from 192.168.60.200:6101 --cluster-copy
缺点:导出redis节点不能是集群的节点只能是非集群
redis-dump redis-load
redis-dump -u 127.0.0.1:6378 -a password > redis_6378.json
cat redis_6378.json redis-load -u 127.0.0.1 -a password
缺点:只能单点到单点或同节点数量和槽分布一样的集群间数据迁移,且会用到keys命令,迁移数据过大会卡住redis其他服务。
redis-migrate-tool
唯品会开源的redis迁移工具,快速、多线程、基于redis复制、实时迁移、迁移过程中,源集群不影响对外提供服务、异构迁移、过滤功能、迁移状态显示、完善的数据抽样校验。
迁移源: 单独的redis实例,twemproxy集群,redis cluster,rdb文件,aof文件。
目标: 单独的redis实例,twemproxy集群,redis cluster,rdb文件。
github地址
官方版本: 仅支持redis3及以下版本
其他人优化版本 :基于官方版本修改,支持redis4及以上版本
官方文档
例子
配置
配置文件示例:从redis cluster集群迁移数据到另外一个cluster集群
#cat rmt.conf
[source]
type: redis cluster
servers:
- 127.0.0.1:8379
[target]
type: redis cluster
servers:
- 127.0.0.1:7379
[common]
listen: 0.0.0.0:8888
配置文件示例:从 rdb 文件恢复数据到 redis cluster 集群
#cat rmt.conf
[source]
type: rdb file
servers:
- /data/redis/dump1.rdb
- /data/redis/dump2.rdb
- /data/redis/dump3.rdb
[target]
type: redis cluster
servers:
- 127.0.0.1:7379
[common]
listen: 0.0.0.0:8888
运行
redis-migrate-tool -c rmt.conf -o log -d
运行状态
通过redis-cli连接redis-migrate-tool监控的端口,运行info命令
redis-cli -h 127.0.0.1 -p 8888
127.0.0.1:8888> info
# Server
version:0.1.0
os:Linux 2.6.32-573.12.1.el6.x86_64 x86_64
multiplexing_api:epoll
gcc_version:4.4.7
process_id:9199
tcp_port:8888
uptime_in_seconds:1662
uptime_in_days:0
config_file:/ect/rmt.conf
# Clients
connected_clients:1
max_clients_limit:100
total_connections_received:3
# Memory
mem_allocator:jemalloc-4.0.4
# Group
source_nodes_count:32
target_nodes_count:48
# Stats
all_rdb_received:1
all_rdb_parsed:1
all_aof_loaded:0
rdb_received_count:32
rdb_parsed_count:32
aof_loaded_count:0
total_msgs_recv:7753587
total_msgs_sent:7753587
total_net_input_bytes:234636318
total_net_output_bytes:255384129
total_net_input_bytes_human:223.77M
total_net_output_bytes_human:243.55M
total_mbufs_inqueue:0
total_msgs_outqueue:0
数据校验
数据校验默认是1000个,可以更改调大
redis-migrate-tool -c rmt.conf -o log -C redis_check
Check job is running...
Checked keys: 1000
Inconsistent value keys: 0
Inconsistent expire keys : 0
Other check error keys: 0
Checked OK keys: 1000
All keys checked OK!
Check job finished, used 1.041s
优点:完全满足redis集群迁移的所有需要,使用简单。
缺点:官方版本不支持redis4及以上的redis版本,优化版不知道稳定性如何,遇到问题查找困难。
redis-shake
redis-shake是阿里云基于豌豆荚redis-port开发的一款非常好用的工具,可以支持备份、恢复、解析、同步等功能,主要的功能有:
- decode,对RDB文件进行读取,并以json格式存储
- restore,将RDB文件恢复到目的Redis服务器
- dump,将源Redis服务器的数据通过RDB文件的方式保存下来
- sync,支持源Redis和目的Redis的数据同步,通过模拟成Slave(使用psync),支持全量和增量数据的同步,对单节点、主从、集群环境之间进行同步(2.8-5.0版本,codis环境),也支持云上环境
- rump,使用scan和restore命令进行数据迁移,对不支持psync命令的环境可以使用这种方式,仅支持全量的数据迁移
迁移方式
sync/psync方式
例子:单点到集群迁移
#修改sync.toml
type = "sync"
[source]
address = "192.168.0.2:6379"
password = "r-aaaaa:xxxxx"
[target]
type = "cluster"
address = "192.168.0.1:6379" # 这里写集群中的任意一个节点的地址即可
password = "r-ccccc:xxxxx"
#启动 redis-shake:
./redis-shake sync.toml
source:为一个单点或者集群的某个节点(集群迁移,每个主节点需要对应启动一个redis-shake)
type: standalone 或 cluster,分别对应目标redis是单机或集群情况
集群与集群间迁移,可以利用官方自带cluster_helper.py自动启动源集群所有分片的数据迁移。
该方式不仅能应用与数据迁移或redis版本升级,还能通过该方法实现异地多活、容灾备份
dump方式
#修改 restore.toml
type = "restore"
[source]
rdb_file_path = "/path/dump.rdb"
[target]
type = "standalone" #恢复到单点
#type = "cluster" #恢复到集群
address = "127.0.0.1:6379"
password = "r-aaaaa:xxxxx"
scan方式
云厂商出于种种考虑禁用了 Redis 的 PSync 命令时,psync不可用。 对于这种情况可以使用 redis-shake 的 scan 模式来进行数据迁移,原理是调用 scan 命令来获取 Redis 中的 key,然后使用 dump 命令获取 key 的内容,最终使用 restore 命令恢复 key 至目的端。
scan命令的缺点就是会漏key,key的一致性不能保证,且该方式没有同步增量的功能,所以推荐以上两种。
type = "scan"
[source]
address = "r-aaaaa.redis.zhangbei.rds.aliyuncs.com:6379"
password = "r-aaaaa:xxxxx"
[target]
type = "standalone" #恢复到单点
#type = "cluster" #恢复到集群
address = "192.168.0.1:6379" # 这里写单点的地址或集群中的任意一个节点的地址即可
password = "r-ccccc:xxxxx"
集群同步到集群与 sync/psync类似,通过cluster_helper.py 启动同步
filters 做数据清洗
redis-shake支持同步过程中做数据筛选
--- cat filters/print.lua
--- function name must be `filter`
---
--- arguments:
--- @id number: the sequence of the cmd
--- @is_base boolean: whether the command is decoded from dump.rdb file
--- @group string: the group of cmd
--- @cmd_name string: cmd name
--- @keys table: keys of the command
--- @slots table: slots of the command
--- @db_id: database id
--- @timestamp_ms number: timestamp in milliseconds, 0 if not available
--- return:
--- @code number:
--- * 0: allow
--- * 1: disallow
--- * 2: error occurred
--- @db_id number: redirection database id
function filter(id, is_base, group, cmd_name, keys, slots, db_id, timestamp_ms)
print(string.format("lua filter. id=[%d], is_base=[%s], db_id=[%d], group=[%s], cmd_name=[%s], keys=[%s], slots=[%s], timestamp_ms=[%d]",
id, tostring(is_base), db_id, group, cmd_name, table.concat(keys, ", "), table.concat(slots, ", "), timestamp_ms))
return 0, db_id
end
code:可选值 0,1,2
- 0:允许此条数据发送至对端
- 1:跳过此条数据
- 2:不应该出现此数据,立即终止 redis-shake
db_id:代表这条命令会发送到哪个 database,只在 swap.db 中使用。一般情况下与传入 db_id 保持一致即可。
优点;稳定,使用的人多,版本还在更新维护,redis各种版本都支持,功能丰富
缺点:集群与集群间同步迁移等操作需要借助python脚本,当源集群分片节点过多,会开启对应数量的进程,会对运行机器有一定的性能要求。
迁移后的数据检验
redis-full-check
同样是阿里的开源项目, redis-full-check通过全量对比源端和目的端的redis中的数据的方式来进行数据校验,其比较方式通过多轮次比较:每次都会抓取源和目的端的数据进行差异化比较,记录不一致的数据进入下轮对比(记录在sqlite3 db中)。然后通过多伦比较不断收敛,减少因数据增量同步导致的源库和目的库的数据不一致。最后sqlite中存在的数据就是最终的差异结果。
github地址
例如:源redis库是10.1.1.1:1234,目的库是10.2.2.2:5678:
./redis-full-check -s 10.1.1.1:1234 -t 10.2.2.2:5678 -p mock_source_password -a mock_target_password --metric metric --log log --result result
#metric 文件信息
type Metric struct {
DateTime string `json:"datetime"` // 时间 格式: 2018-01-09T15:30:03Z
Timestamp int64 `json:"timestamp"` // unix秒级时间戳
Id string `json:"id"` // run id
CompareTimes int `json:"comparetimes"` // 对比轮次
Db int32 `json:"db"` // db id
DbKeys int64 `json:"dbkeys"` // db里的总key数
Process int64 `json:"process"` // 进度, 百分比
OneCompareFinished bool `json:"has_finished"` // 本次compare是否完成
AllFinished bool `json:"all_finished"` // 全部compare是否完成
KeyScan *CounterStat `json:"key_scan"` // scan key的数量
TotalConflict int64 `json:"total_conflict"` // conflict的总数, 包含key + field
TotalKeyConflict int64 `json:"total_key_conflict"` // key conflict总数
TotalFieldConflict int64 `json:"total_field_conflict"` // field conflict总数
// 以下两个map第一层key是字段类型, 有string, hash, list, set, zset, 第二层key是冲突类型, 有有type(类型冲突) / value(值冲突) / lack source(源端缺失) / lack target(目标端缺失) / equal(无冲突)
KeyMetric map[string]map[string]*CounterStat `json:"key_stat"` // key metric
FieldMetric map[string]map[string]*CounterStat `json:"field_stat"` // field metric
}
type CounterStat struct {
Total int64 `json:"total"` // 总量
Speed int64 `json:"speed"` // 速度
}
redis-rdb-tools
Rdbtools(https://github.com/sripathikrishnan/redis-rdb-tools)是 Redis 的 dump.rdb 文件的解析器。
可以将dump.rdb文件解析成方便比对的文件直接进行文件对比
source_dump.rdb target_dump.rdb
rdb --command diff source_dump.rdb | sort > dump1
rdb --command diff target_dump.rdb | sort > dump2
kdiff3 dump1 dump2 #运行你最喜欢的 diff 程序 或小文件直接vimdiff