Skip to content

Commit

Permalink
feat: improve sentinel support (#902)
Browse files Browse the repository at this point in the history
  • Loading branch information
suxb201 authored Dec 12, 2024
1 parent 485fd11 commit 7675749
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 78 deletions.
49 changes: 40 additions & 9 deletions cmd/redis-shake/main.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package main

import (
"RedisShake/internal/client"
"context"
_ "net/http/pprof"
"os"
"os/signal"
"strings"
"sync/atomic"
"syscall"
"time"
Expand Down Expand Up @@ -66,11 +68,23 @@ func main() {
log.Panicf("failed to read the SyncReader config entry. err: %v", err)
}
if opts.Cluster {
log.Infof("create SyncClusterReader")
log.Infof("* address (should be the address of one node in the Redis cluster): %s", opts.Address)
log.Infof("* username: %s", opts.Username)
log.Infof("* password: %s", strings.Repeat("*", len(opts.Password)))
log.Infof("* tls: %v", opts.Tls)
theReader = reader.NewSyncClusterReader(ctx, opts)
log.Infof("create SyncClusterReader: %v", opts.Address)
} else {
if opts.Sentinel.Address != "" {
address := client.FetchAddressFromSentinel(&opts.Sentinel)
opts.Address = address
}
log.Infof("create SyncStandaloneReader")
log.Infof("* address: %s", opts.Address)
log.Infof("* username: %s", opts.Username)
log.Infof("* password: %s", strings.Repeat("*", len(opts.Password)))
log.Infof("* tls: %v", opts.Tls)
theReader = reader.NewSyncStandaloneReader(ctx, opts)
log.Infof("create SyncStandaloneReader: %v", opts.Address)
}
case v.IsSet("scan_reader"):
opts := new(reader.ScanReaderOptions)
Expand All @@ -80,11 +94,19 @@ func main() {
log.Panicf("failed to read the ScanReader config entry. err: %v", err)
}
if opts.Cluster {
log.Infof("create ScanClusterReader")
log.Infof("* address (should be the address of one node in the Redis cluster): %s", opts.Address)
log.Infof("* username: %s", opts.Username)
log.Infof("* password: %s", strings.Repeat("*", len(opts.Password)))
log.Infof("* tls: %v", opts.Tls)
theReader = reader.NewScanClusterReader(ctx, opts)
log.Infof("create ScanClusterReader: %v", opts.Address)
} else {
log.Infof("create ScanStandaloneReader")
log.Infof("* address: %s", opts.Address)
log.Infof("* username: %s", opts.Username)
log.Infof("* password: %s", strings.Repeat("*", len(opts.Password)))
log.Infof("* tls: %v", opts.Tls)
theReader = reader.NewScanStandaloneReader(ctx, opts)
log.Infof("create ScanStandaloneReader: %v", opts.Address)
}
case v.IsSet("rdb_reader"):
opts := new(reader.RdbReaderOptions)
Expand Down Expand Up @@ -121,14 +143,23 @@ func main() {
log.Panicf("the RDBRestoreCommandBehavior can't be 'panic' when the server not reply to commands")
}
if opts.Cluster {
log.Infof("create RedisClusterWriter")
log.Infof("* address (should be the address of one node in the Redis cluster): %s", opts.Address)
log.Infof("* username: %s", opts.Username)
log.Infof("* password: %s", strings.Repeat("*", len(opts.Password)))
log.Infof("* tls: %v", opts.Tls)
theWriter = writer.NewRedisClusterWriter(ctx, opts)
log.Infof("create RedisClusterWriter: %v", opts.Address)
} else if opts.Sentinel {
theWriter = writer.NewRedisSentinelWriter(ctx, opts)
log.Infof("create RedisSentinelWriter: %v", opts.Address)
} else {
if opts.Sentinel.Address != "" {
address := client.FetchAddressFromSentinel(&opts.Sentinel)
opts.Address = address
}
log.Infof("create RedisStandaloneWriter")
log.Infof("* address: %s", opts.Address)
log.Infof("* username: %s", opts.Username)
log.Infof("* password: %s", strings.Repeat("*", len(opts.Password)))
log.Infof("* tls: %v", opts.Tls)
theWriter = writer.NewRedisStandaloneWriter(ctx, opts)
log.Infof("create RedisStandaloneWriter: %v", opts.Address)
}
if config.Opt.Advanced.EmptyDBBeforeSync {
// exec FLUSHALL command to flush db
Expand Down
35 changes: 33 additions & 2 deletions docs/src/en/guide/mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,40 @@ When the source Redis is deployed in a cluster architecture, you can use `sync_r

## Redis Sentinel Architecture

When the source Redis is deployed in a sentinel architecture and RedisShake uses `sync_reader` to connect to the master, it will be treated as a slave by the master and may be elected as the new master by the sentinel.
1. Typically, you can ignore the Sentinel component and directly write the Redis connection information into the RedisShake configuration file.
::: warning
Note that when using `sync_reader` to connect to a Redis Master node managed by Sentinel, RedisShake will be treated as a Slave node by Sentinel, which may cause unexpected issues. Therefore, in such scenarios, it is recommended to choose a replica as the source.
:::
2. If it is not convenient to directly obtain the Redis connection information, you can configure the Sentinel information in the RedisShake configuration file. RedisShake will automatically obtain the master node address from Sentinel. Configuration reference:
```toml
[sync_reader]
cluster = false
address = "" # The source Redis address will be obtained from Sentinel
username = ""
password = "redis6380password"
tls = false
[sync_reader.sentinel]
master_name = "mymaster"
address = "127.0.0.1:26380"
username = ""
password = ""
tls = false

[redis_writer]
cluster = false
address = "" # The target Redis address will be obtained from Sentinel
username = ""
password = "redis6381password"
tls = false
[redis_writer.sentinel]
master_name = "mymaster1"
address = "127.0.0.1:26380"
username = ""
password = ""
tls = false

```

To avoid this situation, you should choose a replica as the source.

## Cloud Redis Services

Expand Down
37 changes: 34 additions & 3 deletions docs/src/zh/guide/mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,40 @@ outline: deep

## Redis Sentinel 架构

当源端 Redis 以 sentinel 架构部署且 RedisShake 使用 `sync_reader` 连接主库时,会被主库当做 slave,从而有可能被 sentinel 选举为新的 master。

为了避免这种情况,应选择备库作为源端。
1. 通常情况下,忽略 Sentinel 组件,直接将 Redis 的连接信息写入 RedisShake 配置文件即可。
::: warning
需要注意的是使用 `sync_reader` 连接被 Sentinel 接管的 Redis Master 节点时,RedisShake 会被 Sentinel 当做 Slave 节点,从而引发非预期内问题。
所以此类场景应尽量选择备库作为源端。
:::
2. 如果不方便直接获取 Redis 的连接信息([#888](https://github.com/tair-opensource/RedisShake/pull/888#issuecomment-2513984861)),可以将 Sentinel 的信息配置在 RedisShake 配置文件中,RedisShake 会自动从 Sentinel 中获取主节点地址。配置参考:
```toml
[sync_reader]
cluster = false
address = "" # 源端 Redis 的地址会从 Sentinel 中获取
username = ""
password = "redis6380password"
tls = false
[sync_reader.sentinel]
master_name = "mymaster"
address = "127.0.0.1:26380"
username = ""
password = ""
tls = false

[redis_writer]
cluster = false
address = "" # 目标端 Redis 的地址会从 Sentinel 中获取
username = ""
password = "redis6381password"
tls = false
[redis_writer.sentinel]
master_name = "mymaster1"
address = "127.0.0.1:26380"
username = ""
password = ""
tls = false

```

## 云 Redis 服务

Expand Down
28 changes: 28 additions & 0 deletions internal/client/sentinel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package client

import (
"RedisShake/internal/log"
"context"
"fmt"
)

type SentinelOptions struct {
MasterName string `mapstructure:"master_name" default:""`
Address string `mapstructure:"address" default:""`
Username string `mapstructure:"username" default:""`
Password string `mapstructure:"password" default:""`
Tls bool `mapstructure:"tls" default:"false"`
}

func FetchAddressFromSentinel(opts *SentinelOptions) string {
log.Infof("fetching master address from sentinel. sentinel address: %s, master name: %s", opts.Address, opts.MasterName)

ctx := context.Background()
c := NewRedisClient(ctx, opts.Address, opts.Username, opts.Password, opts.Tls, false)
defer c.Close()
c.Send("SENTINEL", "GET-MASTER-ADDR-BY-NAME", opts.MasterName)
hostport := ArrayString(c.Receive())
address := fmt.Sprintf("%s:%s", hostport[0], hostport[1])
log.Infof("fetched master address: %s", address)
return address
}
19 changes: 10 additions & 9 deletions internal/reader/sync_standalone_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,16 @@ import (
)

type SyncReaderOptions struct {
Cluster bool `mapstructure:"cluster" default:"false"`
Address string `mapstructure:"address" default:""`
Username string `mapstructure:"username" default:""`
Password string `mapstructure:"password" default:""`
Tls bool `mapstructure:"tls" default:"false"`
SyncRdb bool `mapstructure:"sync_rdb" default:"true"`
SyncAof bool `mapstructure:"sync_aof" default:"true"`
PreferReplica bool `mapstructure:"prefer_replica" default:"false"`
TryDiskless bool `mapstructure:"try_diskless" default:"false"`
Cluster bool `mapstructure:"cluster" default:"false"`
Address string `mapstructure:"address" default:""`
Username string `mapstructure:"username" default:""`
Password string `mapstructure:"password" default:""`
Tls bool `mapstructure:"tls" default:"false"`
SyncRdb bool `mapstructure:"sync_rdb" default:"true"`
SyncAof bool `mapstructure:"sync_aof" default:"true"`
PreferReplica bool `mapstructure:"prefer_replica" default:"false"`
TryDiskless bool `mapstructure:"try_diskless" default:"false"`
Sentinel client.SentinelOptions `mapstructure:"sentinel"`
}

type State string
Expand Down
31 changes: 0 additions & 31 deletions internal/writer/redis_sentinel_writer.go

This file was deleted.

19 changes: 8 additions & 11 deletions internal/writer/redis_standalone_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,14 @@ import (
)

type RedisWriterOptions struct {
Cluster bool `mapstructure:"cluster" default:"false"`
Sentinel bool `mapstructure:"sentinel" default:"false"`
Master string `mapstructure:"master" default:""`
Address string `mapstructure:"address" default:""`
Username string `mapstructure:"username" default:""`
Password string `mapstructure:"password" default:""`
SentinelUsername string `mapstructure:"sentinel_username" default:""`
SentinelPassword string `mapstructure:"sentinel_password" default:""`
Tls bool `mapstructure:"tls" default:"false"`
OffReply bool `mapstructure:"off_reply" default:"false"`
BuffSend bool `mapstructure:"buff_send" default:"false"`
Cluster bool `mapstructure:"cluster" default:"false"`
Address string `mapstructure:"address" default:""`
Username string `mapstructure:"username" default:""`
Password string `mapstructure:"password" default:""`
Tls bool `mapstructure:"tls" default:"false"`
OffReply bool `mapstructure:"off_reply" default:"false"`
BuffSend bool `mapstructure:"buff_send" default:"false"`
Sentinel client.SentinelOptions `mapstructure:"sentinel"`
}

type redisStandaloneWriter struct {
Expand Down
22 changes: 9 additions & 13 deletions shake.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[sync_reader]
cluster = false # set to true if source is a redis cluster
address = "127.0.0.1:6379" # when cluster is true, set address to one of the cluster node
username = "" # keep empty if not using ACL
password = "" # keep empty if no authentication is required
tls = false #
sync_rdb = true # set to false if you don't want to sync rdb
sync_aof = true # set to false if you don't want to sync aof
prefer_replica = false # set to true if you want to sync from replica node
try_diskless = false # set to true if you want to sync by socket and source repl-diskless-sync=yes
cluster = false # Set to true if the source is a Redis cluster
address = "127.0.0.1:6379" # For clusters, specify the address of any cluster node; use the master or slave address in master-slave mode
username = "" # Keep empty if ACL is not in use
password = "" # Keep empty if no authentication is required
tls = false # Set to true to enable TLS if needed
sync_rdb = true # Set to false if RDB synchronization is not required
sync_aof = true # Set to false if AOF synchronization is not required
prefer_replica = false # Set to true to sync from a replica node
try_diskless = false # Set to true for diskless sync if the source has repl-diskless-sync=yes

#[scan_reader]
#cluster = false # set to true if source is a redis cluster
Expand All @@ -29,13 +29,9 @@ try_diskless = false # set to true if you want to sync by socket and sourc

[redis_writer]
cluster = false # set to true if target is a redis cluster
sentinel = false # set to true if target is a redis sentinel
master = "" # set to master name if target is a redis sentinel
address = "127.0.0.1:6380" # when cluster is true, set address to one of the cluster node
username = "" # keep empty if not using ACL
password = "" # keep empty if no authentication is required
sentinel_username = "" # keep empty if not using sentinel ACL
sentinel_password = "" # keep empty if sentinel no authentication is required
tls = false
off_reply = false # turn off the server reply
buff_send = false # buffer send, default false. may be a sync delay when true, but it can greatly improve the speed
Expand Down

0 comments on commit 7675749

Please sign in to comment.