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  }