dubbo.apache.org/dubbo-go/v3@v3.1.1/cluster/loadbalance/consistenthashing/selector.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package consistenthashing 19 20 import ( 21 "crypto/md5" 22 "fmt" 23 "sort" 24 "strconv" 25 "strings" 26 ) 27 28 import ( 29 gxsort "github.com/dubbogo/gost/sort" 30 ) 31 32 import ( 33 "dubbo.apache.org/dubbo-go/v3/protocol" 34 ) 35 36 // selector implementation of Selector:get invoker based on load balancing strategy 37 type selector struct { 38 hashCode uint32 39 replicaNum int 40 virtualInvokers map[uint32]protocol.Invoker 41 keys gxsort.Uint32Slice 42 argumentIndex []int 43 } 44 45 func newSelector(invokers []protocol.Invoker, methodName string, 46 hashCode uint32) *selector { 47 48 selector := &selector{} 49 selector.virtualInvokers = make(map[uint32]protocol.Invoker) 50 selector.hashCode = hashCode 51 url := invokers[0].GetURL() 52 selector.replicaNum = url.GetMethodParamIntValue(methodName, HashNodes, 160) 53 indices := re.Split(url.GetMethodParam(methodName, HashArguments, "0"), -1) 54 for _, index := range indices { 55 i, err := strconv.Atoi(index) 56 if err != nil { 57 return nil 58 } 59 selector.argumentIndex = append(selector.argumentIndex, i) 60 } 61 for _, invoker := range invokers { 62 u := invoker.GetURL() 63 address := u.Ip + ":" + u.Port 64 for i := 0; i < selector.replicaNum/4; i++ { 65 digest := md5.Sum([]byte(address + strconv.Itoa(i))) 66 for j := 0; j < 4; j++ { 67 key := selector.hash(digest, j) 68 selector.keys = append(selector.keys, key) 69 selector.virtualInvokers[key] = invoker 70 } 71 } 72 } 73 sort.Sort(selector.keys) 74 return selector 75 } 76 77 // Select gets invoker based on load balancing strategy 78 func (c *selector) Select(invocation protocol.Invocation) protocol.Invoker { 79 key := c.toKey(invocation.Arguments()) 80 digest := md5.Sum([]byte(key)) 81 return c.selectForKey(c.hash(digest, 0)) 82 } 83 84 func (c *selector) toKey(args []interface{}) string { 85 var sb strings.Builder 86 for i := range c.argumentIndex { 87 if i >= 0 && i < len(args) { 88 _, _ = fmt.Fprint(&sb, args[i].(string)) 89 } 90 } 91 return sb.String() 92 } 93 94 func (c *selector) selectForKey(hash uint32) protocol.Invoker { 95 idx := sort.Search(len(c.keys), func(i int) bool { 96 return c.keys[i] >= hash 97 }) 98 if idx == len(c.keys) { 99 idx = 0 100 } 101 return c.virtualInvokers[c.keys[idx]] 102 } 103 104 func (c *selector) hash(digest [16]byte, i int) uint32 { 105 return (uint32(digest[3+i*4]&0xFF) << 24) | (uint32(digest[2+i*4]&0xFF) << 16) | 106 (uint32(digest[1+i*4]&0xFF) << 8) | uint32(digest[i*4]&0xFF)&0xFFFFFFF 107 }