trpc.group/trpc-go/trpc-go@v1.0.3/naming/loadbalance/consistenthash/consistenthash.go (about)

     1  //
     2  //
     3  // Tencent is pleased to support the open source community by making tRPC available.
     4  //
     5  // Copyright (C) 2023 THL A29 Limited, a Tencent company.
     6  // All rights reserved.
     7  //
     8  // If you have downloaded a copy of the tRPC source code from Tencent,
     9  // please note that tRPC source code is licensed under the  Apache 2.0 License,
    10  // A copy of the Apache 2.0 License is included in this file.
    11  //
    12  //
    13  
    14  // Package consistenthash provides consistent hash utilities.
    15  package consistenthash
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"sort"
    21  	"strconv"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/cespare/xxhash"
    26  	"trpc.group/trpc-go/trpc-go/naming/loadbalance"
    27  	"trpc.group/trpc-go/trpc-go/naming/registry"
    28  )
    29  
    30  // defaultReplicas is the default virtual node coefficient.
    31  const (
    32  	defaultReplicas int = 100
    33  	prime               = 16777619
    34  )
    35  
    36  // Hash is the hash function type.
    37  type Hash func(data []byte) uint64
    38  
    39  // defaultHashFunc uses CRC32 as the default.
    40  var defaultHashFunc Hash = xxhash.Sum64
    41  
    42  func init() {
    43  	loadbalance.Register("consistent_hash", NewConsistentHash())
    44  }
    45  
    46  // NewConsistentHash creates a new ConsistentHash.
    47  func NewConsistentHash() *ConsistentHash {
    48  	return &ConsistentHash{
    49  		pickers:  new(sync.Map),
    50  		hashFunc: defaultHashFunc,
    51  	}
    52  }
    53  
    54  // NewCustomConsistentHash creates a new ConsistentHash with custom hash function.
    55  func NewCustomConsistentHash(hashFunc Hash) *ConsistentHash {
    56  	return &ConsistentHash{
    57  		pickers:  new(sync.Map),
    58  		hashFunc: hashFunc,
    59  	}
    60  }
    61  
    62  // ConsistentHash defines the consistent hash.
    63  type ConsistentHash struct {
    64  	pickers  *sync.Map
    65  	interval time.Duration
    66  	hashFunc Hash
    67  }
    68  
    69  // Select implements loadbalance.LoadBalancer.
    70  func (ch *ConsistentHash) Select(serviceName string, list []*registry.Node,
    71  	opt ...loadbalance.Option) (*registry.Node, error) {
    72  	opts := &loadbalance.Options{}
    73  	for _, o := range opt {
    74  		o(opts)
    75  	}
    76  	p, ok := ch.pickers.Load(serviceName)
    77  	if ok {
    78  		return p.(*chPicker).Pick(list, opts)
    79  	}
    80  
    81  	newPicker := &chPicker{
    82  		interval: ch.interval,
    83  		hashFunc: ch.hashFunc,
    84  	}
    85  	v, ok := ch.pickers.LoadOrStore(serviceName, newPicker)
    86  	if !ok {
    87  		return newPicker.Pick(list, opts)
    88  	}
    89  	return v.(*chPicker).Pick(list, opts)
    90  }
    91  
    92  // chPicker is the picker of the consistent hash.
    93  type chPicker struct {
    94  	list     []*registry.Node
    95  	hashFunc Hash
    96  	keys     Uint64Slice                 // a hash slice of sorted node list, it's length is #(node)*replica
    97  	hashMap  map[uint64][]*registry.Node // a map which keeps hash-nodes maps
    98  	mu       sync.Mutex
    99  	interval time.Duration
   100  }
   101  
   102  // Pick picks a node.
   103  func (p *chPicker) Pick(list []*registry.Node, opts *loadbalance.Options) (*registry.Node, error) {
   104  	if len(list) == 0 {
   105  		return nil, loadbalance.ErrNoServerAvailable
   106  	}
   107  	// Returns error if opts.Key is not provided.
   108  	if opts.Key == "" {
   109  		return nil, errors.New("missing key")
   110  	}
   111  	tmpKeys, tmpMap, err := p.updateState(list, opts.Replicas)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	hash := p.hashFunc([]byte(opts.Key))
   116  	// Find the best matched node by binary search. Node A is better than B if A's hash value is
   117  	// greater than B's.
   118  	idx := sort.Search(len(tmpKeys), func(i int) bool { return tmpKeys[i] >= hash })
   119  	if idx == len(tmpKeys) {
   120  		idx = 0
   121  	}
   122  	nodes, ok := tmpMap[tmpKeys[idx]]
   123  	if !ok {
   124  		return nil, loadbalance.ErrNoServerAvailable
   125  	}
   126  	switch len(nodes) {
   127  	case 1:
   128  		return nodes[0], nil
   129  	default:
   130  		innerIndex := p.hashFunc(innerRepr(opts.Key))
   131  		pos := int(innerIndex % uint64(len(nodes)))
   132  		return nodes[pos], nil
   133  	}
   134  }
   135  
   136  // updateState recalculates list every so often if nodes changed.
   137  func (p *chPicker) updateState(list []*registry.Node, replicas int) (Uint64Slice, map[uint64][]*registry.Node, error) {
   138  	p.mu.Lock()
   139  	defer p.mu.Unlock()
   140  	// if node list is the same as last update, there is no need to update hash ring.
   141  	if isNodeSliceEqualBCE(p.list, list) {
   142  		return p.keys, p.hashMap, nil
   143  	}
   144  	actualReplicas := replicas
   145  	if actualReplicas <= 0 {
   146  		actualReplicas = defaultReplicas
   147  	}
   148  	// update node list.
   149  	p.list = list
   150  	p.hashMap = make(map[uint64][]*registry.Node)
   151  	p.keys = make(Uint64Slice, len(list)*actualReplicas)
   152  	for i, node := range list {
   153  		if node == nil {
   154  			// node must not be nil.
   155  			return nil, nil, errors.New("list contains nil node")
   156  		}
   157  		for j := 0; j < actualReplicas; j++ {
   158  			hash := p.hashFunc([]byte(strconv.Itoa(j) + node.Address))
   159  			p.keys[i*(actualReplicas)+j] = hash
   160  			p.hashMap[hash] = append(p.hashMap[hash], node)
   161  		}
   162  	}
   163  	sort.Sort(p.keys)
   164  	return p.keys, p.hashMap, nil
   165  }
   166  
   167  // Uint64Slice defines uint64 slice.
   168  type Uint64Slice []uint64
   169  
   170  // Len returns the length of the slice.
   171  func (s Uint64Slice) Len() int {
   172  	return len(s)
   173  }
   174  
   175  // Less returns whether the value at i is less than j.
   176  func (s Uint64Slice) Less(i, j int) bool {
   177  	return s[i] < s[j]
   178  }
   179  
   180  // Swap swaps values between i and j.
   181  func (s Uint64Slice) Swap(i, j int) {
   182  	s[i], s[j] = s[j], s[i]
   183  }
   184  
   185  // isNodeSliceEqualBCE check whether two node list is equal by BCE.
   186  func isNodeSliceEqualBCE(a, b []*registry.Node) bool {
   187  	if len(a) != len(b) {
   188  		return false
   189  	}
   190  	if (a == nil) != (b == nil) {
   191  		return false
   192  	}
   193  	b = b[:len(a)]
   194  	for i, v := range a {
   195  		if (v == nil) != (b[i] == nil) {
   196  			return false
   197  		}
   198  		if v.Address != b[i].Address {
   199  			return false
   200  		}
   201  	}
   202  	return true
   203  }
   204  
   205  func innerRepr(key interface{}) []byte {
   206  	return []byte(fmt.Sprintf("%d:%v", prime, key))
   207  }