从实战出发使用RedisShake进行Redis数据在线+离线模式迁移指南
RedisShake是基于redis-port基础上进行改进的是一款开源的Redis迁移工具,支持Cluster集群的在线迁移与离线迁移(备份文件导入)。数据可平滑迁移,当部署在其他云厂商Redis服务上的Cluster集群数据,由于SYNC、PSYNC命令被云厂商禁用,无法在线迁移时,可以选择离线迁移。
【资料图】
RedisShake是一个用于在两个Redis实例之间同步数据的工具,满足非常灵活的同步与迁移需求。Redis实例之间的关系其中可能存在(standalone->standalone),(standalone->Cluster),(Cluster->Cluster)等。目前,比较常用的一个数据迁移工具是Redis-Shake ,这是阿里云Redis和MongoDB团队开发的一个用于 Redis 数据同步的工具。
RedisShake主要是支持Redis的RDB文件的解析、恢复、备份、同步四个功能:
恢复(restore):将 RDB 文件恢复到目标Redis数据库。备份(dump):将源 Redis 的全量数据通过RDB文件备份起来。解析(decode):读取 RDB 文件,并以 JSON 格式解析存储。同步(sync):支持源redis和目的redis的数据同步,支持全量和增量数据的迁移,支持从云下到阿里云云上的同步,也支持云下到云下不同环境的同步,支持单节点、主从版、集群版之间的互相同步。同步(rump):支持源 Redis 和目的 Redis 的数据同步,仅支持全量迁移。采用scan和restore命令进行迁移,支持不同云厂商不同redis版本的迁移。注意:如果源端是集群版,可以启动一个RedisShake,从不同的db结点进行拉取,同时源端不能开启move slot功能;对于目的端,如果是集群版,写入可以是1个或者多个db结点。
如果源端是集群模式,只需要启动一个redis-shake进行拉取,同时不能开启源端的move slot操作。如果目的端是集群模式,可以写入到一个结点,然后再进行slot的迁移,当然也可以多对多写入。
目前,redis-shake到目的端采用单链路实现,对于正常情况下,这不会成为瓶颈,但对于极端情况,qps比较大的时候,此部分性能可能成为瓶颈,后续我们可能会计划对此进行优化。另外,redis-shake到目的端的数据同步采用异步的方式,读写分离在2个线程操作,降低因为网络时延带来的同步性能下降。
主要有两种方式:下载Release版本的可执行二进制包、下载源码文件进行编译操作这两种方式。
Download from Release
点击下载就可以进行直接使用Redis-Shake服务。
除了直接下载可执行包之外,还可以下载源码之后,可以进行运行build.sh文件执行进行编译源码,生成可执行包。可以根据上面的下载中source code进行下载。
或者可以针对于Git进行clone源码仓库,如下所示。
git clone https://github.com/alibaba/RedisShakecd RedisShakesh build.sh
首先如果需要进行同步和重放,则需要进行编辑sync.toml文件以及编辑restore.toml.
redis-shake 支持三种数据迁移模式:sync、restore 和 scan:快速开始:数据迁移(使用 sync 模式)快速开始:从dump.rdb恢复数据(使用 restore 模式)快速开始:数据迁移(使用 scan 模式)使用 filters 做数据清洗运行日志运行监控当我们编辑sync.toml文件之后,可以进行配置我们实际情况下的source源redis实例以及target目标redis实例。之后可以配置对应的cpu和相关性能的配置。下面针对于配置进行相关的配置介绍
type = "sync" # 同步机制实现[source] # 源Redis服务实例version = 5.0 # 填写Redis源服务版本, 例如:2.8, 4.0, 5.0, 6.0, 6.2, 7.0, ...。address = "127.0.0.1:6379" # 源Redis服务实例 地址+端口username = "" # 如果Redis没有配置ACL,则可以不填写,否则需要填写用户名 password = "" # 如果Redis没有配置ACL,则可以不填写,否则需要填写密码tls = false # 是否开启tls安全机制elasticache_psync = "" # 是否支持AWS的elasticache[target]type = "standalone" # 选择Redis的类型:"standalone:单机模式" or "cluster:集群模式"version = 5.0 # 填写Redis源服务版本, 例如:2.8, 4.0, 5.0, 6.0, 6.2, 7.0, ...。# 如果目标Redis服务实例属于cluster集群模式, 那么可以写入其中一个节点的地址和端口.# redis-shake 会通过`cluster nodes` 命令获取其他的节点地址和端口address = "127.0.0.1:6380" # 填写的对应的ip加端口username = "" # 如果Redis没有配置ACL,则可以不填写,否则需要填写用户名 password = "" # 如果Redis没有配置ACL,则可以不填写,否则需要填写密码tls = false # 是否开启tls安全机制[advanced]dir = "data" # 数据同步的存储目录# 设置使用的最大CPU核心数, 如果设置了0 代表着 使用 runtime.NumCPU() 实际的cpu cores数量ncpu = 4# 开启pprof性能检测的port, 0代表着禁用pprof_port = 0 # 开启metric port端口, 0代表着禁用metrics_port = 0# log的相关设置log_file = "redis-shake.log" # 设置对应的日志文件名称log_level = "info" # debug, info or warn # 设置对应的日志级别log_interval = 5 # in seconds # 日志打印频次# redis-shake gets key and value from rdb file, and uses RESTORE command to# create the key in target redis. Redis RESTORE will return a "Target key name# is busy" error when key already exists. You can use this configuration item# to change the default behavior of restore:# panic: redis-shake will stop when meet "Target key name is busy" error.# rewrite: redis-shake will replace the key with new value.# ignore: redis-shake will skip restore the key when meet "Target key name is busy" error.rdb_restore_command_behavior = "rewrite" # restore的操作类型:panic, rewrite or skip# pipeline的大小数量阈值pipeline_count_limit = 1024# Client query buffers accumulate new commands. They are limited to a fixed# amount by default. This amount is normally 1gb.target_redis_client_max_querybuf_len = 1024_000_000# In the Redis protocol, bulk requests, that are, elements representing single# strings, are normally limited to 512 mb.target_redis_proto_max_bulk_len = 512_000_000
修改sync.toml,改为如下配置。
[source]address = "ip:6379"password = ""[target]type = "standalone"address = "ip:6379"password = "r-bbbbb:xxxxx"
./redis-shake sync.toml
修改 sync.toml,改为如下配置:
[source]address = "r-aaaaa.redis.zhangbei.rds.aliyuncs.com:6379"password = "r-aaaaa:xxxxx"[target]type = "cluster"address = "192.168.0.1:6379" # 这里写集群中的任意一个节点的地址即可password = "r-ccccc:xxxxx"
./redis-shake sync.toml
集群C有四个节点:
192.168.0.1:6379192.168.0.2:6379192.168.0.3:6379192.168.0.4:6379把4个节点当成4个单机实例,参照单机到集群 部署 4 个 redis-shake 进行数据同步。不要在同一个目录启动多个 redis-shake,因为 redis-shake 会在本地存储临时文件,多个 redis-shake 之间的临时文件会干扰,正确做法是建立多个目录。
脚本cluster_helper.py可以方便启动多个redis-shake从集群迁移数据,效果等同于方法1。
源端有多少个分片,cluster_helper.py 就会起多少个 redis-shake 进程,所以如果源端分片数较多的时候,需要评估当前机器是否可以承担这么多进程。
cluster_helper.py 异常退出的时候,可能没有正常退出 redis-shake 进程,需要 ps aux | grep redis-shake 检查。
每个 redis-shake 进程的执行日志记录在 RedisShake/cluster_helper/data/xxxxx 中,反馈问题请提供相关日志。
Python 需要 python3.6 及以上版本,安装 Python依赖:
cd RedisShake/cluster_helperpip3 install -r requirements.txt
修改 sync.toml:
type = "sync"[source]address = "192.168.0.1:6379" # 集群 C 中任意一个节点地址password = "r-ccccc:xxxxx"[target]type = "cluster"address = "192.168.1.1:6380" # 集群 D 中任意一个节点地址password = "r-ddddd:xxxxx"
cd RedisShake/cluster_helperpython3 cluster_helper.py ../redis-shake ../sync.toml参数 1 是 redis-shake 可执行程序的路径参数 2 是配置文件路径
[root@redis ~]# redis-shake ./redis-shake.toml2022-08-26 11:20:28 INF GOOS: linux, GOARCH: amd642022-08-26 11:20:28 INF Ncpu: 3, GOMAXPROCS: 32022-08-26 11:20:28 INF pid: 215042022-08-26 11:20:28 INF pprof_port: 02022-08-26 11:20:28 INF No lua file specified, will not filter any cmd.2022-08-26 11:20:28 INF no password. address=[127.0.0.1:6380]2022-08-26 11:20:28 INF redisWriter connected to redis successful. address=[127.0.0.1:6380]2022-08-26 11:20:28 INF no password. address=[127.0.0.1:6379]2022-08-26 11:20:28 INF psyncReader connected to redis successful. address=[127.0.0.1:6379]2022-08-26 11:20:28 WRN remove file. filename=[4200.aof]2022-08-26 11:20:28 WRN remove file. filename=[dump.rdb]2022-08-26 11:20:28 INF start save RDB. address=[127.0.0.1:6379]2022-08-26 11:20:28 INF send [replconf listening-port 10007]2022-08-26 11:20:28 INF send [PSYNC ? -1]2022-08-26 11:20:28 INF receive [FULLRESYNC 1db7c7618b6d0af25ffafb1645d4fba573624d02 0]2022-08-26 11:20:28 INF source db is doing bgsave. address=[127.0.0.1:6379]2022-08-26 11:20:28 INF source db bgsave finished. timeUsed=[0.09]s, address=[127.0.0.1:6379]2022-08-26 11:20:28 INF received rdb length. length=[194]2022-08-26 11:20:28 INF create dump.rdb file. filename_path=[dump.rdb]2022-08-26 11:20:28 INF save RDB finished. address=[127.0.0.1:6379], total_bytes=[194]2022-08-26 11:20:28 INF start send RDB. address=[127.0.0.1:6379]2022-08-26 11:20:28 INF RDB version: 82022-08-26 11:20:28 INF RDB AUX fields. key=[redis-ver], value=[4.0.14]2022-08-26 11:20:28 INF RDB AUX fields. key=[redis-bits], value=[64]2022-08-26 11:20:28 INF RDB AUX fields. key=[ctime], value=[1661484028]2022-08-26 11:20:28 INF RDB AUX fields. key=[used-mem], value=[1897096]2022-08-26 11:20:28 INF RDB repl-stream-db: 02022-08-26 11:20:28 INF RDB AUX fields. key=[repl-id], value=[1db7c7618b6d0af25ffafb1645d4fba573624d02]2022-08-26 11:20:28 INF RDB AUX fields. key=[repl-offset], value=[0]2022-08-26 11:20:28 INF RDB AUX fields. key=[aof-preamble], value=[0]2022-08-26 11:20:28 INF RDB resize db. db_size=[1], expire_size=[0]2022-08-26 11:20:28 INF send RDB finished. address=[127.0.0.1:6379], repl-stream-db=[0]2022-08-26 11:20:28 INF start save AOF. address=[127.0.0.1:6379]2022-08-26 11:20:28 INF AOFWriter open file. filename=[0.aof]2022-08-26 11:20:29 INF AOFReader open file. aof_filename=[0.aof]2022-08-26 11:20:33 INF syncing aof. allowOps=[0.20], disallowOps=[0.00], entryId=[0], unansweredBytesCount=[0]bytes, diff=[0], aofReceivedOffset=[0], aofAppliedOffset=[0]2022-08-26 11:20:38 INF syncing aof. allowOps=[0.20], disallowOps=[0.00], entryId=[1], unansweredBytesCount=[0]bytes, diff=[0], aofReceivedOffset=[14], aofAppliedOffset=[14]2022-08-26 11:20:43 INF syncing aof. allowOps=[0.00], disallowOps=[0.00], entryId=[1], unansweredBytesCount=[0]bytes, diff=[0], aofReceivedOffset=[14], aofAppliedOffset=[14]2022-08-26 11:20:48 INF syncing aof. allowOps=[0.20], disallowOps=[0.00], entryId=[2], unansweredBytesCount=[0]bytes, diff=[0], aofReceivedOffset=[28], aofAppliedOffset=[28]