github.com/andy2046/gopie@v0.7.0/pkg/ringhash/ringhash.go (about)

     1  // Package ringhash provides a ring hash implementation.
     2  package ringhash
     3  
     4  import (
     5  	"errors"
     6  	"hash/crc64"
     7  	"io"
     8  	"math"
     9  	"sort"
    10  	"strconv"
    11  	"sync"
    12  )
    13  
    14  type (
    15  	// Hash is the hash function.
    16  	Hash func(key string) uint64
    17  
    18  	// Node is the node in the ring.
    19  	Node struct {
    20  		Name string
    21  		Load int64
    22  	}
    23  
    24  	// Ring is the data store for keys hash map.
    25  	Ring struct {
    26  		hashFn          Hash
    27  		replicas        int
    28  		balancingFactor float64
    29  		hashKeyMap      map[uint64]string
    30  		hashes          []uint64
    31  		keyLoadMap      map[string]*Node
    32  		totalLoad       int64
    33  
    34  		mu sync.RWMutex
    35  	}
    36  
    37  	// Config is the config for hash ring.
    38  	Config struct {
    39  		HashFn          Hash
    40  		Replicas        int
    41  		BalancingFactor float64
    42  	}
    43  
    44  	// Option applies config to Config.
    45  	Option = func(*Config) error
    46  )
    47  
    48  var (
    49  	// hashCRC64 uses the 64-bit Cyclic Redundancy Check (CRC-64) with the ECMA polynomial.
    50  	hashCRC64 = crc64.New(crc64.MakeTable(crc64.ECMA))
    51  	// ErrNoNode when there is no node added into the hash ring.
    52  	ErrNoNode = errors.New("no node added")
    53  	// ErrNodeNotFound when no node found in LoadMap.
    54  	ErrNodeNotFound = errors.New("node not found in LoadMap")
    55  	// DefaultConfig is the default config for hash ring.
    56  	DefaultConfig = Config{
    57  		HashFn:          hash,
    58  		Replicas:        10,
    59  		BalancingFactor: 1.25,
    60  	}
    61  )
    62  
    63  func hash(key string) uint64 {
    64  	hashCRC64.Reset()
    65  	_, err := io.WriteString(hashCRC64, key)
    66  	if err != nil {
    67  		panic(err)
    68  	}
    69  	return hashCRC64.Sum64()
    70  }
    71  
    72  // New returns a new Ring.
    73  func New(options ...Option) *Ring {
    74  	c := DefaultConfig
    75  	setOption(&c, options...)
    76  	r := &Ring{
    77  		replicas:        c.Replicas,
    78  		balancingFactor: c.BalancingFactor,
    79  		hashFn:          c.HashFn,
    80  		hashKeyMap:      make(map[uint64]string),
    81  		hashes:          []uint64{},
    82  		keyLoadMap:      map[string]*Node{},
    83  	}
    84  	return r
    85  }
    86  
    87  // IsEmpty returns true if there is no node in the ring.
    88  func (r *Ring) IsEmpty() bool {
    89  	return len(r.hashKeyMap) == 0
    90  }
    91  
    92  // AddNode adds Node with key as name to the hash ring.
    93  func (r *Ring) AddNode(keys ...string) {
    94  	r.mu.Lock()
    95  	defer r.mu.Unlock()
    96  
    97  	for _, key := range keys {
    98  		if _, ok := r.keyLoadMap[key]; ok {
    99  			continue
   100  		}
   101  
   102  		r.keyLoadMap[key] = &Node{Name: key, Load: 0}
   103  
   104  		for i := 0; i < r.replicas; i++ {
   105  			h := r.hashFn(key + strconv.Itoa(i))
   106  			r.hashes = append(r.hashes, h)
   107  			r.hashKeyMap[h] = key
   108  		}
   109  	}
   110  
   111  	// sort hashes ascendingly
   112  	sort.Slice(r.hashes, func(i, j int) bool {
   113  		if r.hashes[i] < r.hashes[j] {
   114  			return true
   115  		}
   116  		return false
   117  	})
   118  }
   119  
   120  // GetNode returns the closest node in the hash ring to the provided key.
   121  func (r *Ring) GetNode(key string) (string, error) {
   122  	r.mu.RLock()
   123  	defer r.mu.RUnlock()
   124  
   125  	if r.IsEmpty() {
   126  		return "", ErrNoNode
   127  	}
   128  
   129  	h := r.hashFn(key)
   130  	idx := r.search(h)
   131  	return r.hashKeyMap[r.hashes[idx]], nil
   132  }
   133  
   134  // GetLeastNode uses consistent hashing with bounded loads to get the least loaded node.
   135  func (r *Ring) GetLeastNode(key string) (string, error) {
   136  	r.mu.RLock()
   137  	defer r.mu.RUnlock()
   138  
   139  	if r.IsEmpty() {
   140  		return "", ErrNoNode
   141  	}
   142  
   143  	h := r.hashFn(key)
   144  	idx := r.search(h)
   145  
   146  	i := idx
   147  	var count int
   148  	for {
   149  		count++
   150  		if count >= len(r.hashes) {
   151  			panic("not enough space to distribute load")
   152  		}
   153  		node := r.hashKeyMap[r.hashes[i]]
   154  		if r.loadOK(node) {
   155  			return node, nil
   156  		}
   157  		i++
   158  		if i >= len(r.hashKeyMap) {
   159  			i = 0
   160  		}
   161  	}
   162  }
   163  
   164  // UpdateLoad sets load of the given node to the given load.
   165  func (r *Ring) UpdateLoad(node string, load int64) {
   166  	r.mu.Lock()
   167  	defer r.mu.Unlock()
   168  
   169  	if _, ok := r.keyLoadMap[node]; !ok {
   170  		return
   171  	}
   172  	r.totalLoad -= r.keyLoadMap[node].Load
   173  	r.keyLoadMap[node].Load = load
   174  	r.totalLoad += load
   175  }
   176  
   177  // Add increases load of the given node by 1,
   178  // should only be used with GetLeast.
   179  func (r *Ring) Add(node string) bool {
   180  	r.mu.Lock()
   181  	defer r.mu.Unlock()
   182  
   183  	if _, ok := r.keyLoadMap[node]; !ok {
   184  		return false
   185  	}
   186  	r.keyLoadMap[node].Load++
   187  	r.totalLoad++
   188  	return true
   189  }
   190  
   191  // Done decreases load of the given node by 1,
   192  // should only be used with GetLeast.
   193  func (r *Ring) Done(node string) bool {
   194  	r.mu.Lock()
   195  	defer r.mu.Unlock()
   196  
   197  	if _, ok := r.keyLoadMap[node]; !ok {
   198  		return false
   199  	}
   200  	r.keyLoadMap[node].Load--
   201  	r.totalLoad--
   202  	return true
   203  }
   204  
   205  // RemoveNode deletes node from the hash ring.
   206  func (r *Ring) RemoveNode(node string) bool {
   207  	r.mu.Lock()
   208  	defer r.mu.Unlock()
   209  
   210  	if _, ok := r.keyLoadMap[node]; !ok {
   211  		return false
   212  	}
   213  
   214  	for i := 0; i < r.replicas; i++ {
   215  		h := r.hashFn(node + strconv.Itoa(i))
   216  		delete(r.hashKeyMap, h)
   217  		r.removeFromHashes(h)
   218  	}
   219  	delete(r.keyLoadMap, node)
   220  	return true
   221  }
   222  
   223  // Nodes returns the list of nodes in the hash ring.
   224  func (r *Ring) Nodes() (nodes []string) {
   225  	r.mu.RLock()
   226  	defer r.mu.RUnlock()
   227  
   228  	for k := range r.keyLoadMap {
   229  		nodes = append(nodes, k)
   230  	}
   231  	return
   232  }
   233  
   234  // Loads returns the loads of all the nodes in the hash ring.
   235  func (r *Ring) Loads() map[string]int64 {
   236  	r.mu.RLock()
   237  	defer r.mu.RUnlock()
   238  
   239  	loads := map[string]int64{}
   240  	for k, v := range r.keyLoadMap {
   241  		loads[k] = v.Load
   242  	}
   243  	return loads
   244  }
   245  
   246  // MaxLoad returns the maximum load for a single node in the hash ring,
   247  // which is (totalLoad/numberOfNodes)*balancingFactor.
   248  func (r *Ring) MaxLoad() int64 {
   249  	r.mu.RLock()
   250  	defer r.mu.RUnlock()
   251  
   252  	if r.totalLoad == 0 {
   253  		r.totalLoad = 1
   254  	}
   255  	var avgLoadPerNode float64
   256  	avgLoadPerNode = float64(r.totalLoad / int64(len(r.keyLoadMap)))
   257  	if avgLoadPerNode == 0 {
   258  		avgLoadPerNode = 1
   259  	}
   260  	avgLoadPerNode = math.Ceil(avgLoadPerNode * r.balancingFactor)
   261  	return int64(avgLoadPerNode)
   262  }
   263  
   264  func (r *Ring) removeFromHashes(h uint64) {
   265  	for i := 0; i < len(r.hashes); i++ {
   266  		if r.hashes[i] == h {
   267  			r.hashes = append(r.hashes[:i], r.hashes[i+1:]...)
   268  		}
   269  	}
   270  }
   271  
   272  func setOption(c *Config, options ...func(*Config) error) error {
   273  	for _, opt := range options {
   274  		if err := opt(c); err != nil {
   275  			return err
   276  		}
   277  	}
   278  	return nil
   279  }
   280  
   281  func (r *Ring) search(key uint64) int {
   282  	idx := sort.Search(len(r.hashes), func(i int) bool {
   283  		return r.hashes[i] >= key
   284  	})
   285  
   286  	if idx >= len(r.hashes) {
   287  		idx = 0
   288  	}
   289  	return idx
   290  }
   291  
   292  func (r *Ring) loadOK(key string) bool {
   293  	if r.totalLoad < 0 {
   294  		r.totalLoad = 0
   295  	}
   296  
   297  	var avgLoadPerNode float64
   298  	avgLoadPerNode = float64((r.totalLoad + 1) / int64(len(r.keyLoadMap)))
   299  	if avgLoadPerNode == 0 {
   300  		avgLoadPerNode = 1
   301  	}
   302  	avgLoadPerNode = math.Ceil(avgLoadPerNode * r.balancingFactor)
   303  
   304  	node, ok := r.keyLoadMap[key]
   305  	if !ok {
   306  		panic(ErrNodeNotFound)
   307  	}
   308  
   309  	if float64(node.Load)+1 <= avgLoadPerNode {
   310  		return true
   311  	}
   312  
   313  	return false
   314  }