gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/grpc/balancer/weightedtarget/weightedaggregator/aggregator.go (about) 1 /* 2 * 3 * Copyright 2020 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * 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 // Package weightedaggregator implements state aggregator for weighted_target 20 // balancer. 21 // 22 // This is a separate package so it can be shared by weighted_target and eds. 23 // The eds balancer will be refactored to use weighted_target directly. After 24 // that, all functions and structs in this package can be moved to package 25 // weightedtarget and unexported. 26 package weightedaggregator 27 28 import ( 29 "fmt" 30 "sync" 31 32 "gitee.com/ks-custle/core-gm/grpc/balancer" 33 "gitee.com/ks-custle/core-gm/grpc/balancer/base" 34 "gitee.com/ks-custle/core-gm/grpc/connectivity" 35 "gitee.com/ks-custle/core-gm/grpc/internal/grpclog" 36 "gitee.com/ks-custle/core-gm/grpc/internal/wrr" 37 ) 38 39 type weightedPickerState struct { 40 weight uint32 41 state balancer.State 42 // stateToAggregate is the connectivity state used only for state 43 // aggregation. It could be different from state.ConnectivityState. For 44 // example when a sub-balancer transitions from TransientFailure to 45 // connecting, state.ConnectivityState is Connecting, but stateToAggregate 46 // is still TransientFailure. 47 stateToAggregate connectivity.State 48 } 49 50 func (s *weightedPickerState) String() string { 51 return fmt.Sprintf("weight:%v,picker:%p,state:%v,stateToAggregate:%v", s.weight, s.state.Picker, s.state.ConnectivityState, s.stateToAggregate) 52 } 53 54 // Aggregator is the weighted balancer state aggregator. 55 type Aggregator struct { 56 cc balancer.ClientConn 57 logger *grpclog.PrefixLogger 58 newWRR func() wrr.WRR 59 60 mu sync.Mutex 61 // If started is false, no updates should be sent to the parent cc. A closed 62 // sub-balancer could still send pickers to this aggregator. This makes sure 63 // that no updates will be forwarded to parent when the whole balancer group 64 // and states aggregator is closed. 65 started bool 66 // All balancer IDs exist as keys in this map, even if balancer group is not 67 // started. 68 // 69 // If an ID is not in map, it's either removed or never added. 70 idToPickerState map[string]*weightedPickerState 71 } 72 73 // New creates a new weighted balancer state aggregator. 74 func New(cc balancer.ClientConn, logger *grpclog.PrefixLogger, newWRR func() wrr.WRR) *Aggregator { 75 return &Aggregator{ 76 cc: cc, 77 logger: logger, 78 newWRR: newWRR, 79 idToPickerState: make(map[string]*weightedPickerState), 80 } 81 } 82 83 // Start starts the aggregator. It can be called after Close to restart the 84 // aggretator. 85 func (wbsa *Aggregator) Start() { 86 wbsa.mu.Lock() 87 defer wbsa.mu.Unlock() 88 wbsa.started = true 89 } 90 91 // Stop stops the aggregator. When the aggregator is closed, it won't call 92 // parent ClientConn to update balancer state. 93 func (wbsa *Aggregator) Stop() { 94 wbsa.mu.Lock() 95 defer wbsa.mu.Unlock() 96 wbsa.started = false 97 wbsa.clearStates() 98 } 99 100 // Add adds a sub-balancer state with weight. It adds a place holder, and waits for 101 // the real sub-balancer to update state. 102 func (wbsa *Aggregator) Add(id string, weight uint32) { 103 wbsa.mu.Lock() 104 defer wbsa.mu.Unlock() 105 wbsa.idToPickerState[id] = &weightedPickerState{ 106 weight: weight, 107 // Start everything in CONNECTING, so if one of the sub-balancers 108 // reports TransientFailure, the RPCs will still wait for the other 109 // sub-balancers. 110 state: balancer.State{ 111 ConnectivityState: connectivity.Connecting, 112 Picker: base.NewErrPicker(balancer.ErrNoSubConnAvailable), 113 }, 114 stateToAggregate: connectivity.Connecting, 115 } 116 } 117 118 // Remove removes the sub-balancer state. Future updates from this sub-balancer, 119 // if any, will be ignored. 120 func (wbsa *Aggregator) Remove(id string) { 121 wbsa.mu.Lock() 122 defer wbsa.mu.Unlock() 123 if _, ok := wbsa.idToPickerState[id]; !ok { 124 return 125 } 126 // Remove id and picker from picker map. This also results in future updates 127 // for this ID to be ignored. 128 delete(wbsa.idToPickerState, id) 129 } 130 131 // UpdateWeight updates the weight for the given id. Note that this doesn't 132 // trigger an update to the parent ClientConn. The caller should decide when 133 // it's necessary, and call BuildAndUpdate. 134 func (wbsa *Aggregator) UpdateWeight(id string, newWeight uint32) { 135 wbsa.mu.Lock() 136 defer wbsa.mu.Unlock() 137 pState, ok := wbsa.idToPickerState[id] 138 if !ok { 139 return 140 } 141 pState.weight = newWeight 142 } 143 144 // UpdateState is called to report a balancer state change from sub-balancer. 145 // It's usually called by the balancer group. 146 // 147 // It calls parent ClientConn's UpdateState with the new aggregated state. 148 func (wbsa *Aggregator) UpdateState(id string, newState balancer.State) { 149 wbsa.mu.Lock() 150 defer wbsa.mu.Unlock() 151 oldState, ok := wbsa.idToPickerState[id] 152 if !ok { 153 // All state starts with an entry in pickStateMap. If ID is not in map, 154 // it's either removed, or never existed. 155 return 156 } 157 if !(oldState.state.ConnectivityState == connectivity.TransientFailure && newState.ConnectivityState == connectivity.Connecting) { 158 // If old state is TransientFailure, and new state is Connecting, don't 159 // update the state, to prevent the aggregated state from being always 160 // CONNECTING. Otherwise, stateToAggregate is the same as 161 // state.ConnectivityState. 162 oldState.stateToAggregate = newState.ConnectivityState 163 } 164 oldState.state = newState 165 166 if !wbsa.started { 167 return 168 } 169 wbsa.cc.UpdateState(wbsa.build()) 170 } 171 172 // clearState Reset everything to init state (Connecting) but keep the entry in 173 // map (to keep the weight). 174 // 175 // Caller must hold wbsa.mu. 176 func (wbsa *Aggregator) clearStates() { 177 for _, pState := range wbsa.idToPickerState { 178 pState.state = balancer.State{ 179 ConnectivityState: connectivity.Connecting, 180 Picker: base.NewErrPicker(balancer.ErrNoSubConnAvailable), 181 } 182 pState.stateToAggregate = connectivity.Connecting 183 } 184 } 185 186 // BuildAndUpdate combines the sub-state from each sub-balancer into one state, 187 // and update it to parent ClientConn. 188 func (wbsa *Aggregator) BuildAndUpdate() { 189 wbsa.mu.Lock() 190 defer wbsa.mu.Unlock() 191 if !wbsa.started { 192 return 193 } 194 wbsa.cc.UpdateState(wbsa.build()) 195 } 196 197 // build combines sub-states into one. 198 // 199 // Caller must hold wbsa.mu. 200 func (wbsa *Aggregator) build() balancer.State { 201 wbsa.logger.Infof("Child pickers with config: %+v", wbsa.idToPickerState) 202 m := wbsa.idToPickerState 203 // TODO: use balancer.ConnectivityStateEvaluator to calculate the aggregated 204 // state. 205 var readyN, connectingN, idleN int 206 readyPickerWithWeights := make([]weightedPickerState, 0, len(m)) 207 for _, ps := range m { 208 switch ps.stateToAggregate { 209 case connectivity.Ready: 210 readyN++ 211 readyPickerWithWeights = append(readyPickerWithWeights, *ps) 212 case connectivity.Connecting: 213 connectingN++ 214 case connectivity.Idle: 215 idleN++ 216 } 217 } 218 var aggregatedState connectivity.State 219 switch { 220 case readyN > 0: 221 aggregatedState = connectivity.Ready 222 case connectingN > 0: 223 aggregatedState = connectivity.Connecting 224 case idleN > 0: 225 aggregatedState = connectivity.Idle 226 default: 227 aggregatedState = connectivity.TransientFailure 228 } 229 230 // Make sure picker's return error is consistent with the aggregatedState. 231 var picker balancer.Picker 232 switch aggregatedState { 233 case connectivity.TransientFailure: 234 picker = base.NewErrPicker(balancer.ErrTransientFailure) 235 case connectivity.Connecting: 236 picker = base.NewErrPicker(balancer.ErrNoSubConnAvailable) 237 default: 238 picker = newWeightedPickerGroup(readyPickerWithWeights, wbsa.newWRR) 239 } 240 return balancer.State{ConnectivityState: aggregatedState, Picker: picker} 241 } 242 243 type weightedPickerGroup struct { 244 w wrr.WRR 245 } 246 247 // newWeightedPickerGroup takes pickers with weights, and groups them into one 248 // picker. 249 // 250 // Note it only takes ready pickers. The map shouldn't contain non-ready 251 // pickers. 252 func newWeightedPickerGroup(readyWeightedPickers []weightedPickerState, newWRR func() wrr.WRR) *weightedPickerGroup { 253 w := newWRR() 254 for _, ps := range readyWeightedPickers { 255 w.Add(ps.state.Picker, int64(ps.weight)) 256 } 257 258 return &weightedPickerGroup{ 259 w: w, 260 } 261 } 262 263 func (pg *weightedPickerGroup) Pick(info balancer.PickInfo) (balancer.PickResult, error) { 264 p, ok := pg.w.Next().(balancer.Picker) 265 if !ok { 266 return balancer.PickResult{}, balancer.ErrNoSubConnAvailable 267 } 268 return p.Pick(info) 269 }