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 }