forked from ebhomengo/niki
1
0
Fork 0
niki/vendor/github.com/redis/go-redis/v9/sentinel.go

1359 lines
22 KiB
Go
Raw Normal View History

2024-02-18 10:42:21 +00:00
package redis
import (
"context"
"crypto/tls"
"errors"
"net"
"strings"
"sync"
"time"
"github.com/redis/go-redis/v9/internal"
"github.com/redis/go-redis/v9/internal/pool"
"github.com/redis/go-redis/v9/internal/rand"
)
//------------------------------------------------------------------------------
// FailoverOptions are used to configure a failover client and should
2024-02-18 10:42:21 +00:00
// be passed to NewFailoverClient.
2024-02-18 10:42:21 +00:00
type FailoverOptions struct {
2024-02-18 10:42:21 +00:00
// The master name.
2024-02-18 10:42:21 +00:00
MasterName string
2024-02-18 10:42:21 +00:00
// A seed list of host:port addresses of sentinel nodes.
2024-02-18 10:42:21 +00:00
SentinelAddrs []string
// ClientName will execute the `CLIENT SETNAME ClientName` command for each conn.
2024-02-18 10:42:21 +00:00
ClientName string
// If specified with SentinelPassword, enables ACL-based authentication (via
2024-02-18 10:42:21 +00:00
// AUTH <user> <pass>).
2024-02-18 10:42:21 +00:00
SentinelUsername string
2024-02-18 10:42:21 +00:00
// Sentinel password from "requirepass <password>" (if enabled) in Sentinel
2024-02-18 10:42:21 +00:00
// configuration, or, if SentinelUsername is also supplied, used for ACL-based
2024-02-18 10:42:21 +00:00
// authentication.
2024-02-18 10:42:21 +00:00
SentinelPassword string
// Allows routing read-only commands to the closest master or replica node.
2024-02-18 10:42:21 +00:00
// This option only works with NewFailoverClusterClient.
2024-02-18 10:42:21 +00:00
RouteByLatency bool
2024-02-18 10:42:21 +00:00
// Allows routing read-only commands to the random master or replica node.
2024-02-18 10:42:21 +00:00
// This option only works with NewFailoverClusterClient.
2024-02-18 10:42:21 +00:00
RouteRandomly bool
// Route all commands to replica read-only nodes.
2024-02-18 10:42:21 +00:00
ReplicaOnly bool
// Use replicas disconnected with master when cannot get connected replicas
2024-02-18 10:42:21 +00:00
// Now, this option only works in RandomReplicaAddr function.
2024-02-18 10:42:21 +00:00
UseDisconnectedReplicas bool
// Following options are copied from Options struct.
Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
2024-02-18 10:42:21 +00:00
OnConnect func(ctx context.Context, cn *Conn) error
Protocol int
2024-02-18 10:42:21 +00:00
Username string
2024-02-18 10:42:21 +00:00
Password string
DB int
MaxRetries int
2024-02-18 10:42:21 +00:00
MinRetryBackoff time.Duration
2024-02-18 10:42:21 +00:00
MaxRetryBackoff time.Duration
DialTimeout time.Duration
ReadTimeout time.Duration
WriteTimeout time.Duration
2024-02-18 10:42:21 +00:00
ContextTimeoutEnabled bool
PoolFIFO bool
PoolSize int
PoolTimeout time.Duration
MinIdleConns int
MaxIdleConns int
MaxActiveConns int
2024-02-18 10:42:21 +00:00
ConnMaxIdleTime time.Duration
2024-02-18 10:42:21 +00:00
ConnMaxLifetime time.Duration
TLSConfig *tls.Config
DisableIndentity bool
IdentitySuffix string
2024-02-18 10:42:21 +00:00
}
func (opt *FailoverOptions) clientOptions() *Options {
2024-02-18 10:42:21 +00:00
return &Options{
Addr: "FailoverClient",
2024-02-18 10:42:21 +00:00
ClientName: opt.ClientName,
Dialer: opt.Dialer,
2024-02-18 10:42:21 +00:00
OnConnect: opt.OnConnect,
DB: opt.DB,
2024-02-18 10:42:21 +00:00
Protocol: opt.Protocol,
2024-02-18 10:42:21 +00:00
Username: opt.Username,
2024-02-18 10:42:21 +00:00
Password: opt.Password,
MaxRetries: opt.MaxRetries,
2024-02-18 10:42:21 +00:00
MinRetryBackoff: opt.MinRetryBackoff,
2024-02-18 10:42:21 +00:00
MaxRetryBackoff: opt.MaxRetryBackoff,
DialTimeout: opt.DialTimeout,
ReadTimeout: opt.ReadTimeout,
WriteTimeout: opt.WriteTimeout,
2024-02-18 10:42:21 +00:00
ContextTimeoutEnabled: opt.ContextTimeoutEnabled,
PoolFIFO: opt.PoolFIFO,
PoolSize: opt.PoolSize,
PoolTimeout: opt.PoolTimeout,
MinIdleConns: opt.MinIdleConns,
MaxIdleConns: opt.MaxIdleConns,
MaxActiveConns: opt.MaxActiveConns,
2024-02-18 10:42:21 +00:00
ConnMaxIdleTime: opt.ConnMaxIdleTime,
2024-02-18 10:42:21 +00:00
ConnMaxLifetime: opt.ConnMaxLifetime,
TLSConfig: opt.TLSConfig,
DisableIndentity: opt.DisableIndentity,
IdentitySuffix: opt.IdentitySuffix,
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
func (opt *FailoverOptions) sentinelOptions(addr string) *Options {
2024-02-18 10:42:21 +00:00
return &Options{
Addr: addr,
2024-02-18 10:42:21 +00:00
ClientName: opt.ClientName,
Dialer: opt.Dialer,
2024-02-18 10:42:21 +00:00
OnConnect: opt.OnConnect,
DB: 0,
2024-02-18 10:42:21 +00:00
Username: opt.SentinelUsername,
2024-02-18 10:42:21 +00:00
Password: opt.SentinelPassword,
MaxRetries: opt.MaxRetries,
2024-02-18 10:42:21 +00:00
MinRetryBackoff: opt.MinRetryBackoff,
2024-02-18 10:42:21 +00:00
MaxRetryBackoff: opt.MaxRetryBackoff,
DialTimeout: opt.DialTimeout,
ReadTimeout: opt.ReadTimeout,
WriteTimeout: opt.WriteTimeout,
2024-02-18 10:42:21 +00:00
ContextTimeoutEnabled: opt.ContextTimeoutEnabled,
PoolFIFO: opt.PoolFIFO,
PoolSize: opt.PoolSize,
PoolTimeout: opt.PoolTimeout,
MinIdleConns: opt.MinIdleConns,
MaxIdleConns: opt.MaxIdleConns,
MaxActiveConns: opt.MaxActiveConns,
2024-02-18 10:42:21 +00:00
ConnMaxIdleTime: opt.ConnMaxIdleTime,
2024-02-18 10:42:21 +00:00
ConnMaxLifetime: opt.ConnMaxLifetime,
TLSConfig: opt.TLSConfig,
}
2024-02-18 10:42:21 +00:00
}
func (opt *FailoverOptions) clusterOptions() *ClusterOptions {
2024-02-18 10:42:21 +00:00
return &ClusterOptions{
2024-02-18 10:42:21 +00:00
ClientName: opt.ClientName,
Dialer: opt.Dialer,
2024-02-18 10:42:21 +00:00
OnConnect: opt.OnConnect,
Protocol: opt.Protocol,
2024-02-18 10:42:21 +00:00
Username: opt.Username,
2024-02-18 10:42:21 +00:00
Password: opt.Password,
MaxRedirects: opt.MaxRetries,
RouteByLatency: opt.RouteByLatency,
RouteRandomly: opt.RouteRandomly,
2024-02-18 10:42:21 +00:00
MinRetryBackoff: opt.MinRetryBackoff,
2024-02-18 10:42:21 +00:00
MaxRetryBackoff: opt.MaxRetryBackoff,
DialTimeout: opt.DialTimeout,
ReadTimeout: opt.ReadTimeout,
WriteTimeout: opt.WriteTimeout,
2024-02-18 10:42:21 +00:00
ContextTimeoutEnabled: opt.ContextTimeoutEnabled,
PoolFIFO: opt.PoolFIFO,
PoolSize: opt.PoolSize,
PoolTimeout: opt.PoolTimeout,
MinIdleConns: opt.MinIdleConns,
MaxIdleConns: opt.MaxIdleConns,
MaxActiveConns: opt.MaxActiveConns,
2024-02-18 10:42:21 +00:00
ConnMaxIdleTime: opt.ConnMaxIdleTime,
2024-02-18 10:42:21 +00:00
ConnMaxLifetime: opt.ConnMaxLifetime,
TLSConfig: opt.TLSConfig,
}
2024-02-18 10:42:21 +00:00
}
// NewFailoverClient returns a Redis client that uses Redis Sentinel
2024-02-18 10:42:21 +00:00
// for automatic failover. It's safe for concurrent use by multiple
2024-02-18 10:42:21 +00:00
// goroutines.
2024-02-18 10:42:21 +00:00
func NewFailoverClient(failoverOpt *FailoverOptions) *Client {
2024-02-18 10:42:21 +00:00
if failoverOpt.RouteByLatency {
2024-02-18 10:42:21 +00:00
panic("to route commands by latency, use NewFailoverClusterClient")
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
if failoverOpt.RouteRandomly {
2024-02-18 10:42:21 +00:00
panic("to route commands randomly, use NewFailoverClusterClient")
2024-02-18 10:42:21 +00:00
}
sentinelAddrs := make([]string, len(failoverOpt.SentinelAddrs))
2024-02-18 10:42:21 +00:00
copy(sentinelAddrs, failoverOpt.SentinelAddrs)
rand.Shuffle(len(sentinelAddrs), func(i, j int) {
2024-02-18 10:42:21 +00:00
sentinelAddrs[i], sentinelAddrs[j] = sentinelAddrs[j], sentinelAddrs[i]
2024-02-18 10:42:21 +00:00
})
failover := &sentinelFailover{
opt: failoverOpt,
2024-02-18 10:42:21 +00:00
sentinelAddrs: sentinelAddrs,
}
opt := failoverOpt.clientOptions()
2024-02-18 10:42:21 +00:00
opt.Dialer = masterReplicaDialer(failover)
2024-02-18 10:42:21 +00:00
opt.init()
var connPool *pool.ConnPool
rdb := &Client{
2024-02-18 10:42:21 +00:00
baseClient: &baseClient{
2024-02-18 10:42:21 +00:00
opt: opt,
},
}
2024-02-18 10:42:21 +00:00
rdb.init()
connPool = newConnPool(opt, rdb.dialHook)
2024-02-18 10:42:21 +00:00
rdb.connPool = connPool
2024-02-18 10:42:21 +00:00
rdb.onClose = failover.Close
failover.mu.Lock()
2024-02-18 10:42:21 +00:00
failover.onFailover = func(ctx context.Context, addr string) {
2024-02-18 10:42:21 +00:00
_ = connPool.Filter(func(cn *pool.Conn) bool {
2024-02-18 10:42:21 +00:00
return cn.RemoteAddr().String() != addr
2024-02-18 10:42:21 +00:00
})
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
failover.mu.Unlock()
return rdb
2024-02-18 10:42:21 +00:00
}
func masterReplicaDialer(
2024-02-18 10:42:21 +00:00
failover *sentinelFailover,
2024-02-18 10:42:21 +00:00
) func(ctx context.Context, network, addr string) (net.Conn, error) {
2024-02-18 10:42:21 +00:00
return func(ctx context.Context, network, _ string) (net.Conn, error) {
2024-02-18 10:42:21 +00:00
var addr string
2024-02-18 10:42:21 +00:00
var err error
if failover.opt.ReplicaOnly {
2024-02-18 10:42:21 +00:00
addr, err = failover.RandomReplicaAddr(ctx)
2024-02-18 10:42:21 +00:00
} else {
2024-02-18 10:42:21 +00:00
addr, err = failover.MasterAddr(ctx)
2024-02-18 10:42:21 +00:00
if err == nil {
2024-02-18 10:42:21 +00:00
failover.trySwitchMaster(ctx, addr)
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
return nil, err
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
if failover.opt.Dialer != nil {
2024-02-18 10:42:21 +00:00
return failover.opt.Dialer(ctx, network, addr)
2024-02-18 10:42:21 +00:00
}
netDialer := &net.Dialer{
Timeout: failover.opt.DialTimeout,
2024-02-18 10:42:21 +00:00
KeepAlive: 5 * time.Minute,
}
2024-02-18 10:42:21 +00:00
if failover.opt.TLSConfig == nil {
2024-02-18 10:42:21 +00:00
return netDialer.DialContext(ctx, network, addr)
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return tls.DialWithDialer(netDialer, network, addr, failover.opt.TLSConfig)
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
//------------------------------------------------------------------------------
// SentinelClient is a client for a Redis Sentinel.
2024-02-18 10:42:21 +00:00
type SentinelClient struct {
*baseClient
2024-02-18 10:42:21 +00:00
hooksMixin
}
func NewSentinelClient(opt *Options) *SentinelClient {
2024-02-18 10:42:21 +00:00
opt.init()
2024-02-18 10:42:21 +00:00
c := &SentinelClient{
2024-02-18 10:42:21 +00:00
baseClient: &baseClient{
2024-02-18 10:42:21 +00:00
opt: opt,
},
}
c.initHooks(hooks{
dial: c.baseClient.dial,
2024-02-18 10:42:21 +00:00
process: c.baseClient.process,
})
2024-02-18 10:42:21 +00:00
c.connPool = newConnPool(opt, c.dialHook)
return c
2024-02-18 10:42:21 +00:00
}
func (c *SentinelClient) Process(ctx context.Context, cmd Cmder) error {
2024-02-18 10:42:21 +00:00
err := c.processHook(ctx, cmd)
2024-02-18 10:42:21 +00:00
cmd.SetErr(err)
2024-02-18 10:42:21 +00:00
return err
2024-02-18 10:42:21 +00:00
}
func (c *SentinelClient) pubSub() *PubSub {
2024-02-18 10:42:21 +00:00
pubsub := &PubSub{
2024-02-18 10:42:21 +00:00
opt: c.opt,
newConn: func(ctx context.Context, channels []string) (*pool.Conn, error) {
2024-02-18 10:42:21 +00:00
return c.newConn(ctx)
2024-02-18 10:42:21 +00:00
},
2024-02-18 10:42:21 +00:00
closeConn: c.connPool.CloseConn,
}
2024-02-18 10:42:21 +00:00
pubsub.init()
2024-02-18 10:42:21 +00:00
return pubsub
2024-02-18 10:42:21 +00:00
}
// Ping is used to test if a connection is still alive, or to
2024-02-18 10:42:21 +00:00
// measure latency.
2024-02-18 10:42:21 +00:00
func (c *SentinelClient) Ping(ctx context.Context) *StringCmd {
2024-02-18 10:42:21 +00:00
cmd := NewStringCmd(ctx, "ping")
2024-02-18 10:42:21 +00:00
_ = c.Process(ctx, cmd)
2024-02-18 10:42:21 +00:00
return cmd
2024-02-18 10:42:21 +00:00
}
// Subscribe subscribes the client to the specified channels.
2024-02-18 10:42:21 +00:00
// Channels can be omitted to create empty subscription.
2024-02-18 10:42:21 +00:00
func (c *SentinelClient) Subscribe(ctx context.Context, channels ...string) *PubSub {
2024-02-18 10:42:21 +00:00
pubsub := c.pubSub()
2024-02-18 10:42:21 +00:00
if len(channels) > 0 {
2024-02-18 10:42:21 +00:00
_ = pubsub.Subscribe(ctx, channels...)
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return pubsub
2024-02-18 10:42:21 +00:00
}
// PSubscribe subscribes the client to the given patterns.
2024-02-18 10:42:21 +00:00
// Patterns can be omitted to create empty subscription.
2024-02-18 10:42:21 +00:00
func (c *SentinelClient) PSubscribe(ctx context.Context, channels ...string) *PubSub {
2024-02-18 10:42:21 +00:00
pubsub := c.pubSub()
2024-02-18 10:42:21 +00:00
if len(channels) > 0 {
2024-02-18 10:42:21 +00:00
_ = pubsub.PSubscribe(ctx, channels...)
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return pubsub
2024-02-18 10:42:21 +00:00
}
func (c *SentinelClient) GetMasterAddrByName(ctx context.Context, name string) *StringSliceCmd {
2024-02-18 10:42:21 +00:00
cmd := NewStringSliceCmd(ctx, "sentinel", "get-master-addr-by-name", name)
2024-02-18 10:42:21 +00:00
_ = c.Process(ctx, cmd)
2024-02-18 10:42:21 +00:00
return cmd
2024-02-18 10:42:21 +00:00
}
func (c *SentinelClient) Sentinels(ctx context.Context, name string) *MapStringStringSliceCmd {
2024-02-18 10:42:21 +00:00
cmd := NewMapStringStringSliceCmd(ctx, "sentinel", "sentinels", name)
2024-02-18 10:42:21 +00:00
_ = c.Process(ctx, cmd)
2024-02-18 10:42:21 +00:00
return cmd
2024-02-18 10:42:21 +00:00
}
// Failover forces a failover as if the master was not reachable, and without
2024-02-18 10:42:21 +00:00
// asking for agreement to other Sentinels.
2024-02-18 10:42:21 +00:00
func (c *SentinelClient) Failover(ctx context.Context, name string) *StatusCmd {
2024-02-18 10:42:21 +00:00
cmd := NewStatusCmd(ctx, "sentinel", "failover", name)
2024-02-18 10:42:21 +00:00
_ = c.Process(ctx, cmd)
2024-02-18 10:42:21 +00:00
return cmd
2024-02-18 10:42:21 +00:00
}
// Reset resets all the masters with matching name. The pattern argument is a
2024-02-18 10:42:21 +00:00
// glob-style pattern. The reset process clears any previous state in a master
2024-02-18 10:42:21 +00:00
// (including a failover in progress), and removes every replica and sentinel
2024-02-18 10:42:21 +00:00
// already discovered and associated with the master.
2024-02-18 10:42:21 +00:00
func (c *SentinelClient) Reset(ctx context.Context, pattern string) *IntCmd {
2024-02-18 10:42:21 +00:00
cmd := NewIntCmd(ctx, "sentinel", "reset", pattern)
2024-02-18 10:42:21 +00:00
_ = c.Process(ctx, cmd)
2024-02-18 10:42:21 +00:00
return cmd
2024-02-18 10:42:21 +00:00
}
// FlushConfig forces Sentinel to rewrite its configuration on disk, including
2024-02-18 10:42:21 +00:00
// the current Sentinel state.
2024-02-18 10:42:21 +00:00
func (c *SentinelClient) FlushConfig(ctx context.Context) *StatusCmd {
2024-02-18 10:42:21 +00:00
cmd := NewStatusCmd(ctx, "sentinel", "flushconfig")
2024-02-18 10:42:21 +00:00
_ = c.Process(ctx, cmd)
2024-02-18 10:42:21 +00:00
return cmd
2024-02-18 10:42:21 +00:00
}
// Master shows the state and info of the specified master.
2024-02-18 10:42:21 +00:00
func (c *SentinelClient) Master(ctx context.Context, name string) *MapStringStringCmd {
2024-02-18 10:42:21 +00:00
cmd := NewMapStringStringCmd(ctx, "sentinel", "master", name)
2024-02-18 10:42:21 +00:00
_ = c.Process(ctx, cmd)
2024-02-18 10:42:21 +00:00
return cmd
2024-02-18 10:42:21 +00:00
}
// Masters shows a list of monitored masters and their state.
2024-02-18 10:42:21 +00:00
func (c *SentinelClient) Masters(ctx context.Context) *SliceCmd {
2024-02-18 10:42:21 +00:00
cmd := NewSliceCmd(ctx, "sentinel", "masters")
2024-02-18 10:42:21 +00:00
_ = c.Process(ctx, cmd)
2024-02-18 10:42:21 +00:00
return cmd
2024-02-18 10:42:21 +00:00
}
// Replicas shows a list of replicas for the specified master and their state.
2024-02-18 10:42:21 +00:00
func (c *SentinelClient) Replicas(ctx context.Context, name string) *MapStringStringSliceCmd {
2024-02-18 10:42:21 +00:00
cmd := NewMapStringStringSliceCmd(ctx, "sentinel", "replicas", name)
2024-02-18 10:42:21 +00:00
_ = c.Process(ctx, cmd)
2024-02-18 10:42:21 +00:00
return cmd
2024-02-18 10:42:21 +00:00
}
// CkQuorum checks if the current Sentinel configuration is able to reach the
2024-02-18 10:42:21 +00:00
// quorum needed to failover a master, and the majority needed to authorize the
2024-02-18 10:42:21 +00:00
// failover. This command should be used in monitoring systems to check if a
2024-02-18 10:42:21 +00:00
// Sentinel deployment is ok.
2024-02-18 10:42:21 +00:00
func (c *SentinelClient) CkQuorum(ctx context.Context, name string) *StringCmd {
2024-02-18 10:42:21 +00:00
cmd := NewStringCmd(ctx, "sentinel", "ckquorum", name)
2024-02-18 10:42:21 +00:00
_ = c.Process(ctx, cmd)
2024-02-18 10:42:21 +00:00
return cmd
2024-02-18 10:42:21 +00:00
}
// Monitor tells the Sentinel to start monitoring a new master with the specified
2024-02-18 10:42:21 +00:00
// name, ip, port, and quorum.
2024-02-18 10:42:21 +00:00
func (c *SentinelClient) Monitor(ctx context.Context, name, ip, port, quorum string) *StringCmd {
2024-02-18 10:42:21 +00:00
cmd := NewStringCmd(ctx, "sentinel", "monitor", name, ip, port, quorum)
2024-02-18 10:42:21 +00:00
_ = c.Process(ctx, cmd)
2024-02-18 10:42:21 +00:00
return cmd
2024-02-18 10:42:21 +00:00
}
// Set is used in order to change configuration parameters of a specific master.
2024-02-18 10:42:21 +00:00
func (c *SentinelClient) Set(ctx context.Context, name, option, value string) *StringCmd {
2024-02-18 10:42:21 +00:00
cmd := NewStringCmd(ctx, "sentinel", "set", name, option, value)
2024-02-18 10:42:21 +00:00
_ = c.Process(ctx, cmd)
2024-02-18 10:42:21 +00:00
return cmd
2024-02-18 10:42:21 +00:00
}
// Remove is used in order to remove the specified master: the master will no
2024-02-18 10:42:21 +00:00
// longer be monitored, and will totally be removed from the internal state of
2024-02-18 10:42:21 +00:00
// the Sentinel.
2024-02-18 10:42:21 +00:00
func (c *SentinelClient) Remove(ctx context.Context, name string) *StringCmd {
2024-02-18 10:42:21 +00:00
cmd := NewStringCmd(ctx, "sentinel", "remove", name)
2024-02-18 10:42:21 +00:00
_ = c.Process(ctx, cmd)
2024-02-18 10:42:21 +00:00
return cmd
2024-02-18 10:42:21 +00:00
}
//------------------------------------------------------------------------------
type sentinelFailover struct {
opt *FailoverOptions
sentinelAddrs []string
onFailover func(ctx context.Context, addr string)
onUpdate func(ctx context.Context)
mu sync.RWMutex
2024-02-18 10:42:21 +00:00
_masterAddr string
sentinel *SentinelClient
pubsub *PubSub
2024-02-18 10:42:21 +00:00
}
func (c *sentinelFailover) Close() error {
2024-02-18 10:42:21 +00:00
c.mu.Lock()
2024-02-18 10:42:21 +00:00
defer c.mu.Unlock()
2024-02-18 10:42:21 +00:00
if c.sentinel != nil {
2024-02-18 10:42:21 +00:00
return c.closeSentinel()
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return nil
2024-02-18 10:42:21 +00:00
}
func (c *sentinelFailover) closeSentinel() error {
2024-02-18 10:42:21 +00:00
firstErr := c.pubsub.Close()
2024-02-18 10:42:21 +00:00
c.pubsub = nil
err := c.sentinel.Close()
2024-02-18 10:42:21 +00:00
if err != nil && firstErr == nil {
2024-02-18 10:42:21 +00:00
firstErr = err
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
c.sentinel = nil
return firstErr
2024-02-18 10:42:21 +00:00
}
func (c *sentinelFailover) RandomReplicaAddr(ctx context.Context) (string, error) {
2024-02-18 10:42:21 +00:00
if c.opt == nil {
2024-02-18 10:42:21 +00:00
return "", errors.New("opt is nil")
2024-02-18 10:42:21 +00:00
}
addresses, err := c.replicaAddrs(ctx, false)
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
return "", err
2024-02-18 10:42:21 +00:00
}
if len(addresses) == 0 && c.opt.UseDisconnectedReplicas {
2024-02-18 10:42:21 +00:00
addresses, err = c.replicaAddrs(ctx, true)
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
return "", err
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
if len(addresses) == 0 {
2024-02-18 10:42:21 +00:00
return c.MasterAddr(ctx)
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return addresses[rand.Intn(len(addresses))], nil
2024-02-18 10:42:21 +00:00
}
func (c *sentinelFailover) MasterAddr(ctx context.Context) (string, error) {
2024-02-18 10:42:21 +00:00
c.mu.RLock()
2024-02-18 10:42:21 +00:00
sentinel := c.sentinel
2024-02-18 10:42:21 +00:00
c.mu.RUnlock()
if sentinel != nil {
2024-02-18 10:42:21 +00:00
addr, err := c.getMasterAddr(ctx, sentinel)
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
2024-02-18 10:42:21 +00:00
return "", err
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
// Continue on other errors
2024-02-18 10:42:21 +00:00
internal.Logger.Printf(ctx, "sentinel: GetMasterAddrByName name=%q failed: %s",
2024-02-18 10:42:21 +00:00
c.opt.MasterName, err)
2024-02-18 10:42:21 +00:00
} else {
2024-02-18 10:42:21 +00:00
return addr, nil
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
c.mu.Lock()
2024-02-18 10:42:21 +00:00
defer c.mu.Unlock()
if c.sentinel != nil {
2024-02-18 10:42:21 +00:00
addr, err := c.getMasterAddr(ctx, c.sentinel)
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
_ = c.closeSentinel()
2024-02-18 10:42:21 +00:00
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
2024-02-18 10:42:21 +00:00
return "", err
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
// Continue on other errors
2024-02-18 10:42:21 +00:00
internal.Logger.Printf(ctx, "sentinel: GetMasterAddrByName name=%q failed: %s",
2024-02-18 10:42:21 +00:00
c.opt.MasterName, err)
2024-02-18 10:42:21 +00:00
} else {
2024-02-18 10:42:21 +00:00
return addr, nil
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
for i, sentinelAddr := range c.sentinelAddrs {
2024-02-18 10:42:21 +00:00
sentinel := NewSentinelClient(c.opt.sentinelOptions(sentinelAddr))
masterAddr, err := sentinel.GetMasterAddrByName(ctx, c.opt.MasterName).Result()
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
_ = sentinel.Close()
2024-02-18 10:42:21 +00:00
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
2024-02-18 10:42:21 +00:00
return "", err
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
internal.Logger.Printf(ctx, "sentinel: GetMasterAddrByName master=%q failed: %s",
2024-02-18 10:42:21 +00:00
c.opt.MasterName, err)
2024-02-18 10:42:21 +00:00
continue
2024-02-18 10:42:21 +00:00
}
// Push working sentinel to the top.
2024-02-18 10:42:21 +00:00
c.sentinelAddrs[0], c.sentinelAddrs[i] = c.sentinelAddrs[i], c.sentinelAddrs[0]
2024-02-18 10:42:21 +00:00
c.setSentinel(ctx, sentinel)
addr := net.JoinHostPort(masterAddr[0], masterAddr[1])
2024-02-18 10:42:21 +00:00
return addr, nil
2024-02-18 10:42:21 +00:00
}
return "", errors.New("redis: all sentinels specified in configuration are unreachable")
2024-02-18 10:42:21 +00:00
}
func (c *sentinelFailover) replicaAddrs(ctx context.Context, useDisconnected bool) ([]string, error) {
2024-02-18 10:42:21 +00:00
c.mu.RLock()
2024-02-18 10:42:21 +00:00
sentinel := c.sentinel
2024-02-18 10:42:21 +00:00
c.mu.RUnlock()
if sentinel != nil {
2024-02-18 10:42:21 +00:00
addrs, err := c.getReplicaAddrs(ctx, sentinel)
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
2024-02-18 10:42:21 +00:00
return nil, err
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
// Continue on other errors
2024-02-18 10:42:21 +00:00
internal.Logger.Printf(ctx, "sentinel: Replicas name=%q failed: %s",
2024-02-18 10:42:21 +00:00
c.opt.MasterName, err)
2024-02-18 10:42:21 +00:00
} else if len(addrs) > 0 {
2024-02-18 10:42:21 +00:00
return addrs, nil
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
c.mu.Lock()
2024-02-18 10:42:21 +00:00
defer c.mu.Unlock()
if c.sentinel != nil {
2024-02-18 10:42:21 +00:00
addrs, err := c.getReplicaAddrs(ctx, c.sentinel)
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
_ = c.closeSentinel()
2024-02-18 10:42:21 +00:00
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
2024-02-18 10:42:21 +00:00
return nil, err
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
// Continue on other errors
2024-02-18 10:42:21 +00:00
internal.Logger.Printf(ctx, "sentinel: Replicas name=%q failed: %s",
2024-02-18 10:42:21 +00:00
c.opt.MasterName, err)
2024-02-18 10:42:21 +00:00
} else if len(addrs) > 0 {
2024-02-18 10:42:21 +00:00
return addrs, nil
2024-02-18 10:42:21 +00:00
} else {
2024-02-18 10:42:21 +00:00
// No error and no replicas.
2024-02-18 10:42:21 +00:00
_ = c.closeSentinel()
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
var sentinelReachable bool
for i, sentinelAddr := range c.sentinelAddrs {
2024-02-18 10:42:21 +00:00
sentinel := NewSentinelClient(c.opt.sentinelOptions(sentinelAddr))
replicas, err := sentinel.Replicas(ctx, c.opt.MasterName).Result()
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
_ = sentinel.Close()
2024-02-18 10:42:21 +00:00
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
2024-02-18 10:42:21 +00:00
return nil, err
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
internal.Logger.Printf(ctx, "sentinel: Replicas master=%q failed: %s",
2024-02-18 10:42:21 +00:00
c.opt.MasterName, err)
2024-02-18 10:42:21 +00:00
continue
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
sentinelReachable = true
2024-02-18 10:42:21 +00:00
addrs := parseReplicaAddrs(replicas, useDisconnected)
2024-02-18 10:42:21 +00:00
if len(addrs) == 0 {
2024-02-18 10:42:21 +00:00
continue
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
// Push working sentinel to the top.
2024-02-18 10:42:21 +00:00
c.sentinelAddrs[0], c.sentinelAddrs[i] = c.sentinelAddrs[i], c.sentinelAddrs[0]
2024-02-18 10:42:21 +00:00
c.setSentinel(ctx, sentinel)
return addrs, nil
2024-02-18 10:42:21 +00:00
}
if sentinelReachable {
2024-02-18 10:42:21 +00:00
return []string{}, nil
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return []string{}, errors.New("redis: all sentinels specified in configuration are unreachable")
2024-02-18 10:42:21 +00:00
}
func (c *sentinelFailover) getMasterAddr(ctx context.Context, sentinel *SentinelClient) (string, error) {
2024-02-18 10:42:21 +00:00
addr, err := sentinel.GetMasterAddrByName(ctx, c.opt.MasterName).Result()
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
return "", err
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return net.JoinHostPort(addr[0], addr[1]), nil
2024-02-18 10:42:21 +00:00
}
func (c *sentinelFailover) getReplicaAddrs(ctx context.Context, sentinel *SentinelClient) ([]string, error) {
2024-02-18 10:42:21 +00:00
addrs, err := sentinel.Replicas(ctx, c.opt.MasterName).Result()
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
internal.Logger.Printf(ctx, "sentinel: Replicas name=%q failed: %s",
2024-02-18 10:42:21 +00:00
c.opt.MasterName, err)
2024-02-18 10:42:21 +00:00
return nil, err
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return parseReplicaAddrs(addrs, false), nil
2024-02-18 10:42:21 +00:00
}
func parseReplicaAddrs(addrs []map[string]string, keepDisconnected bool) []string {
2024-02-18 10:42:21 +00:00
nodes := make([]string, 0, len(addrs))
2024-02-18 10:42:21 +00:00
for _, node := range addrs {
2024-02-18 10:42:21 +00:00
isDown := false
2024-02-18 10:42:21 +00:00
if flags, ok := node["flags"]; ok {
2024-02-18 10:42:21 +00:00
for _, flag := range strings.Split(flags, ",") {
2024-02-18 10:42:21 +00:00
switch flag {
2024-02-18 10:42:21 +00:00
case "s_down", "o_down":
2024-02-18 10:42:21 +00:00
isDown = true
2024-02-18 10:42:21 +00:00
case "disconnected":
2024-02-18 10:42:21 +00:00
if !keepDisconnected {
2024-02-18 10:42:21 +00:00
isDown = true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
if !isDown && node["ip"] != "" && node["port"] != "" {
2024-02-18 10:42:21 +00:00
nodes = append(nodes, net.JoinHostPort(node["ip"], node["port"]))
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
return nodes
2024-02-18 10:42:21 +00:00
}
func (c *sentinelFailover) trySwitchMaster(ctx context.Context, addr string) {
2024-02-18 10:42:21 +00:00
c.mu.RLock()
2024-02-18 10:42:21 +00:00
currentAddr := c._masterAddr //nolint:ifshort
2024-02-18 10:42:21 +00:00
c.mu.RUnlock()
if addr == currentAddr {
2024-02-18 10:42:21 +00:00
return
2024-02-18 10:42:21 +00:00
}
c.mu.Lock()
2024-02-18 10:42:21 +00:00
defer c.mu.Unlock()
if addr == c._masterAddr {
2024-02-18 10:42:21 +00:00
return
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
c._masterAddr = addr
internal.Logger.Printf(ctx, "sentinel: new master=%q addr=%q",
2024-02-18 10:42:21 +00:00
c.opt.MasterName, addr)
2024-02-18 10:42:21 +00:00
if c.onFailover != nil {
2024-02-18 10:42:21 +00:00
c.onFailover(ctx, addr)
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
func (c *sentinelFailover) setSentinel(ctx context.Context, sentinel *SentinelClient) {
2024-02-18 10:42:21 +00:00
if c.sentinel != nil {
2024-02-18 10:42:21 +00:00
panic("not reached")
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
c.sentinel = sentinel
2024-02-18 10:42:21 +00:00
c.discoverSentinels(ctx)
c.pubsub = sentinel.Subscribe(ctx, "+switch-master", "+replica-reconf-done")
2024-02-18 10:42:21 +00:00
go c.listen(c.pubsub)
2024-02-18 10:42:21 +00:00
}
func (c *sentinelFailover) discoverSentinels(ctx context.Context) {
2024-02-18 10:42:21 +00:00
sentinels, err := c.sentinel.Sentinels(ctx, c.opt.MasterName).Result()
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
internal.Logger.Printf(ctx, "sentinel: Sentinels master=%q failed: %s", c.opt.MasterName, err)
2024-02-18 10:42:21 +00:00
return
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
for _, sentinel := range sentinels {
2024-02-18 10:42:21 +00:00
ip, ok := sentinel["ip"]
2024-02-18 10:42:21 +00:00
if !ok {
2024-02-18 10:42:21 +00:00
continue
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
port, ok := sentinel["port"]
2024-02-18 10:42:21 +00:00
if !ok {
2024-02-18 10:42:21 +00:00
continue
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
if ip != "" && port != "" {
2024-02-18 10:42:21 +00:00
sentinelAddr := net.JoinHostPort(ip, port)
2024-02-18 10:42:21 +00:00
if !contains(c.sentinelAddrs, sentinelAddr) {
2024-02-18 10:42:21 +00:00
internal.Logger.Printf(ctx, "sentinel: discovered new sentinel=%q for master=%q",
2024-02-18 10:42:21 +00:00
sentinelAddr, c.opt.MasterName)
2024-02-18 10:42:21 +00:00
c.sentinelAddrs = append(c.sentinelAddrs, sentinelAddr)
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
func (c *sentinelFailover) listen(pubsub *PubSub) {
2024-02-18 10:42:21 +00:00
ctx := context.TODO()
if c.onUpdate != nil {
2024-02-18 10:42:21 +00:00
c.onUpdate(ctx)
2024-02-18 10:42:21 +00:00
}
ch := pubsub.Channel()
2024-02-18 10:42:21 +00:00
for msg := range ch {
2024-02-18 10:42:21 +00:00
if msg.Channel == "+switch-master" {
2024-02-18 10:42:21 +00:00
parts := strings.Split(msg.Payload, " ")
2024-02-18 10:42:21 +00:00
if parts[0] != c.opt.MasterName {
2024-02-18 10:42:21 +00:00
internal.Logger.Printf(pubsub.getContext(), "sentinel: ignore addr for master=%q", parts[0])
2024-02-18 10:42:21 +00:00
continue
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
addr := net.JoinHostPort(parts[3], parts[4])
2024-02-18 10:42:21 +00:00
c.trySwitchMaster(pubsub.getContext(), addr)
2024-02-18 10:42:21 +00:00
}
if c.onUpdate != nil {
2024-02-18 10:42:21 +00:00
c.onUpdate(ctx)
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
func contains(slice []string, str string) bool {
2024-02-18 10:42:21 +00:00
for _, s := range slice {
2024-02-18 10:42:21 +00:00
if s == str {
2024-02-18 10:42:21 +00:00
return true
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
return false
2024-02-18 10:42:21 +00:00
}
//------------------------------------------------------------------------------
// NewFailoverClusterClient returns a client that supports routing read-only commands
2024-02-18 10:42:21 +00:00
// to a replica node.
2024-02-18 10:42:21 +00:00
func NewFailoverClusterClient(failoverOpt *FailoverOptions) *ClusterClient {
2024-02-18 10:42:21 +00:00
sentinelAddrs := make([]string, len(failoverOpt.SentinelAddrs))
2024-02-18 10:42:21 +00:00
copy(sentinelAddrs, failoverOpt.SentinelAddrs)
failover := &sentinelFailover{
opt: failoverOpt,
2024-02-18 10:42:21 +00:00
sentinelAddrs: sentinelAddrs,
}
opt := failoverOpt.clusterOptions()
2024-02-18 10:42:21 +00:00
opt.ClusterSlots = func(ctx context.Context) ([]ClusterSlot, error) {
2024-02-18 10:42:21 +00:00
masterAddr, err := failover.MasterAddr(ctx)
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
return nil, err
2024-02-18 10:42:21 +00:00
}
nodes := []ClusterNode{{
2024-02-18 10:42:21 +00:00
Addr: masterAddr,
}}
replicaAddrs, err := failover.replicaAddrs(ctx, false)
2024-02-18 10:42:21 +00:00
if err != nil {
2024-02-18 10:42:21 +00:00
return nil, err
2024-02-18 10:42:21 +00:00
}
for _, replicaAddr := range replicaAddrs {
2024-02-18 10:42:21 +00:00
nodes = append(nodes, ClusterNode{
2024-02-18 10:42:21 +00:00
Addr: replicaAddr,
})
2024-02-18 10:42:21 +00:00
}
slots := []ClusterSlot{
2024-02-18 10:42:21 +00:00
{
2024-02-18 10:42:21 +00:00
Start: 0,
End: 16383,
2024-02-18 10:42:21 +00:00
Nodes: nodes,
},
}
2024-02-18 10:42:21 +00:00
return slots, nil
2024-02-18 10:42:21 +00:00
}
c := NewClusterClient(opt)
failover.mu.Lock()
2024-02-18 10:42:21 +00:00
failover.onUpdate = func(ctx context.Context) {
2024-02-18 10:42:21 +00:00
c.ReloadState(ctx)
2024-02-18 10:42:21 +00:00
}
2024-02-18 10:42:21 +00:00
failover.mu.Unlock()
return c
2024-02-18 10:42:21 +00:00
}