dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/balancer/clusterimpl/picker.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  /*
    19   *
    20   * Copyright 2020 gRPC authors.
    21   *
    22   */
    23  
    24  package clusterimpl
    25  
    26  import (
    27  	orcapb "github.com/cncf/xds/go/xds/data/orca/v3"
    28  
    29  	dubbogoLogger "github.com/dubbogo/gost/log/logger"
    30  
    31  	"google.golang.org/grpc/balancer"
    32  
    33  	"google.golang.org/grpc/codes"
    34  
    35  	"google.golang.org/grpc/connectivity"
    36  
    37  	"google.golang.org/grpc/status"
    38  )
    39  
    40  import (
    41  	"dubbo.apache.org/dubbo-go/v3/xds/client"
    42  	"dubbo.apache.org/dubbo-go/v3/xds/client/load"
    43  	"dubbo.apache.org/dubbo-go/v3/xds/utils/wrr"
    44  )
    45  
    46  // NewRandomWRR is used when calculating drops. It's exported so that tests can
    47  // override it.
    48  var NewRandomWRR = wrr.NewRandom
    49  
    50  const million = 1000000
    51  
    52  type dropper struct {
    53  	category string
    54  	w        wrr.WRR
    55  }
    56  
    57  // greatest common divisor (GCD) via Euclidean algorithm
    58  func gcd(a, b uint32) uint32 {
    59  	for b != 0 {
    60  		t := b
    61  		b = a % b
    62  		a = t
    63  	}
    64  	return a
    65  }
    66  
    67  func newDropper(c DropConfig) *dropper {
    68  	w := NewRandomWRR()
    69  	gcdv := gcd(c.RequestsPerMillion, million)
    70  	// Return true for RequestPerMillion, false for the rest.
    71  	w.Add(true, int64(c.RequestsPerMillion/gcdv))
    72  	w.Add(false, int64((million-c.RequestsPerMillion)/gcdv))
    73  
    74  	return &dropper{
    75  		category: c.Category,
    76  		w:        w,
    77  	}
    78  }
    79  
    80  func (d *dropper) drop() (ret bool) {
    81  	return d.w.Next().(bool)
    82  }
    83  
    84  const (
    85  	serverLoadCPUName    = "cpu_utilization"
    86  	serverLoadMemoryName = "mem_utilization"
    87  )
    88  
    89  // loadReporter wraps the methods from the loadStore that are used here.
    90  type loadReporter interface {
    91  	CallStarted(locality string)
    92  	CallFinished(locality string, err error)
    93  	CallServerLoad(locality, name string, val float64)
    94  	CallDropped(locality string)
    95  }
    96  
    97  // Picker implements RPC drop, circuit breaking drop and load reporting.
    98  type picker struct {
    99  	drops     []*dropper
   100  	s         balancer.State
   101  	loadStore loadReporter
   102  	counter   *client.ClusterRequestsCounter
   103  	countMax  uint32
   104  }
   105  
   106  func newPicker(s balancer.State, config *dropConfigs, loadStore load.PerClusterReporter) *picker {
   107  	return &picker{
   108  		drops:     config.drops,
   109  		s:         s,
   110  		loadStore: loadStore,
   111  		counter:   config.requestCounter,
   112  		countMax:  config.requestCountMax,
   113  	}
   114  }
   115  
   116  func (d *picker) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
   117  	// Don't drop unless the inner picker is READY. Similar to
   118  	// https://github.com/grpc/grpc-go/issues/2622.
   119  	if d.s.ConnectivityState == connectivity.Ready {
   120  		// Check if this RPC should be dropped by category.
   121  		for _, dp := range d.drops {
   122  			if dp.drop() {
   123  				if d.loadStore != nil {
   124  					d.loadStore.CallDropped(dp.category)
   125  				}
   126  				return balancer.PickResult{}, status.Errorf(codes.Unavailable, "RPC is dropped")
   127  			}
   128  		}
   129  	}
   130  
   131  	// Check if this RPC should be dropped by circuit breaking.
   132  	if d.counter != nil {
   133  		if err := d.counter.StartRequest(d.countMax); err != nil {
   134  			// Drops by circuit breaking are reported with empty category. They
   135  			// will be reported only in total drops, but not in per category.
   136  			if d.loadStore != nil {
   137  				d.loadStore.CallDropped("")
   138  			}
   139  			return balancer.PickResult{}, status.Errorf(codes.Unavailable, err.Error())
   140  		}
   141  	}
   142  
   143  	var lIDStr string
   144  	pr, err := d.s.Picker.Pick(info)
   145  	if scw, ok := pr.SubConn.(*scWrapper); ok {
   146  		// This OK check also covers the case err!=nil, because SubConn will be
   147  		// nil.
   148  		pr.SubConn = scw.SubConn
   149  		var e error
   150  		// If locality ID isn't found in the wrapper, an empty locality ID will
   151  		// be used.
   152  		lIDStr, e = scw.localityID().ToString()
   153  		if e != nil {
   154  			dubbogoLogger.Infof("failed to marshal LocalityID: %#v, loads won't be reported", scw.localityID())
   155  		}
   156  	}
   157  
   158  	if err != nil {
   159  		if d.counter != nil {
   160  			// Release one request count if this pick fails.
   161  			d.counter.EndRequest()
   162  		}
   163  		return pr, err
   164  	}
   165  
   166  	if d.loadStore != nil {
   167  		d.loadStore.CallStarted(lIDStr)
   168  		oldDone := pr.Done
   169  		pr.Done = func(info balancer.DoneInfo) {
   170  			if oldDone != nil {
   171  				oldDone(info)
   172  			}
   173  			d.loadStore.CallFinished(lIDStr, info.Err)
   174  
   175  			load, ok := info.ServerLoad.(*orcapb.OrcaLoadReport)
   176  			if !ok {
   177  				return
   178  			}
   179  			d.loadStore.CallServerLoad(lIDStr, serverLoadCPUName, load.CpuUtilization)
   180  			d.loadStore.CallServerLoad(lIDStr, serverLoadMemoryName, load.MemUtilization)
   181  			for n, c := range load.RequestCost {
   182  				d.loadStore.CallServerLoad(lIDStr, n, c)
   183  			}
   184  			for n, c := range load.Utilization {
   185  				d.loadStore.CallServerLoad(lIDStr, n, c)
   186  			}
   187  		}
   188  	}
   189  
   190  	if d.counter != nil {
   191  		// Update Done() so that when the RPC finishes, the request count will
   192  		// be released.
   193  		oldDone := pr.Done
   194  		pr.Done = func(doneInfo balancer.DoneInfo) {
   195  			d.counter.EndRequest()
   196  			if oldDone != nil {
   197  				oldDone(doneInfo)
   198  			}
   199  		}
   200  	}
   201  
   202  	return pr, err
   203  }