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 }