| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503 |
- package redis
- import (
- "context"
- "crypto/tls"
- "errors"
- "net"
- "strings"
- "sync"
- "time"
- "github.com/go-redis/redis/internal"
- "github.com/go-redis/redis/internal/pool"
- )
- //------------------------------------------------------------------------------
- // FailoverOptions are used to configure a failover client and should
- // be passed to NewFailoverClient.
- type FailoverOptions struct {
- // The master name.
- MasterName string
- // A seed list of host:port addresses of sentinel nodes.
- SentinelAddrs []string
- SentinelPassword string
- // Following options are copied from Options struct.
- Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
- OnConnect func(*Conn) error
- Password string
- DB int
- MaxRetries int
- MinRetryBackoff time.Duration
- MaxRetryBackoff time.Duration
- DialTimeout time.Duration
- ReadTimeout time.Duration
- WriteTimeout time.Duration
- PoolSize int
- MinIdleConns int
- MaxConnAge time.Duration
- PoolTimeout time.Duration
- IdleTimeout time.Duration
- IdleCheckFrequency time.Duration
- TLSConfig *tls.Config
- }
- func (opt *FailoverOptions) options() *Options {
- return &Options{
- Addr: "FailoverClient",
- Dialer: opt.Dialer,
- OnConnect: opt.OnConnect,
- DB: opt.DB,
- Password: opt.Password,
- MaxRetries: opt.MaxRetries,
- MinRetryBackoff: opt.MinRetryBackoff,
- MaxRetryBackoff: opt.MaxRetryBackoff,
- DialTimeout: opt.DialTimeout,
- ReadTimeout: opt.ReadTimeout,
- WriteTimeout: opt.WriteTimeout,
- PoolSize: opt.PoolSize,
- PoolTimeout: opt.PoolTimeout,
- IdleTimeout: opt.IdleTimeout,
- IdleCheckFrequency: opt.IdleCheckFrequency,
- MinIdleConns: opt.MinIdleConns,
- MaxConnAge: opt.MaxConnAge,
- TLSConfig: opt.TLSConfig,
- }
- }
- // NewFailoverClient returns a Redis client that uses Redis Sentinel
- // for automatic failover. It's safe for concurrent use by multiple
- // goroutines.
- func NewFailoverClient(failoverOpt *FailoverOptions) *Client {
- opt := failoverOpt.options()
- opt.init()
- failover := &sentinelFailover{
- masterName: failoverOpt.MasterName,
- sentinelAddrs: failoverOpt.SentinelAddrs,
- password: failoverOpt.SentinelPassword,
- opt: opt,
- }
- c := Client{
- baseClient: newBaseClient(opt, failover.Pool()),
- ctx: context.Background(),
- }
- c.cmdable = c.Process
- c.onClose = failover.Close
- return &c
- }
- //------------------------------------------------------------------------------
- type SentinelClient struct {
- *baseClient
- ctx context.Context
- }
- func NewSentinelClient(opt *Options) *SentinelClient {
- opt.init()
- c := &SentinelClient{
- baseClient: &baseClient{
- opt: opt,
- connPool: newConnPool(opt),
- },
- ctx: context.Background(),
- }
- return c
- }
- func (c *SentinelClient) Context() context.Context {
- return c.ctx
- }
- func (c *SentinelClient) WithContext(ctx context.Context) *SentinelClient {
- if ctx == nil {
- panic("nil context")
- }
- clone := *c
- clone.ctx = ctx
- return &clone
- }
- func (c *SentinelClient) Process(cmd Cmder) error {
- return c.ProcessContext(c.ctx, cmd)
- }
- func (c *SentinelClient) ProcessContext(ctx context.Context, cmd Cmder) error {
- return c.baseClient.process(ctx, cmd)
- }
- func (c *SentinelClient) pubSub() *PubSub {
- pubsub := &PubSub{
- opt: c.opt,
- newConn: func(channels []string) (*pool.Conn, error) {
- return c.newConn(context.TODO())
- },
- closeConn: c.connPool.CloseConn,
- }
- pubsub.init()
- return pubsub
- }
- // Ping is used to test if a connection is still alive, or to
- // measure latency.
- func (c *SentinelClient) Ping() *StringCmd {
- cmd := NewStringCmd("ping")
- _ = c.Process(cmd)
- return cmd
- }
- // Subscribe subscribes the client to the specified channels.
- // Channels can be omitted to create empty subscription.
- func (c *SentinelClient) Subscribe(channels ...string) *PubSub {
- pubsub := c.pubSub()
- if len(channels) > 0 {
- _ = pubsub.Subscribe(channels...)
- }
- return pubsub
- }
- // PSubscribe subscribes the client to the given patterns.
- // Patterns can be omitted to create empty subscription.
- func (c *SentinelClient) PSubscribe(channels ...string) *PubSub {
- pubsub := c.pubSub()
- if len(channels) > 0 {
- _ = pubsub.PSubscribe(channels...)
- }
- return pubsub
- }
- func (c *SentinelClient) GetMasterAddrByName(name string) *StringSliceCmd {
- cmd := NewStringSliceCmd("sentinel", "get-master-addr-by-name", name)
- _ = c.Process(cmd)
- return cmd
- }
- func (c *SentinelClient) Sentinels(name string) *SliceCmd {
- cmd := NewSliceCmd("sentinel", "sentinels", name)
- _ = c.Process(cmd)
- return cmd
- }
- // Failover forces a failover as if the master was not reachable, and without
- // asking for agreement to other Sentinels.
- func (c *SentinelClient) Failover(name string) *StatusCmd {
- cmd := NewStatusCmd("sentinel", "failover", name)
- _ = c.Process(cmd)
- return cmd
- }
- // Reset resets all the masters with matching name. The pattern argument is a
- // glob-style pattern. The reset process clears any previous state in a master
- // (including a failover in progress), and removes every slave and sentinel
- // already discovered and associated with the master.
- func (c *SentinelClient) Reset(pattern string) *IntCmd {
- cmd := NewIntCmd("sentinel", "reset", pattern)
- _ = c.Process(cmd)
- return cmd
- }
- // FlushConfig forces Sentinel to rewrite its configuration on disk, including
- // the current Sentinel state.
- func (c *SentinelClient) FlushConfig() *StatusCmd {
- cmd := NewStatusCmd("sentinel", "flushconfig")
- _ = c.Process(cmd)
- return cmd
- }
- // Master shows the state and info of the specified master.
- func (c *SentinelClient) Master(name string) *StringStringMapCmd {
- cmd := NewStringStringMapCmd("sentinel", "master", name)
- _ = c.Process(cmd)
- return cmd
- }
- // Masters shows a list of monitored masters and their state.
- func (c *SentinelClient) Masters() *SliceCmd {
- cmd := NewSliceCmd("sentinel", "masters")
- _ = c.Process(cmd)
- return cmd
- }
- // Slaves shows a list of slaves for the specified master and their state.
- func (c *SentinelClient) Slaves(name string) *SliceCmd {
- cmd := NewSliceCmd("sentinel", "slaves", name)
- _ = c.Process(cmd)
- return cmd
- }
- // CkQuorum checks if the current Sentinel configuration is able to reach the
- // quorum needed to failover a master, and the majority needed to authorize the
- // failover. This command should be used in monitoring systems to check if a
- // Sentinel deployment is ok.
- func (c *SentinelClient) CkQuorum(name string) *StringCmd {
- cmd := NewStringCmd("sentinel", "ckquorum", name)
- _ = c.Process(cmd)
- return cmd
- }
- // Monitor tells the Sentinel to start monitoring a new master with the specified
- // name, ip, port, and quorum.
- func (c *SentinelClient) Monitor(name, ip, port, quorum string) *StringCmd {
- cmd := NewStringCmd("sentinel", "monitor", name, ip, port, quorum)
- _ = c.Process(cmd)
- return cmd
- }
- // Set is used in order to change configuration parameters of a specific master.
- func (c *SentinelClient) Set(name, option, value string) *StringCmd {
- cmd := NewStringCmd("sentinel", "set", name, option, value)
- _ = c.Process(cmd)
- return cmd
- }
- // Remove is used in order to remove the specified master: the master will no
- // longer be monitored, and will totally be removed from the internal state of
- // the Sentinel.
- func (c *SentinelClient) Remove(name string) *StringCmd {
- cmd := NewStringCmd("sentinel", "remove", name)
- _ = c.Process(cmd)
- return cmd
- }
- type sentinelFailover struct {
- sentinelAddrs []string
- opt *Options
- password string
- pool *pool.ConnPool
- poolOnce sync.Once
- mu sync.RWMutex
- masterName string
- _masterAddr string
- sentinel *SentinelClient
- pubsub *PubSub
- }
- func (c *sentinelFailover) Close() error {
- c.mu.Lock()
- defer c.mu.Unlock()
- if c.sentinel != nil {
- return c.closeSentinel()
- }
- return nil
- }
- func (c *sentinelFailover) closeSentinel() error {
- firstErr := c.pubsub.Close()
- c.pubsub = nil
- err := c.sentinel.Close()
- if err != nil && firstErr == nil {
- firstErr = err
- }
- c.sentinel = nil
- return firstErr
- }
- func (c *sentinelFailover) Pool() *pool.ConnPool {
- c.poolOnce.Do(func() {
- opt := *c.opt
- opt.Dialer = c.dial
- c.pool = newConnPool(&opt)
- })
- return c.pool
- }
- func (c *sentinelFailover) dial(ctx context.Context, network, _ string) (net.Conn, error) {
- addr, err := c.MasterAddr()
- if err != nil {
- return nil, err
- }
- if c.opt.Dialer != nil {
- return c.opt.Dialer(ctx, network, addr)
- }
- return net.DialTimeout("tcp", addr, c.opt.DialTimeout)
- }
- func (c *sentinelFailover) MasterAddr() (string, error) {
- addr, err := c.masterAddr()
- if err != nil {
- return "", err
- }
- c.switchMaster(addr)
- return addr, nil
- }
- func (c *sentinelFailover) masterAddr() (string, error) {
- c.mu.RLock()
- sentinel := c.sentinel
- c.mu.RUnlock()
- if sentinel != nil {
- addr := c.getMasterAddr(sentinel)
- if addr != "" {
- return addr, nil
- }
- }
- c.mu.Lock()
- defer c.mu.Unlock()
- if c.sentinel != nil {
- addr := c.getMasterAddr(c.sentinel)
- if addr != "" {
- return addr, nil
- }
- _ = c.closeSentinel()
- }
- for i, sentinelAddr := range c.sentinelAddrs {
- sentinel := NewSentinelClient(&Options{
- Addr: sentinelAddr,
- Dialer: c.opt.Dialer,
- Password: c.password,
- MaxRetries: c.opt.MaxRetries,
- DialTimeout: c.opt.DialTimeout,
- ReadTimeout: c.opt.ReadTimeout,
- WriteTimeout: c.opt.WriteTimeout,
- PoolSize: c.opt.PoolSize,
- PoolTimeout: c.opt.PoolTimeout,
- IdleTimeout: c.opt.IdleTimeout,
- IdleCheckFrequency: c.opt.IdleCheckFrequency,
- TLSConfig: c.opt.TLSConfig,
- })
- masterAddr, err := sentinel.GetMasterAddrByName(c.masterName).Result()
- if err != nil {
- internal.Logger.Printf("sentinel: GetMasterAddrByName master=%q failed: %s",
- c.masterName, err)
- _ = sentinel.Close()
- continue
- }
- // Push working sentinel to the top.
- c.sentinelAddrs[0], c.sentinelAddrs[i] = c.sentinelAddrs[i], c.sentinelAddrs[0]
- c.setSentinel(sentinel)
- addr := net.JoinHostPort(masterAddr[0], masterAddr[1])
- return addr, nil
- }
- return "", errors.New("redis: all sentinels are unreachable")
- }
- func (c *sentinelFailover) getMasterAddr(sentinel *SentinelClient) string {
- addr, err := sentinel.GetMasterAddrByName(c.masterName).Result()
- if err != nil {
- internal.Logger.Printf("sentinel: GetMasterAddrByName name=%q failed: %s",
- c.masterName, err)
- return ""
- }
- return net.JoinHostPort(addr[0], addr[1])
- }
- func (c *sentinelFailover) switchMaster(addr string) {
- c.mu.RLock()
- masterAddr := c._masterAddr
- c.mu.RUnlock()
- if masterAddr == addr {
- return
- }
- c.mu.Lock()
- defer c.mu.Unlock()
- if c._masterAddr == addr {
- return
- }
- internal.Logger.Printf("sentinel: new master=%q addr=%q",
- c.masterName, addr)
- _ = c.Pool().Filter(func(cn *pool.Conn) bool {
- return cn.RemoteAddr().String() != addr
- })
- c._masterAddr = addr
- }
- func (c *sentinelFailover) setSentinel(sentinel *SentinelClient) {
- if c.sentinel != nil {
- panic("not reached")
- }
- c.sentinel = sentinel
- c.discoverSentinels()
- c.pubsub = sentinel.Subscribe("+switch-master")
- go c.listen(c.pubsub)
- }
- func (c *sentinelFailover) discoverSentinels() {
- sentinels, err := c.sentinel.Sentinels(c.masterName).Result()
- if err != nil {
- internal.Logger.Printf("sentinel: Sentinels master=%q failed: %s", c.masterName, err)
- return
- }
- for _, sentinel := range sentinels {
- vals := sentinel.([]interface{})
- for i := 0; i < len(vals); i += 2 {
- key := vals[i].(string)
- if key == "name" {
- sentinelAddr := vals[i+1].(string)
- if !contains(c.sentinelAddrs, sentinelAddr) {
- internal.Logger.Printf("sentinel: discovered new sentinel=%q for master=%q",
- sentinelAddr, c.masterName)
- c.sentinelAddrs = append(c.sentinelAddrs, sentinelAddr)
- }
- }
- }
- }
- }
- func (c *sentinelFailover) listen(pubsub *PubSub) {
- ch := pubsub.Channel()
- for {
- msg, ok := <-ch
- if !ok {
- break
- }
- if msg.Channel == "+switch-master" {
- parts := strings.Split(msg.Payload, " ")
- if parts[0] != c.masterName {
- internal.Logger.Printf("sentinel: ignore addr for master=%q", parts[0])
- continue
- }
- addr := net.JoinHostPort(parts[3], parts[4])
- c.switchMaster(addr)
- }
- }
- }
- func contains(slice []string, str string) bool {
- for _, s := range slice {
- if s == str {
- return true
- }
- }
- return false
- }
|