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.

ring.go 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. package redis
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "math/rand"
  7. "strconv"
  8. "sync"
  9. "sync/atomic"
  10. "time"
  11. "github.com/go-redis/redis/internal"
  12. "github.com/go-redis/redis/internal/consistenthash"
  13. "github.com/go-redis/redis/internal/hashtag"
  14. "github.com/go-redis/redis/internal/pool"
  15. )
  16. // Hash is type of hash function used in consistent hash.
  17. type Hash consistenthash.Hash
  18. var errRingShardsDown = errors.New("redis: all ring shards are down")
  19. // RingOptions are used to configure a ring client and should be
  20. // passed to NewRing.
  21. type RingOptions struct {
  22. // Map of name => host:port addresses of ring shards.
  23. Addrs map[string]string
  24. // Frequency of PING commands sent to check shards availability.
  25. // Shard is considered down after 3 subsequent failed checks.
  26. HeartbeatFrequency time.Duration
  27. // Hash function used in consistent hash.
  28. // Default is crc32.ChecksumIEEE.
  29. Hash Hash
  30. // Number of replicas in consistent hash.
  31. // Default is 100 replicas.
  32. //
  33. // Higher number of replicas will provide less deviation, that is keys will be
  34. // distributed to nodes more evenly.
  35. //
  36. // Following is deviation for common nreplicas:
  37. // --------------------------------------------------------
  38. // | nreplicas | standard error | 99% confidence interval |
  39. // | 10 | 0.3152 | (0.37, 1.98) |
  40. // | 100 | 0.0997 | (0.76, 1.28) |
  41. // | 1000 | 0.0316 | (0.92, 1.09) |
  42. // --------------------------------------------------------
  43. //
  44. // See https://arxiv.org/abs/1406.2294 for reference
  45. HashReplicas int
  46. // Following options are copied from Options struct.
  47. OnConnect func(*Conn) error
  48. DB int
  49. Password string
  50. MaxRetries int
  51. MinRetryBackoff time.Duration
  52. MaxRetryBackoff time.Duration
  53. DialTimeout time.Duration
  54. ReadTimeout time.Duration
  55. WriteTimeout time.Duration
  56. PoolSize int
  57. MinIdleConns int
  58. MaxConnAge time.Duration
  59. PoolTimeout time.Duration
  60. IdleTimeout time.Duration
  61. IdleCheckFrequency time.Duration
  62. }
  63. func (opt *RingOptions) init() {
  64. if opt.HeartbeatFrequency == 0 {
  65. opt.HeartbeatFrequency = 500 * time.Millisecond
  66. }
  67. if opt.HashReplicas == 0 {
  68. opt.HashReplicas = 100
  69. }
  70. switch opt.MinRetryBackoff {
  71. case -1:
  72. opt.MinRetryBackoff = 0
  73. case 0:
  74. opt.MinRetryBackoff = 8 * time.Millisecond
  75. }
  76. switch opt.MaxRetryBackoff {
  77. case -1:
  78. opt.MaxRetryBackoff = 0
  79. case 0:
  80. opt.MaxRetryBackoff = 512 * time.Millisecond
  81. }
  82. }
  83. func (opt *RingOptions) clientOptions() *Options {
  84. return &Options{
  85. OnConnect: opt.OnConnect,
  86. DB: opt.DB,
  87. Password: opt.Password,
  88. DialTimeout: opt.DialTimeout,
  89. ReadTimeout: opt.ReadTimeout,
  90. WriteTimeout: opt.WriteTimeout,
  91. PoolSize: opt.PoolSize,
  92. MinIdleConns: opt.MinIdleConns,
  93. MaxConnAge: opt.MaxConnAge,
  94. PoolTimeout: opt.PoolTimeout,
  95. IdleTimeout: opt.IdleTimeout,
  96. IdleCheckFrequency: opt.IdleCheckFrequency,
  97. }
  98. }
  99. //------------------------------------------------------------------------------
  100. type ringShard struct {
  101. Client *Client
  102. down int32
  103. }
  104. func (shard *ringShard) String() string {
  105. var state string
  106. if shard.IsUp() {
  107. state = "up"
  108. } else {
  109. state = "down"
  110. }
  111. return fmt.Sprintf("%s is %s", shard.Client, state)
  112. }
  113. func (shard *ringShard) IsDown() bool {
  114. const threshold = 3
  115. return atomic.LoadInt32(&shard.down) >= threshold
  116. }
  117. func (shard *ringShard) IsUp() bool {
  118. return !shard.IsDown()
  119. }
  120. // Vote votes to set shard state and returns true if state was changed.
  121. func (shard *ringShard) Vote(up bool) bool {
  122. if up {
  123. changed := shard.IsDown()
  124. atomic.StoreInt32(&shard.down, 0)
  125. return changed
  126. }
  127. if shard.IsDown() {
  128. return false
  129. }
  130. atomic.AddInt32(&shard.down, 1)
  131. return shard.IsDown()
  132. }
  133. //------------------------------------------------------------------------------
  134. type ringShards struct {
  135. opt *RingOptions
  136. mu sync.RWMutex
  137. hash *consistenthash.Map
  138. shards map[string]*ringShard // read only
  139. list []*ringShard // read only
  140. len int
  141. closed bool
  142. }
  143. func newRingShards(opt *RingOptions) *ringShards {
  144. return &ringShards{
  145. opt: opt,
  146. hash: newConsistentHash(opt),
  147. shards: make(map[string]*ringShard),
  148. }
  149. }
  150. func (c *ringShards) Add(name string, cl *Client) {
  151. shard := &ringShard{Client: cl}
  152. c.hash.Add(name)
  153. c.shards[name] = shard
  154. c.list = append(c.list, shard)
  155. }
  156. func (c *ringShards) List() []*ringShard {
  157. c.mu.RLock()
  158. list := c.list
  159. c.mu.RUnlock()
  160. return list
  161. }
  162. func (c *ringShards) Hash(key string) string {
  163. c.mu.RLock()
  164. hash := c.hash.Get(key)
  165. c.mu.RUnlock()
  166. return hash
  167. }
  168. func (c *ringShards) GetByKey(key string) (*ringShard, error) {
  169. key = hashtag.Key(key)
  170. c.mu.RLock()
  171. if c.closed {
  172. c.mu.RUnlock()
  173. return nil, pool.ErrClosed
  174. }
  175. hash := c.hash.Get(key)
  176. if hash == "" {
  177. c.mu.RUnlock()
  178. return nil, errRingShardsDown
  179. }
  180. shard := c.shards[hash]
  181. c.mu.RUnlock()
  182. return shard, nil
  183. }
  184. func (c *ringShards) GetByHash(name string) (*ringShard, error) {
  185. if name == "" {
  186. return c.Random()
  187. }
  188. c.mu.RLock()
  189. shard := c.shards[name]
  190. c.mu.RUnlock()
  191. return shard, nil
  192. }
  193. func (c *ringShards) Random() (*ringShard, error) {
  194. return c.GetByKey(strconv.Itoa(rand.Int()))
  195. }
  196. // heartbeat monitors state of each shard in the ring.
  197. func (c *ringShards) Heartbeat(frequency time.Duration) {
  198. ticker := time.NewTicker(frequency)
  199. defer ticker.Stop()
  200. for range ticker.C {
  201. var rebalance bool
  202. c.mu.RLock()
  203. if c.closed {
  204. c.mu.RUnlock()
  205. break
  206. }
  207. shards := c.list
  208. c.mu.RUnlock()
  209. for _, shard := range shards {
  210. err := shard.Client.Ping().Err()
  211. if shard.Vote(err == nil || err == pool.ErrPoolTimeout) {
  212. internal.Logf("ring shard state changed: %s", shard)
  213. rebalance = true
  214. }
  215. }
  216. if rebalance {
  217. c.rebalance()
  218. }
  219. }
  220. }
  221. // rebalance removes dead shards from the Ring.
  222. func (c *ringShards) rebalance() {
  223. hash := newConsistentHash(c.opt)
  224. var shardsNum int
  225. for name, shard := range c.shards {
  226. if shard.IsUp() {
  227. hash.Add(name)
  228. shardsNum++
  229. }
  230. }
  231. c.mu.Lock()
  232. c.hash = hash
  233. c.len = shardsNum
  234. c.mu.Unlock()
  235. }
  236. func (c *ringShards) Len() int {
  237. c.mu.RLock()
  238. l := c.len
  239. c.mu.RUnlock()
  240. return l
  241. }
  242. func (c *ringShards) Close() error {
  243. c.mu.Lock()
  244. defer c.mu.Unlock()
  245. if c.closed {
  246. return nil
  247. }
  248. c.closed = true
  249. var firstErr error
  250. for _, shard := range c.shards {
  251. if err := shard.Client.Close(); err != nil && firstErr == nil {
  252. firstErr = err
  253. }
  254. }
  255. c.hash = nil
  256. c.shards = nil
  257. c.list = nil
  258. return firstErr
  259. }
  260. //------------------------------------------------------------------------------
  261. // Ring is a Redis client that uses consistent hashing to distribute
  262. // keys across multiple Redis servers (shards). It's safe for
  263. // concurrent use by multiple goroutines.
  264. //
  265. // Ring monitors the state of each shard and removes dead shards from
  266. // the ring. When a shard comes online it is added back to the ring. This
  267. // gives you maximum availability and partition tolerance, but no
  268. // consistency between different shards or even clients. Each client
  269. // uses shards that are available to the client and does not do any
  270. // coordination when shard state is changed.
  271. //
  272. // Ring should be used when you need multiple Redis servers for caching
  273. // and can tolerate losing data when one of the servers dies.
  274. // Otherwise you should use Redis Cluster.
  275. type Ring struct {
  276. cmdable
  277. ctx context.Context
  278. opt *RingOptions
  279. shards *ringShards
  280. cmdsInfoCache *cmdsInfoCache
  281. process func(Cmder) error
  282. processPipeline func([]Cmder) error
  283. }
  284. func NewRing(opt *RingOptions) *Ring {
  285. opt.init()
  286. ring := &Ring{
  287. opt: opt,
  288. shards: newRingShards(opt),
  289. }
  290. ring.cmdsInfoCache = newCmdsInfoCache(ring.cmdsInfo)
  291. ring.process = ring.defaultProcess
  292. ring.processPipeline = ring.defaultProcessPipeline
  293. ring.cmdable.setProcessor(ring.Process)
  294. for name, addr := range opt.Addrs {
  295. clopt := opt.clientOptions()
  296. clopt.Addr = addr
  297. ring.shards.Add(name, NewClient(clopt))
  298. }
  299. go ring.shards.Heartbeat(opt.HeartbeatFrequency)
  300. return ring
  301. }
  302. func (c *Ring) Context() context.Context {
  303. if c.ctx != nil {
  304. return c.ctx
  305. }
  306. return context.Background()
  307. }
  308. func (c *Ring) WithContext(ctx context.Context) *Ring {
  309. if ctx == nil {
  310. panic("nil context")
  311. }
  312. c2 := c.copy()
  313. c2.ctx = ctx
  314. return c2
  315. }
  316. func (c *Ring) copy() *Ring {
  317. cp := *c
  318. return &cp
  319. }
  320. // Options returns read-only Options that were used to create the client.
  321. func (c *Ring) Options() *RingOptions {
  322. return c.opt
  323. }
  324. func (c *Ring) retryBackoff(attempt int) time.Duration {
  325. return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff)
  326. }
  327. // PoolStats returns accumulated connection pool stats.
  328. func (c *Ring) PoolStats() *PoolStats {
  329. shards := c.shards.List()
  330. var acc PoolStats
  331. for _, shard := range shards {
  332. s := shard.Client.connPool.Stats()
  333. acc.Hits += s.Hits
  334. acc.Misses += s.Misses
  335. acc.Timeouts += s.Timeouts
  336. acc.TotalConns += s.TotalConns
  337. acc.IdleConns += s.IdleConns
  338. }
  339. return &acc
  340. }
  341. // Len returns the current number of shards in the ring.
  342. func (c *Ring) Len() int {
  343. return c.shards.Len()
  344. }
  345. // Subscribe subscribes the client to the specified channels.
  346. func (c *Ring) Subscribe(channels ...string) *PubSub {
  347. if len(channels) == 0 {
  348. panic("at least one channel is required")
  349. }
  350. shard, err := c.shards.GetByKey(channels[0])
  351. if err != nil {
  352. // TODO: return PubSub with sticky error
  353. panic(err)
  354. }
  355. return shard.Client.Subscribe(channels...)
  356. }
  357. // PSubscribe subscribes the client to the given patterns.
  358. func (c *Ring) PSubscribe(channels ...string) *PubSub {
  359. if len(channels) == 0 {
  360. panic("at least one channel is required")
  361. }
  362. shard, err := c.shards.GetByKey(channels[0])
  363. if err != nil {
  364. // TODO: return PubSub with sticky error
  365. panic(err)
  366. }
  367. return shard.Client.PSubscribe(channels...)
  368. }
  369. // ForEachShard concurrently calls the fn on each live shard in the ring.
  370. // It returns the first error if any.
  371. func (c *Ring) ForEachShard(fn func(client *Client) error) error {
  372. shards := c.shards.List()
  373. var wg sync.WaitGroup
  374. errCh := make(chan error, 1)
  375. for _, shard := range shards {
  376. if shard.IsDown() {
  377. continue
  378. }
  379. wg.Add(1)
  380. go func(shard *ringShard) {
  381. defer wg.Done()
  382. err := fn(shard.Client)
  383. if err != nil {
  384. select {
  385. case errCh <- err:
  386. default:
  387. }
  388. }
  389. }(shard)
  390. }
  391. wg.Wait()
  392. select {
  393. case err := <-errCh:
  394. return err
  395. default:
  396. return nil
  397. }
  398. }
  399. func (c *Ring) cmdsInfo() (map[string]*CommandInfo, error) {
  400. shards := c.shards.List()
  401. firstErr := errRingShardsDown
  402. for _, shard := range shards {
  403. cmdsInfo, err := shard.Client.Command().Result()
  404. if err == nil {
  405. return cmdsInfo, nil
  406. }
  407. if firstErr == nil {
  408. firstErr = err
  409. }
  410. }
  411. return nil, firstErr
  412. }
  413. func (c *Ring) cmdInfo(name string) *CommandInfo {
  414. cmdsInfo, err := c.cmdsInfoCache.Get()
  415. if err != nil {
  416. return nil
  417. }
  418. info := cmdsInfo[name]
  419. if info == nil {
  420. internal.Logf("info for cmd=%s not found", name)
  421. }
  422. return info
  423. }
  424. func (c *Ring) cmdShard(cmd Cmder) (*ringShard, error) {
  425. cmdInfo := c.cmdInfo(cmd.Name())
  426. pos := cmdFirstKeyPos(cmd, cmdInfo)
  427. if pos == 0 {
  428. return c.shards.Random()
  429. }
  430. firstKey := cmd.stringArg(pos)
  431. return c.shards.GetByKey(firstKey)
  432. }
  433. // Do creates a Cmd from the args and processes the cmd.
  434. func (c *Ring) Do(args ...interface{}) *Cmd {
  435. cmd := NewCmd(args...)
  436. c.Process(cmd)
  437. return cmd
  438. }
  439. func (c *Ring) WrapProcess(
  440. fn func(oldProcess func(cmd Cmder) error) func(cmd Cmder) error,
  441. ) {
  442. c.process = fn(c.process)
  443. }
  444. func (c *Ring) Process(cmd Cmder) error {
  445. return c.process(cmd)
  446. }
  447. func (c *Ring) defaultProcess(cmd Cmder) error {
  448. for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
  449. if attempt > 0 {
  450. time.Sleep(c.retryBackoff(attempt))
  451. }
  452. shard, err := c.cmdShard(cmd)
  453. if err != nil {
  454. cmd.setErr(err)
  455. return err
  456. }
  457. err = shard.Client.Process(cmd)
  458. if err == nil {
  459. return nil
  460. }
  461. if !internal.IsRetryableError(err, cmd.readTimeout() == nil) {
  462. return err
  463. }
  464. }
  465. return cmd.Err()
  466. }
  467. func (c *Ring) Pipeline() Pipeliner {
  468. pipe := Pipeline{
  469. exec: c.processPipeline,
  470. }
  471. pipe.cmdable.setProcessor(pipe.Process)
  472. return &pipe
  473. }
  474. func (c *Ring) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) {
  475. return c.Pipeline().Pipelined(fn)
  476. }
  477. func (c *Ring) WrapProcessPipeline(
  478. fn func(oldProcess func([]Cmder) error) func([]Cmder) error,
  479. ) {
  480. c.processPipeline = fn(c.processPipeline)
  481. }
  482. func (c *Ring) defaultProcessPipeline(cmds []Cmder) error {
  483. cmdsMap := make(map[string][]Cmder)
  484. for _, cmd := range cmds {
  485. cmdInfo := c.cmdInfo(cmd.Name())
  486. hash := cmd.stringArg(cmdFirstKeyPos(cmd, cmdInfo))
  487. if hash != "" {
  488. hash = c.shards.Hash(hashtag.Key(hash))
  489. }
  490. cmdsMap[hash] = append(cmdsMap[hash], cmd)
  491. }
  492. for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
  493. if attempt > 0 {
  494. time.Sleep(c.retryBackoff(attempt))
  495. }
  496. var mu sync.Mutex
  497. var failedCmdsMap map[string][]Cmder
  498. var wg sync.WaitGroup
  499. for hash, cmds := range cmdsMap {
  500. wg.Add(1)
  501. go func(hash string, cmds []Cmder) {
  502. defer wg.Done()
  503. shard, err := c.shards.GetByHash(hash)
  504. if err != nil {
  505. setCmdsErr(cmds, err)
  506. return
  507. }
  508. cn, err := shard.Client.getConn()
  509. if err != nil {
  510. setCmdsErr(cmds, err)
  511. return
  512. }
  513. canRetry, err := shard.Client.pipelineProcessCmds(cn, cmds)
  514. shard.Client.releaseConnStrict(cn, err)
  515. if canRetry && internal.IsRetryableError(err, true) {
  516. mu.Lock()
  517. if failedCmdsMap == nil {
  518. failedCmdsMap = make(map[string][]Cmder)
  519. }
  520. failedCmdsMap[hash] = cmds
  521. mu.Unlock()
  522. }
  523. }(hash, cmds)
  524. }
  525. wg.Wait()
  526. if len(failedCmdsMap) == 0 {
  527. break
  528. }
  529. cmdsMap = failedCmdsMap
  530. }
  531. return cmdsFirstErr(cmds)
  532. }
  533. func (c *Ring) TxPipeline() Pipeliner {
  534. panic("not implemented")
  535. }
  536. func (c *Ring) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) {
  537. panic("not implemented")
  538. }
  539. // Close closes the ring client, releasing any open resources.
  540. //
  541. // It is rare to Close a Ring, as the Ring is meant to be long-lived
  542. // and shared between many goroutines.
  543. func (c *Ring) Close() error {
  544. return c.shards.Close()
  545. }
  546. func newConsistentHash(opt *RingOptions) *consistenthash.Map {
  547. return consistenthash.New(opt.HashReplicas, consistenthash.Hash(opt.Hash))
  548. }