You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

797 lines
20 KiB

  1. package redis
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "errors"
  6. "net"
  7. "strings"
  8. "sync"
  9. "time"
  10. "github.com/go-redis/redis/v8/internal"
  11. "github.com/go-redis/redis/v8/internal/pool"
  12. "github.com/go-redis/redis/v8/internal/rand"
  13. )
  14. //------------------------------------------------------------------------------
  15. // FailoverOptions are used to configure a failover client and should
  16. // be passed to NewFailoverClient.
  17. type FailoverOptions struct {
  18. // The master name.
  19. MasterName string
  20. // A seed list of host:port addresses of sentinel nodes.
  21. SentinelAddrs []string
  22. // If specified with SentinelPassword, enables ACL-based authentication (via
  23. // AUTH <user> <pass>).
  24. SentinelUsername string
  25. // Sentinel password from "requirepass <password>" (if enabled) in Sentinel
  26. // configuration, or, if SentinelUsername is also supplied, used for ACL-based
  27. // authentication.
  28. SentinelPassword string
  29. // Allows routing read-only commands to the closest master or slave node.
  30. // This option only works with NewFailoverClusterClient.
  31. RouteByLatency bool
  32. // Allows routing read-only commands to the random master or slave node.
  33. // This option only works with NewFailoverClusterClient.
  34. RouteRandomly bool
  35. // Route all commands to slave read-only nodes.
  36. SlaveOnly bool
  37. // Use slaves disconnected with master when cannot get connected slaves
  38. // Now, this option only works in RandomSlaveAddr function.
  39. UseDisconnectedSlaves bool
  40. // Following options are copied from Options struct.
  41. Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
  42. OnConnect func(ctx context.Context, cn *Conn) error
  43. Username string
  44. Password string
  45. DB int
  46. MaxRetries int
  47. MinRetryBackoff time.Duration
  48. MaxRetryBackoff time.Duration
  49. DialTimeout time.Duration
  50. ReadTimeout time.Duration
  51. WriteTimeout time.Duration
  52. // PoolFIFO uses FIFO mode for each node connection pool GET/PUT (default LIFO).
  53. PoolFIFO bool
  54. PoolSize int
  55. MinIdleConns int
  56. MaxConnAge time.Duration
  57. PoolTimeout time.Duration
  58. IdleTimeout time.Duration
  59. IdleCheckFrequency time.Duration
  60. TLSConfig *tls.Config
  61. }
  62. func (opt *FailoverOptions) clientOptions() *Options {
  63. return &Options{
  64. Addr: "FailoverClient",
  65. Dialer: opt.Dialer,
  66. OnConnect: opt.OnConnect,
  67. DB: opt.DB,
  68. Username: opt.Username,
  69. Password: opt.Password,
  70. MaxRetries: opt.MaxRetries,
  71. MinRetryBackoff: opt.MinRetryBackoff,
  72. MaxRetryBackoff: opt.MaxRetryBackoff,
  73. DialTimeout: opt.DialTimeout,
  74. ReadTimeout: opt.ReadTimeout,
  75. WriteTimeout: opt.WriteTimeout,
  76. PoolFIFO: opt.PoolFIFO,
  77. PoolSize: opt.PoolSize,
  78. PoolTimeout: opt.PoolTimeout,
  79. IdleTimeout: opt.IdleTimeout,
  80. IdleCheckFrequency: opt.IdleCheckFrequency,
  81. MinIdleConns: opt.MinIdleConns,
  82. MaxConnAge: opt.MaxConnAge,
  83. TLSConfig: opt.TLSConfig,
  84. }
  85. }
  86. func (opt *FailoverOptions) sentinelOptions(addr string) *Options {
  87. return &Options{
  88. Addr: addr,
  89. Dialer: opt.Dialer,
  90. OnConnect: opt.OnConnect,
  91. DB: 0,
  92. Username: opt.SentinelUsername,
  93. Password: opt.SentinelPassword,
  94. MaxRetries: opt.MaxRetries,
  95. MinRetryBackoff: opt.MinRetryBackoff,
  96. MaxRetryBackoff: opt.MaxRetryBackoff,
  97. DialTimeout: opt.DialTimeout,
  98. ReadTimeout: opt.ReadTimeout,
  99. WriteTimeout: opt.WriteTimeout,
  100. PoolFIFO: opt.PoolFIFO,
  101. PoolSize: opt.PoolSize,
  102. PoolTimeout: opt.PoolTimeout,
  103. IdleTimeout: opt.IdleTimeout,
  104. IdleCheckFrequency: opt.IdleCheckFrequency,
  105. MinIdleConns: opt.MinIdleConns,
  106. MaxConnAge: opt.MaxConnAge,
  107. TLSConfig: opt.TLSConfig,
  108. }
  109. }
  110. func (opt *FailoverOptions) clusterOptions() *ClusterOptions {
  111. return &ClusterOptions{
  112. Dialer: opt.Dialer,
  113. OnConnect: opt.OnConnect,
  114. Username: opt.Username,
  115. Password: opt.Password,
  116. MaxRedirects: opt.MaxRetries,
  117. RouteByLatency: opt.RouteByLatency,
  118. RouteRandomly: opt.RouteRandomly,
  119. MinRetryBackoff: opt.MinRetryBackoff,
  120. MaxRetryBackoff: opt.MaxRetryBackoff,
  121. DialTimeout: opt.DialTimeout,
  122. ReadTimeout: opt.ReadTimeout,
  123. WriteTimeout: opt.WriteTimeout,
  124. PoolFIFO: opt.PoolFIFO,
  125. PoolSize: opt.PoolSize,
  126. PoolTimeout: opt.PoolTimeout,
  127. IdleTimeout: opt.IdleTimeout,
  128. IdleCheckFrequency: opt.IdleCheckFrequency,
  129. MinIdleConns: opt.MinIdleConns,
  130. MaxConnAge: opt.MaxConnAge,
  131. TLSConfig: opt.TLSConfig,
  132. }
  133. }
  134. // NewFailoverClient returns a Redis client that uses Redis Sentinel
  135. // for automatic failover. It's safe for concurrent use by multiple
  136. // goroutines.
  137. func NewFailoverClient(failoverOpt *FailoverOptions) *Client {
  138. if failoverOpt.RouteByLatency {
  139. panic("to route commands by latency, use NewFailoverClusterClient")
  140. }
  141. if failoverOpt.RouteRandomly {
  142. panic("to route commands randomly, use NewFailoverClusterClient")
  143. }
  144. sentinelAddrs := make([]string, len(failoverOpt.SentinelAddrs))
  145. copy(sentinelAddrs, failoverOpt.SentinelAddrs)
  146. rand.Shuffle(len(sentinelAddrs), func(i, j int) {
  147. sentinelAddrs[i], sentinelAddrs[j] = sentinelAddrs[j], sentinelAddrs[i]
  148. })
  149. failover := &sentinelFailover{
  150. opt: failoverOpt,
  151. sentinelAddrs: sentinelAddrs,
  152. }
  153. opt := failoverOpt.clientOptions()
  154. opt.Dialer = masterSlaveDialer(failover)
  155. opt.init()
  156. connPool := newConnPool(opt)
  157. failover.mu.Lock()
  158. failover.onFailover = func(ctx context.Context, addr string) {
  159. _ = connPool.Filter(func(cn *pool.Conn) bool {
  160. return cn.RemoteAddr().String() != addr
  161. })
  162. }
  163. failover.mu.Unlock()
  164. c := Client{
  165. baseClient: newBaseClient(opt, connPool),
  166. ctx: context.Background(),
  167. }
  168. c.cmdable = c.Process
  169. c.onClose = failover.Close
  170. return &c
  171. }
  172. func masterSlaveDialer(
  173. failover *sentinelFailover,
  174. ) func(ctx context.Context, network, addr string) (net.Conn, error) {
  175. return func(ctx context.Context, network, _ string) (net.Conn, error) {
  176. var addr string
  177. var err error
  178. if failover.opt.SlaveOnly {
  179. addr, err = failover.RandomSlaveAddr(ctx)
  180. } else {
  181. addr, err = failover.MasterAddr(ctx)
  182. if err == nil {
  183. failover.trySwitchMaster(ctx, addr)
  184. }
  185. }
  186. if err != nil {
  187. return nil, err
  188. }
  189. if failover.opt.Dialer != nil {
  190. return failover.opt.Dialer(ctx, network, addr)
  191. }
  192. netDialer := &net.Dialer{
  193. Timeout: failover.opt.DialTimeout,
  194. KeepAlive: 5 * time.Minute,
  195. }
  196. if failover.opt.TLSConfig == nil {
  197. return netDialer.DialContext(ctx, network, addr)
  198. }
  199. return tls.DialWithDialer(netDialer, network, addr, failover.opt.TLSConfig)
  200. }
  201. }
  202. //------------------------------------------------------------------------------
  203. // SentinelClient is a client for a Redis Sentinel.
  204. type SentinelClient struct {
  205. *baseClient
  206. hooks
  207. ctx context.Context
  208. }
  209. func NewSentinelClient(opt *Options) *SentinelClient {
  210. opt.init()
  211. c := &SentinelClient{
  212. baseClient: &baseClient{
  213. opt: opt,
  214. connPool: newConnPool(opt),
  215. },
  216. ctx: context.Background(),
  217. }
  218. return c
  219. }
  220. func (c *SentinelClient) Context() context.Context {
  221. return c.ctx
  222. }
  223. func (c *SentinelClient) WithContext(ctx context.Context) *SentinelClient {
  224. if ctx == nil {
  225. panic("nil context")
  226. }
  227. clone := *c
  228. clone.ctx = ctx
  229. return &clone
  230. }
  231. func (c *SentinelClient) Process(ctx context.Context, cmd Cmder) error {
  232. return c.hooks.process(ctx, cmd, c.baseClient.process)
  233. }
  234. func (c *SentinelClient) pubSub() *PubSub {
  235. pubsub := &PubSub{
  236. opt: c.opt,
  237. newConn: func(ctx context.Context, channels []string) (*pool.Conn, error) {
  238. return c.newConn(ctx)
  239. },
  240. closeConn: c.connPool.CloseConn,
  241. }
  242. pubsub.init()
  243. return pubsub
  244. }
  245. // Ping is used to test if a connection is still alive, or to
  246. // measure latency.
  247. func (c *SentinelClient) Ping(ctx context.Context) *StringCmd {
  248. cmd := NewStringCmd(ctx, "ping")
  249. _ = c.Process(ctx, cmd)
  250. return cmd
  251. }
  252. // Subscribe subscribes the client to the specified channels.
  253. // Channels can be omitted to create empty subscription.
  254. func (c *SentinelClient) Subscribe(ctx context.Context, channels ...string) *PubSub {
  255. pubsub := c.pubSub()
  256. if len(channels) > 0 {
  257. _ = pubsub.Subscribe(ctx, channels...)
  258. }
  259. return pubsub
  260. }
  261. // PSubscribe subscribes the client to the given patterns.
  262. // Patterns can be omitted to create empty subscription.
  263. func (c *SentinelClient) PSubscribe(ctx context.Context, channels ...string) *PubSub {
  264. pubsub := c.pubSub()
  265. if len(channels) > 0 {
  266. _ = pubsub.PSubscribe(ctx, channels...)
  267. }
  268. return pubsub
  269. }
  270. func (c *SentinelClient) GetMasterAddrByName(ctx context.Context, name string) *StringSliceCmd {
  271. cmd := NewStringSliceCmd(ctx, "sentinel", "get-master-addr-by-name", name)
  272. _ = c.Process(ctx, cmd)
  273. return cmd
  274. }
  275. func (c *SentinelClient) Sentinels(ctx context.Context, name string) *SliceCmd {
  276. cmd := NewSliceCmd(ctx, "sentinel", "sentinels", name)
  277. _ = c.Process(ctx, cmd)
  278. return cmd
  279. }
  280. // Failover forces a failover as if the master was not reachable, and without
  281. // asking for agreement to other Sentinels.
  282. func (c *SentinelClient) Failover(ctx context.Context, name string) *StatusCmd {
  283. cmd := NewStatusCmd(ctx, "sentinel", "failover", name)
  284. _ = c.Process(ctx, cmd)
  285. return cmd
  286. }
  287. // Reset resets all the masters with matching name. The pattern argument is a
  288. // glob-style pattern. The reset process clears any previous state in a master
  289. // (including a failover in progress), and removes every slave and sentinel
  290. // already discovered and associated with the master.
  291. func (c *SentinelClient) Reset(ctx context.Context, pattern string) *IntCmd {
  292. cmd := NewIntCmd(ctx, "sentinel", "reset", pattern)
  293. _ = c.Process(ctx, cmd)
  294. return cmd
  295. }
  296. // FlushConfig forces Sentinel to rewrite its configuration on disk, including
  297. // the current Sentinel state.
  298. func (c *SentinelClient) FlushConfig(ctx context.Context) *StatusCmd {
  299. cmd := NewStatusCmd(ctx, "sentinel", "flushconfig")
  300. _ = c.Process(ctx, cmd)
  301. return cmd
  302. }
  303. // Master shows the state and info of the specified master.
  304. func (c *SentinelClient) Master(ctx context.Context, name string) *StringStringMapCmd {
  305. cmd := NewStringStringMapCmd(ctx, "sentinel", "master", name)
  306. _ = c.Process(ctx, cmd)
  307. return cmd
  308. }
  309. // Masters shows a list of monitored masters and their state.
  310. func (c *SentinelClient) Masters(ctx context.Context) *SliceCmd {
  311. cmd := NewSliceCmd(ctx, "sentinel", "masters")
  312. _ = c.Process(ctx, cmd)
  313. return cmd
  314. }
  315. // Slaves shows a list of slaves for the specified master and their state.
  316. func (c *SentinelClient) Slaves(ctx context.Context, name string) *SliceCmd {
  317. cmd := NewSliceCmd(ctx, "sentinel", "slaves", name)
  318. _ = c.Process(ctx, cmd)
  319. return cmd
  320. }
  321. // CkQuorum checks if the current Sentinel configuration is able to reach the
  322. // quorum needed to failover a master, and the majority needed to authorize the
  323. // failover. This command should be used in monitoring systems to check if a
  324. // Sentinel deployment is ok.
  325. func (c *SentinelClient) CkQuorum(ctx context.Context, name string) *StringCmd {
  326. cmd := NewStringCmd(ctx, "sentinel", "ckquorum", name)
  327. _ = c.Process(ctx, cmd)
  328. return cmd
  329. }
  330. // Monitor tells the Sentinel to start monitoring a new master with the specified
  331. // name, ip, port, and quorum.
  332. func (c *SentinelClient) Monitor(ctx context.Context, name, ip, port, quorum string) *StringCmd {
  333. cmd := NewStringCmd(ctx, "sentinel", "monitor", name, ip, port, quorum)
  334. _ = c.Process(ctx, cmd)
  335. return cmd
  336. }
  337. // Set is used in order to change configuration parameters of a specific master.
  338. func (c *SentinelClient) Set(ctx context.Context, name, option, value string) *StringCmd {
  339. cmd := NewStringCmd(ctx, "sentinel", "set", name, option, value)
  340. _ = c.Process(ctx, cmd)
  341. return cmd
  342. }
  343. // Remove is used in order to remove the specified master: the master will no
  344. // longer be monitored, and will totally be removed from the internal state of
  345. // the Sentinel.
  346. func (c *SentinelClient) Remove(ctx context.Context, name string) *StringCmd {
  347. cmd := NewStringCmd(ctx, "sentinel", "remove", name)
  348. _ = c.Process(ctx, cmd)
  349. return cmd
  350. }
  351. //------------------------------------------------------------------------------
  352. type sentinelFailover struct {
  353. opt *FailoverOptions
  354. sentinelAddrs []string
  355. onFailover func(ctx context.Context, addr string)
  356. onUpdate func(ctx context.Context)
  357. mu sync.RWMutex
  358. _masterAddr string
  359. sentinel *SentinelClient
  360. pubsub *PubSub
  361. }
  362. func (c *sentinelFailover) Close() error {
  363. c.mu.Lock()
  364. defer c.mu.Unlock()
  365. if c.sentinel != nil {
  366. return c.closeSentinel()
  367. }
  368. return nil
  369. }
  370. func (c *sentinelFailover) closeSentinel() error {
  371. firstErr := c.pubsub.Close()
  372. c.pubsub = nil
  373. err := c.sentinel.Close()
  374. if err != nil && firstErr == nil {
  375. firstErr = err
  376. }
  377. c.sentinel = nil
  378. return firstErr
  379. }
  380. func (c *sentinelFailover) RandomSlaveAddr(ctx context.Context) (string, error) {
  381. if c.opt == nil {
  382. return "", errors.New("opt is nil")
  383. }
  384. addresses, err := c.slaveAddrs(ctx, false)
  385. if err != nil {
  386. return "", err
  387. }
  388. if len(addresses) == 0 && c.opt.UseDisconnectedSlaves {
  389. addresses, err = c.slaveAddrs(ctx, true)
  390. if err != nil {
  391. return "", err
  392. }
  393. }
  394. if len(addresses) == 0 {
  395. return c.MasterAddr(ctx)
  396. }
  397. return addresses[rand.Intn(len(addresses))], nil
  398. }
  399. func (c *sentinelFailover) MasterAddr(ctx context.Context) (string, error) {
  400. c.mu.RLock()
  401. sentinel := c.sentinel
  402. c.mu.RUnlock()
  403. if sentinel != nil {
  404. addr := c.getMasterAddr(ctx, sentinel)
  405. if addr != "" {
  406. return addr, nil
  407. }
  408. }
  409. c.mu.Lock()
  410. defer c.mu.Unlock()
  411. if c.sentinel != nil {
  412. addr := c.getMasterAddr(ctx, c.sentinel)
  413. if addr != "" {
  414. return addr, nil
  415. }
  416. _ = c.closeSentinel()
  417. }
  418. for i, sentinelAddr := range c.sentinelAddrs {
  419. sentinel := NewSentinelClient(c.opt.sentinelOptions(sentinelAddr))
  420. masterAddr, err := sentinel.GetMasterAddrByName(ctx, c.opt.MasterName).Result()
  421. if err != nil {
  422. internal.Logger.Printf(ctx, "sentinel: GetMasterAddrByName master=%q failed: %s",
  423. c.opt.MasterName, err)
  424. _ = sentinel.Close()
  425. continue
  426. }
  427. // Push working sentinel to the top.
  428. c.sentinelAddrs[0], c.sentinelAddrs[i] = c.sentinelAddrs[i], c.sentinelAddrs[0]
  429. c.setSentinel(ctx, sentinel)
  430. addr := net.JoinHostPort(masterAddr[0], masterAddr[1])
  431. return addr, nil
  432. }
  433. return "", errors.New("redis: all sentinels specified in configuration are unreachable")
  434. }
  435. func (c *sentinelFailover) slaveAddrs(ctx context.Context, useDisconnected bool) ([]string, error) {
  436. c.mu.RLock()
  437. sentinel := c.sentinel
  438. c.mu.RUnlock()
  439. if sentinel != nil {
  440. addrs := c.getSlaveAddrs(ctx, sentinel)
  441. if len(addrs) > 0 {
  442. return addrs, nil
  443. }
  444. }
  445. c.mu.Lock()
  446. defer c.mu.Unlock()
  447. if c.sentinel != nil {
  448. addrs := c.getSlaveAddrs(ctx, c.sentinel)
  449. if len(addrs) > 0 {
  450. return addrs, nil
  451. }
  452. _ = c.closeSentinel()
  453. }
  454. var sentinelReachable bool
  455. for i, sentinelAddr := range c.sentinelAddrs {
  456. sentinel := NewSentinelClient(c.opt.sentinelOptions(sentinelAddr))
  457. slaves, err := sentinel.Slaves(ctx, c.opt.MasterName).Result()
  458. if err != nil {
  459. internal.Logger.Printf(ctx, "sentinel: Slaves master=%q failed: %s",
  460. c.opt.MasterName, err)
  461. _ = sentinel.Close()
  462. continue
  463. }
  464. sentinelReachable = true
  465. addrs := parseSlaveAddrs(slaves, useDisconnected)
  466. if len(addrs) == 0 {
  467. continue
  468. }
  469. // Push working sentinel to the top.
  470. c.sentinelAddrs[0], c.sentinelAddrs[i] = c.sentinelAddrs[i], c.sentinelAddrs[0]
  471. c.setSentinel(ctx, sentinel)
  472. return addrs, nil
  473. }
  474. if sentinelReachable {
  475. return []string{}, nil
  476. }
  477. return []string{}, errors.New("redis: all sentinels specified in configuration are unreachable")
  478. }
  479. func (c *sentinelFailover) getMasterAddr(ctx context.Context, sentinel *SentinelClient) string {
  480. addr, err := sentinel.GetMasterAddrByName(ctx, c.opt.MasterName).Result()
  481. if err != nil {
  482. internal.Logger.Printf(ctx, "sentinel: GetMasterAddrByName name=%q failed: %s",
  483. c.opt.MasterName, err)
  484. return ""
  485. }
  486. return net.JoinHostPort(addr[0], addr[1])
  487. }
  488. func (c *sentinelFailover) getSlaveAddrs(ctx context.Context, sentinel *SentinelClient) []string {
  489. addrs, err := sentinel.Slaves(ctx, c.opt.MasterName).Result()
  490. if err != nil {
  491. internal.Logger.Printf(ctx, "sentinel: Slaves name=%q failed: %s",
  492. c.opt.MasterName, err)
  493. return []string{}
  494. }
  495. return parseSlaveAddrs(addrs, false)
  496. }
  497. func parseSlaveAddrs(addrs []interface{}, keepDisconnected bool) []string {
  498. nodes := make([]string, 0, len(addrs))
  499. for _, node := range addrs {
  500. ip := ""
  501. port := ""
  502. flags := []string{}
  503. lastkey := ""
  504. isDown := false
  505. for _, key := range node.([]interface{}) {
  506. switch lastkey {
  507. case "ip":
  508. ip = key.(string)
  509. case "port":
  510. port = key.(string)
  511. case "flags":
  512. flags = strings.Split(key.(string), ",")
  513. }
  514. lastkey = key.(string)
  515. }
  516. for _, flag := range flags {
  517. switch flag {
  518. case "s_down", "o_down":
  519. isDown = true
  520. case "disconnected":
  521. if !keepDisconnected {
  522. isDown = true
  523. }
  524. }
  525. }
  526. if !isDown {
  527. nodes = append(nodes, net.JoinHostPort(ip, port))
  528. }
  529. }
  530. return nodes
  531. }
  532. func (c *sentinelFailover) trySwitchMaster(ctx context.Context, addr string) {
  533. c.mu.RLock()
  534. currentAddr := c._masterAddr //nolint:ifshort
  535. c.mu.RUnlock()
  536. if addr == currentAddr {
  537. return
  538. }
  539. c.mu.Lock()
  540. defer c.mu.Unlock()
  541. if addr == c._masterAddr {
  542. return
  543. }
  544. c._masterAddr = addr
  545. internal.Logger.Printf(ctx, "sentinel: new master=%q addr=%q",
  546. c.opt.MasterName, addr)
  547. if c.onFailover != nil {
  548. c.onFailover(ctx, addr)
  549. }
  550. }
  551. func (c *sentinelFailover) setSentinel(ctx context.Context, sentinel *SentinelClient) {
  552. if c.sentinel != nil {
  553. panic("not reached")
  554. }
  555. c.sentinel = sentinel
  556. c.discoverSentinels(ctx)
  557. c.pubsub = sentinel.Subscribe(ctx, "+switch-master", "+slave-reconf-done")
  558. go c.listen(c.pubsub)
  559. }
  560. func (c *sentinelFailover) discoverSentinels(ctx context.Context) {
  561. sentinels, err := c.sentinel.Sentinels(ctx, c.opt.MasterName).Result()
  562. if err != nil {
  563. internal.Logger.Printf(ctx, "sentinel: Sentinels master=%q failed: %s", c.opt.MasterName, err)
  564. return
  565. }
  566. for _, sentinel := range sentinels {
  567. vals := sentinel.([]interface{})
  568. var ip, port string
  569. for i := 0; i < len(vals); i += 2 {
  570. key := vals[i].(string)
  571. switch key {
  572. case "ip":
  573. ip = vals[i+1].(string)
  574. case "port":
  575. port = vals[i+1].(string)
  576. }
  577. }
  578. if ip != "" && port != "" {
  579. sentinelAddr := net.JoinHostPort(ip, port)
  580. if !contains(c.sentinelAddrs, sentinelAddr) {
  581. internal.Logger.Printf(ctx, "sentinel: discovered new sentinel=%q for master=%q",
  582. sentinelAddr, c.opt.MasterName)
  583. c.sentinelAddrs = append(c.sentinelAddrs, sentinelAddr)
  584. }
  585. }
  586. }
  587. }
  588. func (c *sentinelFailover) listen(pubsub *PubSub) {
  589. ctx := context.TODO()
  590. if c.onUpdate != nil {
  591. c.onUpdate(ctx)
  592. }
  593. ch := pubsub.Channel()
  594. for msg := range ch {
  595. if msg.Channel == "+switch-master" {
  596. parts := strings.Split(msg.Payload, " ")
  597. if parts[0] != c.opt.MasterName {
  598. internal.Logger.Printf(pubsub.getContext(), "sentinel: ignore addr for master=%q", parts[0])
  599. continue
  600. }
  601. addr := net.JoinHostPort(parts[3], parts[4])
  602. c.trySwitchMaster(pubsub.getContext(), addr)
  603. }
  604. if c.onUpdate != nil {
  605. c.onUpdate(ctx)
  606. }
  607. }
  608. }
  609. func contains(slice []string, str string) bool {
  610. for _, s := range slice {
  611. if s == str {
  612. return true
  613. }
  614. }
  615. return false
  616. }
  617. //------------------------------------------------------------------------------
  618. // NewFailoverClusterClient returns a client that supports routing read-only commands
  619. // to a slave node.
  620. func NewFailoverClusterClient(failoverOpt *FailoverOptions) *ClusterClient {
  621. sentinelAddrs := make([]string, len(failoverOpt.SentinelAddrs))
  622. copy(sentinelAddrs, failoverOpt.SentinelAddrs)
  623. failover := &sentinelFailover{
  624. opt: failoverOpt,
  625. sentinelAddrs: sentinelAddrs,
  626. }
  627. opt := failoverOpt.clusterOptions()
  628. opt.ClusterSlots = func(ctx context.Context) ([]ClusterSlot, error) {
  629. masterAddr, err := failover.MasterAddr(ctx)
  630. if err != nil {
  631. return nil, err
  632. }
  633. nodes := []ClusterNode{{
  634. Addr: masterAddr,
  635. }}
  636. slaveAddrs, err := failover.slaveAddrs(ctx, false)
  637. if err != nil {
  638. return nil, err
  639. }
  640. for _, slaveAddr := range slaveAddrs {
  641. nodes = append(nodes, ClusterNode{
  642. Addr: slaveAddr,
  643. })
  644. }
  645. slots := []ClusterSlot{
  646. {
  647. Start: 0,
  648. End: 16383,
  649. Nodes: nodes,
  650. },
  651. }
  652. return slots, nil
  653. }
  654. c := NewClusterClient(opt)
  655. failover.mu.Lock()
  656. failover.onUpdate = func(ctx context.Context) {
  657. c.ReloadState(ctx)
  658. }
  659. failover.mu.Unlock()
  660. return c
  661. }