github.com/uber/kraken@v0.1.4/lib/hashring/ring.go (about)

     1  // Copyright (c) 2016-2019 Uber Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  package hashring
    15  
    16  import (
    17  	"log"
    18  	"sync"
    19  	"time"
    20  
    21  	"github.com/uber/kraken/core"
    22  	"github.com/uber/kraken/lib/healthcheck"
    23  	"github.com/uber/kraken/lib/hostlist"
    24  	"github.com/uber/kraken/lib/hrw"
    25  	"github.com/uber/kraken/utils/stringset"
    26  )
    27  
    28  const _defaultWeight = 100
    29  
    30  // Watcher allows clients to watch the ring for changes. Whenever membership
    31  // changes, each registered Watcher is notified with the latest hosts.
    32  type Watcher interface {
    33  	Notify(latest stringset.Set)
    34  }
    35  
    36  // Ring is a rendezvous hashing ring which calculates an ordered replica set
    37  // of healthy addresses which own any given digest.
    38  //
    39  // Address membership within the ring is defined by a dynamic hostlist.List. On
    40  // top of that, replica sets are filtered by the health status of their addresses.
    41  // Membership and health status may be refreshed by using Monitor.
    42  //
    43  // Ring maintains the invariant that it is always non-empty and can always provide
    44  // locations, although in some scenarios the provided locations are not guaranteed
    45  // to be healthy (see Locations).
    46  type Ring interface {
    47  	Locations(d core.Digest) []string
    48  	Contains(addr string) bool
    49  	Monitor(stop <-chan struct{})
    50  	Refresh()
    51  }
    52  
    53  type ring struct {
    54  	config  Config
    55  	cluster hostlist.List
    56  	filter  healthcheck.Filter
    57  
    58  	mu      sync.RWMutex // Protects the following fields:
    59  	addrs   stringset.Set
    60  	hash    *hrw.RendezvousHash
    61  	healthy stringset.Set
    62  
    63  	watchers []Watcher
    64  }
    65  
    66  // Option allows setting custom parameters for ring.
    67  type Option func(*ring)
    68  
    69  // WithWatcher adds a watcher to the ring. Can be used multiple times.
    70  func WithWatcher(w Watcher) Option {
    71  	return func(r *ring) { r.watchers = append(r.watchers, w) }
    72  }
    73  
    74  // New creates a new Ring whose members are defined by cluster.
    75  func New(
    76  	config Config, cluster hostlist.List, filter healthcheck.Filter, opts ...Option) Ring {
    77  
    78  	config.applyDefaults()
    79  	r := &ring{
    80  		config:  config,
    81  		cluster: cluster,
    82  		filter:  filter,
    83  	}
    84  	for _, opt := range opts {
    85  		opt(r)
    86  	}
    87  	r.Refresh()
    88  	return r
    89  }
    90  
    91  // Locations returns an ordered replica set of healthy addresses which own d.
    92  // If all addresses in the replica set are unhealthy, then returns the next
    93  // healthy address. If all addresses in the ring are unhealthy, then returns
    94  // the first address which owns d (regardless of health). As such, Locations
    95  // always returns a non-empty list.
    96  func (r *ring) Locations(d core.Digest) []string {
    97  	r.mu.RLock()
    98  	defer r.mu.RUnlock()
    99  
   100  	nodes := r.hash.GetOrderedNodes(d.ShardID(), len(r.addrs))
   101  	if len(nodes) != len(r.addrs) {
   102  		// This should never happen.
   103  		log.Fatal("invariant violation: ordered hash nodes not equal to cluster size")
   104  	}
   105  
   106  	if len(r.healthy) == 0 {
   107  		return []string{nodes[0].Label}
   108  	}
   109  
   110  	var locs []string
   111  	for i := 0; i < len(nodes) && (len(locs) == 0 || i < r.config.MaxReplica); i++ {
   112  		addr := nodes[i].Label
   113  		if r.healthy.Has(addr) {
   114  			locs = append(locs, addr)
   115  		}
   116  	}
   117  	return locs
   118  }
   119  
   120  // Contains returns whether the ring contains addr.
   121  func (r *ring) Contains(addr string) bool {
   122  	r.mu.RLock()
   123  	defer r.mu.RUnlock()
   124  
   125  	return r.addrs.Has(addr)
   126  }
   127  
   128  // Monitor refreshes the ring at the configured interval. Blocks until the
   129  // provided stop channel is closed.
   130  func (r *ring) Monitor(stop <-chan struct{}) {
   131  	for {
   132  		select {
   133  		case <-stop:
   134  			return
   135  		case <-time.After(r.config.RefreshInterval):
   136  			r.Refresh()
   137  		}
   138  	}
   139  }
   140  
   141  // Refresh updates the membership and health information of r.
   142  func (r *ring) Refresh() {
   143  	latest := r.cluster.Resolve()
   144  
   145  	healthy := r.filter.Run(latest)
   146  
   147  	hash := r.hash
   148  	if !stringset.Equal(r.addrs, latest) {
   149  		// Membership has changed -- update hash nodes.
   150  		hash = hrw.NewRendezvousHash(hrw.Murmur3Hash, hrw.UInt64ToFloat64)
   151  		for addr := range latest {
   152  			hash.AddNode(addr, _defaultWeight)
   153  		}
   154  		// Notify watchers.
   155  		for _, w := range r.watchers {
   156  			w.Notify(latest.Copy())
   157  		}
   158  	}
   159  
   160  	r.mu.Lock()
   161  	r.addrs = latest
   162  	r.hash = hash
   163  	r.healthy = healthy
   164  	r.mu.Unlock()
   165  }