github.com/polarismesh/polaris@v1.17.8/common/hash/ketama.go (about)

     1  /**
     2   * Tencent is pleased to support the open source community by making Polaris available.
     3   *
     4   * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
     5   *
     6   * Licensed under the BSD 3-Clause License (the "License");
     7   * you may not use this file except in compliance with the License.
     8   * You may obtain a copy of the License at
     9   *
    10   * https://opensource.org/licenses/BSD-3-Clause
    11   *
    12   * Unless required by applicable law or agreed to in writing, software distributed
    13   * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
    14   * CONDITIONS OF ANY KIND, either express or implied. See the License for the
    15   * specific language governing permissions and limitations under the License.
    16   */
    17  
    18  package hash
    19  
    20  import (
    21  	"crypto/sha1"
    22  	"fmt"
    23  	"sort"
    24  )
    25  
    26  // Bucket single bucket of hash ring
    27  type Bucket struct {
    28  	Host   string
    29  	Weight uint32
    30  }
    31  
    32  type continuumPoint struct {
    33  	bucket Bucket
    34  	point  uint
    35  }
    36  
    37  // Continuum consistent hash ring
    38  type Continuum struct {
    39  	ring points
    40  }
    41  
    42  type points []continuumPoint
    43  
    44  // Less 比较大小
    45  func (c points) Less(i, j int) bool { return c[i].point < c[j].point }
    46  
    47  // Len 长度
    48  func (c points) Len() int { return len(c) }
    49  
    50  // Swap 交换
    51  func (c points) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
    52  
    53  func sha1Digest(in string) []byte {
    54  	h := sha1.New()
    55  	h.Write([]byte(in))
    56  	return h.Sum(nil)
    57  }
    58  
    59  func HashString(in string) uint {
    60  	digest := sha1Digest(in)
    61  	return uint(digest[3])<<24 | uint(digest[2])<<16 | uint(digest[1])<<8 | uint(digest[0])
    62  }
    63  
    64  // New hash ring
    65  func New(buckets map[Bucket]bool) *Continuum {
    66  	numBuckets := len(buckets)
    67  
    68  	if numBuckets == 0 {
    69  		return nil
    70  	}
    71  
    72  	ring := make(points, 0, numBuckets*160)
    73  
    74  	var totalWeight uint32
    75  	for bucket := range buckets {
    76  		totalWeight += bucket.Weight
    77  	}
    78  
    79  	for bucket := range buckets {
    80  		pct := float64(bucket.Weight) / float64(totalWeight)
    81  
    82  		// this is the equivalent of C's promotion rules, but in Go, to maintain exact compatibility with the C library
    83  		limit := int(pct * 40.0 * float64(numBuckets))
    84  
    85  		for k := 0; k < limit; k++ {
    86  			/* 40 hashes, 4 numbers per hash = 160 points per bucket */
    87  			ss := fmt.Sprintf("%s-%d", bucket.Host, k)
    88  			digest := sha1Digest(ss)
    89  
    90  			for h := 0; h < 4; h++ {
    91  				point := continuumPoint{
    92  					point:  uint(digest[3+h*4])<<24 | uint(digest[2+h*4])<<16 | uint(digest[1+h*4])<<8 | uint(digest[h*4]),
    93  					bucket: bucket,
    94  				}
    95  				ring = append(ring, point)
    96  			}
    97  		}
    98  	}
    99  
   100  	sort.Sort(ring)
   101  
   102  	return &Continuum{
   103  		ring: ring,
   104  	}
   105  }
   106  
   107  // Hash hash string to lookup node
   108  func (c *Continuum) Hash(h uint) string {
   109  	if len(c.ring) == 0 {
   110  		return ""
   111  	}
   112  
   113  	// the above md5 is way more expensive than this branch
   114  	var i uint
   115  	i = uint(sort.Search(len(c.ring), func(i int) bool { return c.ring[i].point >= h }))
   116  	if i >= uint(len(c.ring)) {
   117  		i = 0
   118  	}
   119  
   120  	return c.ring[i].bucket.Host
   121  }