2023-10-27 17:41:45 +08:00
|
|
|
|
package rdm
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
machineapp "mayfly-go/internal/machine/application"
|
|
|
|
|
|
"mayfly-go/pkg/errorx"
|
|
|
|
|
|
"mayfly-go/pkg/logx"
|
|
|
|
|
|
"mayfly-go/pkg/utils/netx"
|
|
|
|
|
|
"net"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
type RedisMode string
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
|
StandaloneMode RedisMode = "standalone"
|
|
|
|
|
|
ClusterMode RedisMode = "cluster"
|
|
|
|
|
|
SentinelMode RedisMode = "sentinel"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
type RedisInfo struct {
|
|
|
|
|
|
Id uint64 `json:"id"`
|
|
|
|
|
|
|
2024-10-16 17:24:50 +08:00
|
|
|
|
Host string `json:"host"`
|
|
|
|
|
|
Db int `json:"db"` // 库号
|
|
|
|
|
|
Mode RedisMode `json:"mode"`
|
|
|
|
|
|
Username string `json:"-"`
|
|
|
|
|
|
Password string `json:"-"`
|
|
|
|
|
|
RedisNodePassword string `json:"-"`
|
2023-10-27 17:41:45 +08:00
|
|
|
|
|
2023-12-05 23:03:51 +08:00
|
|
|
|
Name string `json:"-"`
|
2024-05-08 21:04:25 +08:00
|
|
|
|
CodePath []string `json:"codePath"`
|
2023-12-05 23:03:51 +08:00
|
|
|
|
SshTunnelMachineId int `json:"-"`
|
2023-10-27 17:41:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (r *RedisInfo) Conn() (*RedisConn, error) {
|
|
|
|
|
|
redisMode := r.Mode
|
|
|
|
|
|
if redisMode == StandaloneMode {
|
|
|
|
|
|
return r.connStandalone()
|
|
|
|
|
|
}
|
|
|
|
|
|
if redisMode == ClusterMode {
|
|
|
|
|
|
return r.connCluster()
|
|
|
|
|
|
}
|
|
|
|
|
|
if redisMode == SentinelMode {
|
|
|
|
|
|
return r.connSentinel()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil, errorx.NewBiz("redis mode error")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (re *RedisInfo) connStandalone() (*RedisConn, error) {
|
|
|
|
|
|
redisOptions := &redis.Options{
|
|
|
|
|
|
Addr: re.Host,
|
|
|
|
|
|
Username: re.Username,
|
|
|
|
|
|
Password: re.Password, // no password set
|
|
|
|
|
|
DB: re.Db, // use default DB
|
|
|
|
|
|
DialTimeout: 8 * time.Second,
|
|
|
|
|
|
ReadTimeout: -1, // Disable timeouts, because SSH does not support deadlines.
|
|
|
|
|
|
WriteTimeout: -1,
|
|
|
|
|
|
}
|
|
|
|
|
|
if re.SshTunnelMachineId > 0 {
|
|
|
|
|
|
redisOptions.Dialer = getRedisDialer(re.SshTunnelMachineId)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
cli := redis.NewClient(redisOptions)
|
|
|
|
|
|
_, e := cli.Ping(context.Background()).Result()
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
cli.Close()
|
2025-10-18 11:21:33 +08:00
|
|
|
|
return nil, errorx.NewBizf("redis standalone connection failed: %s", e.Error())
|
2023-10-27 17:41:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-20 22:43:53 +08:00
|
|
|
|
logx.Infof("redis standalone connection: %s/%d", re.Host, re.Db)
|
2023-10-27 17:41:45 +08:00
|
|
|
|
|
|
|
|
|
|
rc := &RedisConn{Id: getConnId(re.Id, re.Db), Info: re}
|
|
|
|
|
|
rc.Cli = cli
|
|
|
|
|
|
return rc, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (re *RedisInfo) connCluster() (*RedisConn, error) {
|
|
|
|
|
|
redisClusterOptions := &redis.ClusterOptions{
|
|
|
|
|
|
Addrs: strings.Split(re.Host, ","),
|
|
|
|
|
|
Username: re.Username,
|
|
|
|
|
|
Password: re.Password,
|
|
|
|
|
|
DialTimeout: 8 * time.Second,
|
|
|
|
|
|
}
|
|
|
|
|
|
if re.SshTunnelMachineId > 0 {
|
|
|
|
|
|
redisClusterOptions.Dialer = getRedisDialer(re.SshTunnelMachineId)
|
|
|
|
|
|
}
|
|
|
|
|
|
cli := redis.NewClusterClient(redisClusterOptions)
|
|
|
|
|
|
// 测试连接
|
|
|
|
|
|
_, e := cli.Ping(context.Background()).Result()
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
cli.Close()
|
2025-10-18 11:21:33 +08:00
|
|
|
|
return nil, errorx.NewBizf("redis cluster connection failed: %s", e.Error())
|
2023-10-27 17:41:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-20 22:43:53 +08:00
|
|
|
|
logx.Infof("redis cluster connection: %s/%d", re.Host, re.Db)
|
2023-10-27 17:41:45 +08:00
|
|
|
|
|
|
|
|
|
|
rc := &RedisConn{Id: getConnId(re.Id, re.Db), Info: re}
|
|
|
|
|
|
rc.ClusterCli = cli
|
|
|
|
|
|
return rc, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (re *RedisInfo) connSentinel() (*RedisConn, error) {
|
|
|
|
|
|
// sentinel模式host为 masterName=host:port,host:port
|
|
|
|
|
|
masterNameAndHosts := strings.Split(re.Host, "=")
|
|
|
|
|
|
sentinelOptions := &redis.FailoverOptions{
|
|
|
|
|
|
MasterName: masterNameAndHosts[0],
|
|
|
|
|
|
SentinelAddrs: strings.Split(masterNameAndHosts[1], ","),
|
|
|
|
|
|
Username: re.Username,
|
2024-10-16 17:24:50 +08:00
|
|
|
|
Password: re.RedisNodePassword, // no password set
|
2024-07-19 17:06:11 +08:00
|
|
|
|
SentinelUsername: re.Username,
|
2024-10-16 17:24:50 +08:00
|
|
|
|
SentinelPassword: re.Password, // 哨兵节点密码
|
2023-10-27 17:41:45 +08:00
|
|
|
|
DB: re.Db, // use default DB
|
|
|
|
|
|
DialTimeout: 8 * time.Second,
|
|
|
|
|
|
ReadTimeout: -1, // Disable timeouts, because SSH does not support deadlines.
|
|
|
|
|
|
WriteTimeout: -1,
|
|
|
|
|
|
}
|
|
|
|
|
|
if re.SshTunnelMachineId > 0 {
|
|
|
|
|
|
sentinelOptions.Dialer = getRedisDialer(re.SshTunnelMachineId)
|
|
|
|
|
|
}
|
|
|
|
|
|
cli := redis.NewFailoverClient(sentinelOptions)
|
|
|
|
|
|
|
|
|
|
|
|
_, e := cli.Ping(context.Background()).Result()
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
cli.Close()
|
2025-10-18 11:21:33 +08:00
|
|
|
|
return nil, errorx.NewBizf("redis sentinel connection failed: %s", e.Error())
|
2023-10-27 17:41:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-20 22:43:53 +08:00
|
|
|
|
logx.Infof("redis sentinel connection: %s/%d", re.Host, re.Db)
|
2023-10-27 17:41:45 +08:00
|
|
|
|
|
|
|
|
|
|
rc := &RedisConn{Id: getConnId(re.Id, re.Db), Info: re}
|
|
|
|
|
|
rc.Cli = cli
|
|
|
|
|
|
return rc, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func getRedisDialer(machineId int) func(ctx context.Context, network, addr string) (net.Conn, error) {
|
2025-05-22 23:29:50 +08:00
|
|
|
|
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
|
|
|
|
sshTunnel, err := machineapp.GetMachineApp().GetSshTunnelMachine(ctx, machineId)
|
2023-10-27 17:41:45 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if sshConn, err := sshTunnel.GetDialConn(network, addr); err == nil {
|
|
|
|
|
|
// 将ssh conn包装,否则redis内部设置超时会报错,ssh conn不支持设置超时会返回错误: ssh: tcpChan: deadline not supported
|
|
|
|
|
|
return &netx.WrapSshConn{Conn: sshConn}, nil
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成redis连接id
|
|
|
|
|
|
func getConnId(id uint64, db int) string {
|
|
|
|
|
|
if id == 0 {
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
return fmt.Sprintf("%d/%d", id, db)
|
|
|
|
|
|
}
|