http urls monitor.

ring.go 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636
  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 constistent 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 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. processPipeline func([]Cmder) error
  282. }
  283. func NewRing(opt *RingOptions) *Ring {
  284. opt.init()
  285. ring := &Ring{
  286. opt: opt,
  287. shards: newRingShards(opt),
  288. }
  289. ring.cmdsInfoCache = newCmdsInfoCache(ring.cmdsInfo)
  290. ring.processPipeline = ring.defaultProcessPipeline
  291. ring.cmdable.setProcessor(ring.Process)
  292. for name, addr := range opt.Addrs {
  293. clopt := opt.clientOptions()
  294. clopt.Addr = addr
  295. ring.shards.Add(name, NewClient(clopt))
  296. }
  297. go ring.shards.Heartbeat(opt.HeartbeatFrequency)
  298. return ring
  299. }
  300. func (c *Ring) Context() context.Context {
  301. if c.ctx != nil {
  302. return c.ctx
  303. }
  304. return context.Background()
  305. }
  306. func (c *Ring) WithContext(ctx context.Context) *Ring {
  307. if ctx == nil {
  308. panic("nil context")
  309. }
  310. c2 := c.copy()
  311. c2.ctx = ctx
  312. return c2
  313. }
  314. func (c *Ring) copy() *Ring {
  315. cp := *c
  316. return &cp
  317. }
  318. // Options returns read-only Options that were used to create the client.
  319. func (c *Ring) Options() *RingOptions {
  320. return c.opt
  321. }
  322. func (c *Ring) retryBackoff(attempt int) time.Duration {
  323. return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff)
  324. }
  325. // PoolStats returns accumulated connection pool stats.
  326. func (c *Ring) PoolStats() *PoolStats {
  327. shards := c.shards.List()
  328. var acc PoolStats
  329. for _, shard := range shards {
  330. s := shard.Client.connPool.Stats()
  331. acc.Hits += s.Hits
  332. acc.Misses += s.Misses
  333. acc.Timeouts += s.Timeouts
  334. acc.TotalConns += s.TotalConns
  335. acc.IdleConns += s.IdleConns
  336. }
  337. return &acc
  338. }
  339. // Len returns the current number of shards in the ring.
  340. func (c *Ring) Len() int {
  341. return c.shards.Len()
  342. }
  343. // Subscribe subscribes the client to the specified channels.
  344. func (c *Ring) Subscribe(channels ...string) *PubSub {
  345. if len(channels) == 0 {
  346. panic("at least one channel is required")
  347. }
  348. shard, err := c.shards.GetByKey(channels[0])
  349. if err != nil {
  350. // TODO: return PubSub with sticky error
  351. panic(err)
  352. }
  353. return shard.Client.Subscribe(channels...)
  354. }
  355. // PSubscribe subscribes the client to the given patterns.
  356. func (c *Ring) PSubscribe(channels ...string) *PubSub {
  357. if len(channels) == 0 {
  358. panic("at least one channel is required")
  359. }
  360. shard, err := c.shards.GetByKey(channels[0])
  361. if err != nil {
  362. // TODO: return PubSub with sticky error
  363. panic(err)
  364. }
  365. return shard.Client.PSubscribe(channels...)
  366. }
  367. // ForEachShard concurrently calls the fn on each live shard in the ring.
  368. // It returns the first error if any.
  369. func (c *Ring) ForEachShard(fn func(client *Client) error) error {
  370. shards := c.shards.List()
  371. var wg sync.WaitGroup
  372. errCh := make(chan error, 1)
  373. for _, shard := range shards {
  374. if shard.IsDown() {
  375. continue
  376. }
  377. wg.Add(1)
  378. go func(shard *ringShard) {
  379. defer wg.Done()
  380. err := fn(shard.Client)
  381. if err != nil {
  382. select {
  383. case errCh <- err:
  384. default:
  385. }
  386. }
  387. }(shard)
  388. }
  389. wg.Wait()
  390. select {
  391. case err := <-errCh:
  392. return err
  393. default:
  394. return nil
  395. }
  396. }
  397. func (c *Ring) cmdsInfo() (map[string]*CommandInfo, error) {
  398. shards := c.shards.List()
  399. firstErr := errRingShardsDown
  400. for _, shard := range shards {
  401. cmdsInfo, err := shard.Client.Command().Result()
  402. if err == nil {
  403. return cmdsInfo, nil
  404. }
  405. if firstErr == nil {
  406. firstErr = err
  407. }
  408. }
  409. return nil, firstErr
  410. }
  411. func (c *Ring) cmdInfo(name string) *CommandInfo {
  412. cmdsInfo, err := c.cmdsInfoCache.Get()
  413. if err != nil {
  414. return nil
  415. }
  416. info := cmdsInfo[name]
  417. if info == nil {
  418. internal.Logf("info for cmd=%s not found", name)
  419. }
  420. return info
  421. }
  422. func (c *Ring) cmdShard(cmd Cmder) (*ringShard, error) {
  423. cmdInfo := c.cmdInfo(cmd.Name())
  424. pos := cmdFirstKeyPos(cmd, cmdInfo)
  425. if pos == 0 {
  426. return c.shards.Random()
  427. }
  428. firstKey := cmd.stringArg(pos)
  429. return c.shards.GetByKey(firstKey)
  430. }
  431. // Do creates a Cmd from the args and processes the cmd.
  432. func (c *Ring) Do(args ...interface{}) *Cmd {
  433. cmd := NewCmd(args...)
  434. c.Process(cmd)
  435. return cmd
  436. }
  437. func (c *Ring) WrapProcess(
  438. fn func(oldProcess func(cmd Cmder) error) func(cmd Cmder) error,
  439. ) {
  440. c.ForEachShard(func(c *Client) error {
  441. c.WrapProcess(fn)
  442. return nil
  443. })
  444. }
  445. func (c *Ring) Process(cmd Cmder) error {
  446. shard, err := c.cmdShard(cmd)
  447. if err != nil {
  448. cmd.setErr(err)
  449. return err
  450. }
  451. return shard.Client.Process(cmd)
  452. }
  453. func (c *Ring) Pipeline() Pipeliner {
  454. pipe := Pipeline{
  455. exec: c.processPipeline,
  456. }
  457. pipe.cmdable.setProcessor(pipe.Process)
  458. return &pipe
  459. }
  460. func (c *Ring) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) {
  461. return c.Pipeline().Pipelined(fn)
  462. }
  463. func (c *Ring) WrapProcessPipeline(
  464. fn func(oldProcess func([]Cmder) error) func([]Cmder) error,
  465. ) {
  466. c.processPipeline = fn(c.processPipeline)
  467. }
  468. func (c *Ring) defaultProcessPipeline(cmds []Cmder) error {
  469. cmdsMap := make(map[string][]Cmder)
  470. for _, cmd := range cmds {
  471. cmdInfo := c.cmdInfo(cmd.Name())
  472. hash := cmd.stringArg(cmdFirstKeyPos(cmd, cmdInfo))
  473. if hash != "" {
  474. hash = c.shards.Hash(hashtag.Key(hash))
  475. }
  476. cmdsMap[hash] = append(cmdsMap[hash], cmd)
  477. }
  478. for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
  479. if attempt > 0 {
  480. time.Sleep(c.retryBackoff(attempt))
  481. }
  482. var failedCmdsMap map[string][]Cmder
  483. for hash, cmds := range cmdsMap {
  484. shard, err := c.shards.GetByHash(hash)
  485. if err != nil {
  486. setCmdsErr(cmds, err)
  487. continue
  488. }
  489. cn, err := shard.Client.getConn()
  490. if err != nil {
  491. setCmdsErr(cmds, err)
  492. continue
  493. }
  494. canRetry, err := shard.Client.pipelineProcessCmds(cn, cmds)
  495. if err == nil || internal.IsRedisError(err) {
  496. shard.Client.connPool.Put(cn)
  497. continue
  498. }
  499. shard.Client.connPool.Remove(cn)
  500. if canRetry && internal.IsRetryableError(err, true) {
  501. if failedCmdsMap == nil {
  502. failedCmdsMap = make(map[string][]Cmder)
  503. }
  504. failedCmdsMap[hash] = cmds
  505. }
  506. }
  507. if len(failedCmdsMap) == 0 {
  508. break
  509. }
  510. cmdsMap = failedCmdsMap
  511. }
  512. return cmdsFirstErr(cmds)
  513. }
  514. func (c *Ring) TxPipeline() Pipeliner {
  515. panic("not implemented")
  516. }
  517. func (c *Ring) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) {
  518. panic("not implemented")
  519. }
  520. // Close closes the ring client, releasing any open resources.
  521. //
  522. // It is rare to Close a Ring, as the Ring is meant to be long-lived
  523. // and shared between many goroutines.
  524. func (c *Ring) Close() error {
  525. return c.shards.Close()
  526. }
  527. func newConsistentHash(opt *RingOptions) *consistenthash.Map {
  528. return consistenthash.New(opt.HashReplicas, consistenthash.Hash(opt.Hash))
  529. }