From 96f97244282b36efd1228ce132dedd2fb005d7b0 Mon Sep 17 00:00:00 2001 From: suxb201 Date: Fri, 25 Aug 2023 17:07:07 +0800 Subject: [PATCH] feature: Support AWS sync mode --- README.md | 2 +- cmd/redis-shake/main.go | 2 +- docs/src/zh/function/best_practices.md | 2 +- docs/src/zh/function/introduction.md | 2 +- docs/src/zh/guide/mode.md | 48 ++++++++++++++++++++++- internal/config/config.go | 13 +++++- internal/log/init.go | 5 +-- internal/reader/sync_standalone_reader.go | 2 +- shake.toml | 2 +- 9 files changed, 65 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 9bd3440..08cb390 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # RedisShake: Redis Data Processing & Migration Tool -[![CI](https://github.com/tair-opensource/RedisShake/actions/workflows/ci.yml/badge.svg?event=push?branch=v4)](https://github.com/tair-opensource/RedisShake/actions/workflows/ci.yml) +[![CI](https://github.com/tair-opensource/RedisShake/actions/workflows/ci.yml/badge.svg?event=push&branch=v4)](https://github.com/tair-opensource/RedisShake/actions/workflows/ci.yml) [![CI](https://github.com/tair-opensource/RedisShake/actions/workflows/pages.yml/badge.svg?branch=v4)](https://github.com/tair-opensource/RedisShake/actions/workflows/pages.yml) [![CI](https://github.com/tair-opensource/RedisShake/actions/workflows/release.yml/badge.svg?branch=v4)](https://github.com/tair-opensource/RedisShake/actions/workflows/release.yml) diff --git a/cmd/redis-shake/main.go b/cmd/redis-shake/main.go index 2aea899..106d044 100644 --- a/cmd/redis-shake/main.go +++ b/cmd/redis-shake/main.go @@ -15,7 +15,7 @@ import ( func main() { v := config.LoadConfig() - log.Init(config.Opt.Advanced.LogLevel, config.Opt.Advanced.LogFile) + log.Init(config.Opt.Advanced.LogLevel, config.Opt.Advanced.LogFile, config.Opt.Advanced.Dir) utils.ChdirAndAcquireFileLock() utils.SetNcpu() utils.SetPprofPort() diff --git a/docs/src/zh/function/best_practices.md b/docs/src/zh/function/best_practices.md index 46fe000..9b69f62 100644 --- a/docs/src/zh/function/best_practices.md +++ b/docs/src/zh/function/best_practices.md @@ -4,4 +4,4 @@ outline: deep # 最佳实践 - +TODO diff --git a/docs/src/zh/function/introduction.md b/docs/src/zh/function/introduction.md index 168fcc1..50e0f7d 100644 --- a/docs/src/zh/function/introduction.md +++ b/docs/src/zh/function/introduction.md @@ -4,5 +4,5 @@ outline: deep # 什么是 function - +TODO diff --git a/docs/src/zh/guide/mode.md b/docs/src/zh/guide/mode.md index 1cc2282..7fcba0f 100644 --- a/docs/src/zh/guide/mode.md +++ b/docs/src/zh/guide/mode.md @@ -6,6 +6,50 @@ outline: deep 目前 RedisShake 有三种迁移模式:`PSync`、`RDB` 和 `SCAN`,分别对应 [`sync_reader`](../reader/sync_reader.md)、[`rdb_reader`](../reader/rdb_reader.md) -和 [`scan_reader`](../reader/scan_reader.md)。这三种模式各有适合的场景,本文根据场景特点介绍如何选择。 +和 [`scan_reader`](../reader/scan_reader.md)。 + +对于从备份中恢复数据的场景,可以使用 `rdb_reader`。 + +对于数据迁移场景,优先选择 `sync_reader`。一些云厂商没有提供 PSync 协议支持,可以选择`scan_reader`。 + +对于长期的数据同步场景,RedisShake 目前没有能力承接,因为 PSync 协议并不可靠,当复制连接断开时,RedisShake 将无法重新连接至源端数据库。如果对于可用性要求不高,可以使用 `scan_reader`。如果写入量不大,且不存在大 key,也可以考虑 `scan_reader`。 + +不同模式各有优缺点,需要查看各 Reader 章节了解更多信息。 + +## 云 Redis 服务 + +主流云厂商都提供了 Redis 服务,不过有几个原因导致在这些服务上使用 RedisShake 较为复杂: +1. 引擎限制。存在一些自研的 Redis-like 数据库没有兼容 PSync 协议。 +2. 架构限制。较多云厂商支持代理模式,即在用户与 Redis 服务之间增加 Proxy 组件。因为 Proxy 组件的存在,所以 PSync 协议无法支持。 +3. 安全限制。在原生 Redis 中 PSync 协议基本会触发 fork(2),会导致内存膨胀与用户请求延迟增加,较坏情况下甚至会发生 out of memory。尽管这些都有方案缓解,但并不是所有云厂商都有这方面的投入。 +4. 商业策略。较多用户使用 RedisShake 是为了下云或者换云,所以部分云厂商并不希望用户使用 RedisShake,从而屏蔽了 PSync 协议。 + +下文会结合实践经验,介绍一些特殊场景下的 RedisShake 使用方案。 + +### 阿里云 Redis 与 Tair + +阿里云 Redis 与 Tair 都支持 PSync 协议,推荐使用 `sync_reader`。用户需要创建一个具有复制权限的账号,RedisShake 可以使用该账号进行数据同步,具体创建步骤见 [创建与管理账号](https://help.aliyun.com/zh/redis/user-guide/create-and-manage-database-accounts)。 + +例外情况: +1. 2.8 版本的 Redis 实例不支持创建复制权限的账号,需要 [升级大版本](https://help.aliyun.com/zh/redis/user-guide/upgrade-the-major-version-1)。 +2. 集群架构的 Reids 与 Tair 实例在 [代理模式](https://help.aliyun.com/zh/redis/product-overview/cluster-master-replica-instances#section-h69-izd-531) 下不支持 PSync 协议。 +3. 读写分离架构不支持 PSync 协议。 + +在不支持 PSync 协议的场景下,可以使用 `scan_reader`。需要注意的是,`scan_reader` 会对源库造成较大的压力。 + +### AWS ElastiCache and MemoryDB + +优选 `sync_reader`, AWS ElastiCache and MemoryDB 默认情况下没有开启 PSync 协议,但是可以通过提交工单的方式请求开启 PSync 协议。AWS 会在工单中给出一份重命名的 PSync 命令,比如 `xhma21yfkssync` 和 `nmfu2bl5osync`。此命令效果等同于 `psync` 命令,只是名字不一样。 +用户修改 RedisShake 配置文件中的 `aws_psync` 配置项即可。对于单实例只写一对 `ip:port@cmd` 即可,对于集群实例,需要写上所有的 `ip:port@cmd`,以逗号分隔。 + +不方便提交工单时,可以使用 `scan_reader`。需要注意的是,`scan_reader` 会对源库造成较大的压力。 + +## 自建 Redis 或 Redis-like 数据库 + +## Redis Sentinel 架构 + +当 Redis 以 sentinel 架构部署时,RedisShake 通过 PSync 协议连接主库会被认为是 slave,从而有可能被 sentinel 选举为新的 master。为了避免这种情况,应选择备库作为源端。 + + + -TODO diff --git a/internal/config/config.go b/internal/config/config.go index 09205bd..1055462 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,11 +1,13 @@ package config import ( + "RedisShake/internal/log" "fmt" "github.com/mcuadros/go-defaults" "github.com/rs/zerolog" "github.com/spf13/viper" "os" + "strings" ) type AdvancedOptions struct { @@ -34,11 +36,18 @@ type AdvancedOptions struct { TargetRedisClientMaxQuerybufLen int64 `mapstructure:"target_redis_client_max_querybuf_len" default:"1024000000"` TargetRedisProtoMaxBulkLen uint64 `mapstructure:"target_redis_proto_max_bulk_len" default:"512000000"` - AwsPSync string `mapstructure:"aws_psync" default:""` // "ip:port@xxxpsync;ip:port@xxxpsync" + AwsPSync string `mapstructure:"aws_psync" default:""` // 10.0.0.1:6379@nmfu2sl5osync,10.0.0.1:6379@xhma21xfkssync } func (opt *AdvancedOptions) GetPSyncCommand(address string) string { - return fmt.Sprintf("psync %s 1 0", address) + items := strings.Split(opt.AwsPSync, ",") + for _, item := range items { + if strings.HasPrefix(item, address) { + return strings.Split(item, "@")[1] + } + } + log.Panicf("can not find aws psync command. address=[%s],aws_psync=[%s]", address, opt.AwsPSync) + return "" } type ShakeOptions struct { diff --git a/internal/log/init.go b/internal/log/init.go index 0274946..0f7d530 100644 --- a/internal/log/init.go +++ b/internal/log/init.go @@ -1,7 +1,6 @@ package log import ( - "RedisShake/internal/config" "fmt" "github.com/rs/zerolog" "os" @@ -10,7 +9,7 @@ import ( var logger zerolog.Logger -func Init(level string, file string) { +func Init(level string, file string, dir string) { // log level switch level { case "debug": @@ -24,7 +23,7 @@ func Init(level string, file string) { } // dir - dir, err := filepath.Abs(config.Opt.Advanced.Dir) + dir, err := filepath.Abs(dir) if err != nil { panic(fmt.Sprintf("failed to determine current directory: %v", err)) } diff --git a/internal/reader/sync_standalone_reader.go b/internal/reader/sync_standalone_reader.go index 7931e60..bde9a7b 100644 --- a/internal/reader/sync_standalone_reader.go +++ b/internal/reader/sync_standalone_reader.go @@ -116,7 +116,7 @@ func (r *syncStandaloneReader) sendPSync() { // send PSync argv := []string{"PSYNC", "?", "-1"} if config.Opt.Advanced.AwsPSync != "" { - argv = []string{config.Opt.Advanced.AwsPSync, "?", "-1"} // TODO AWS PSYNC + argv = []string{config.Opt.Advanced.GetPSyncCommand(r.stat.Address), "?", "-1"} } r.client.Send(argv...) diff --git a/shake.toml b/shake.toml index 2b0afb8..3a15fcf 100644 --- a/shake.toml +++ b/shake.toml @@ -59,4 +59,4 @@ target_redis_client_max_querybuf_len = 1024_000_000 target_redis_proto_max_bulk_len = 512_000_000 # If the source is Elasticache or MemoryDB, you can set this item. -aws_psync = "" +aws_psync = "" # example: aws_psync = "10.0.0.1:6379@nmfu2sl5osync,10.0.0.1:6379@xhma21xfkssync"