dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/balancer/clustermanager/balancerstateaggregator.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 clustermanager 25 26 import ( 27 "fmt" 28 "sync" 29 ) 30 31 import ( 32 dubbogoLogger "github.com/dubbogo/gost/log/logger" 33 34 "google.golang.org/grpc/balancer" 35 "google.golang.org/grpc/balancer/base" 36 37 "google.golang.org/grpc/connectivity" 38 ) 39 40 type subBalancerState struct { 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 *subBalancerState) String() string { 51 return fmt.Sprintf("picker:%p,state:%v,stateToAggregate:%v", s.state.Picker, s.state.ConnectivityState, s.stateToAggregate) 52 } 53 54 type balancerStateAggregator struct { 55 cc balancer.ClientConn 56 logger dubbogoLogger.Logger 57 58 mu sync.Mutex 59 // If started is false, no updates should be sent to the parent cc. A closed 60 // sub-balancer could still send pickers to this aggregator. This makes sure 61 // that no updates will be forwarded to parent when the whole balancer group 62 // and states aggregator is closed. 63 started bool 64 // All balancer IDs exist as keys in this map, even if balancer group is not 65 // started. 66 // 67 // If an ID is not in map, it's either removed or never added. 68 idToPickerState map[string]*subBalancerState 69 } 70 71 func newBalancerStateAggregator(cc balancer.ClientConn, logger dubbogoLogger.Logger) *balancerStateAggregator { 72 return &balancerStateAggregator{ 73 cc: cc, 74 logger: logger, 75 idToPickerState: make(map[string]*subBalancerState), 76 } 77 } 78 79 // Start starts the aggregator. It can be called after Close to restart the 80 // aggretator. 81 func (bsa *balancerStateAggregator) start() { 82 bsa.mu.Lock() 83 defer bsa.mu.Unlock() 84 bsa.started = true 85 } 86 87 // Close closes the aggregator. When the aggregator is closed, it won't call 88 // parent ClientConn to update balancer state. 89 func (bsa *balancerStateAggregator) close() { 90 bsa.mu.Lock() 91 defer bsa.mu.Unlock() 92 bsa.started = false 93 bsa.clearStates() 94 } 95 96 // add adds a sub-balancer state with weight. It adds a place holder, and waits 97 // for the real sub-balancer to update state. 98 // 99 // This is called when there's a new child. 100 func (bsa *balancerStateAggregator) add(id string) { 101 bsa.mu.Lock() 102 defer bsa.mu.Unlock() 103 bsa.idToPickerState[id] = &subBalancerState{ 104 // Start everything in CONNECTING, so if one of the sub-balancers 105 // reports TransientFailure, the RPCs will still wait for the other 106 // sub-balancers. 107 state: balancer.State{ 108 ConnectivityState: connectivity.Connecting, 109 Picker: base.NewErrPicker(balancer.ErrNoSubConnAvailable), 110 }, 111 stateToAggregate: connectivity.Connecting, 112 } 113 } 114 115 // remove removes the sub-balancer state. Future updates from this sub-balancer, 116 // if any, will be ignored. 117 // 118 // This is called when a child is removed. 119 func (bsa *balancerStateAggregator) remove(id string) { 120 bsa.mu.Lock() 121 defer bsa.mu.Unlock() 122 if _, ok := bsa.idToPickerState[id]; !ok { 123 return 124 } 125 // Remove id and picker from picker map. This also results in future updates 126 // for this ID to be ignored. 127 delete(bsa.idToPickerState, id) 128 } 129 130 // UpdateState is called to report a balancer state change from sub-balancer. 131 // It's usually called by the balancer group. 132 // 133 // It calls parent ClientConn's UpdateState with the new aggregated state. 134 func (bsa *balancerStateAggregator) UpdateState(id string, state balancer.State) { 135 bsa.mu.Lock() 136 defer bsa.mu.Unlock() 137 pickerSt, ok := bsa.idToPickerState[id] 138 if !ok { 139 // All state starts with an entry in pickStateMap. If ID is not in map, 140 // it's either removed, or never existed. 141 return 142 } 143 if !(pickerSt.state.ConnectivityState == connectivity.TransientFailure && state.ConnectivityState == connectivity.Connecting) { 144 // If old state is TransientFailure, and new state is Connecting, don't 145 // update the state, to prevent the aggregated state from being always 146 // CONNECTING. Otherwise, stateToAggregate is the same as 147 // state.ConnectivityState. 148 pickerSt.stateToAggregate = state.ConnectivityState 149 } 150 pickerSt.state = state 151 152 if !bsa.started { 153 return 154 } 155 bsa.cc.UpdateState(bsa.build()) 156 } 157 158 // clearState Reset everything to init state (Connecting) but keep the entry in 159 // map (to keep the weight). 160 // 161 // Caller must hold bsa.mu. 162 func (bsa *balancerStateAggregator) clearStates() { 163 for _, pState := range bsa.idToPickerState { 164 pState.state = balancer.State{ 165 ConnectivityState: connectivity.Connecting, 166 Picker: base.NewErrPicker(balancer.ErrNoSubConnAvailable), 167 } 168 pState.stateToAggregate = connectivity.Connecting 169 } 170 } 171 172 // buildAndUpdate combines the sub-state from each sub-balancer into one state, 173 // and update it to parent ClientConn. 174 func (bsa *balancerStateAggregator) buildAndUpdate() { 175 bsa.mu.Lock() 176 defer bsa.mu.Unlock() 177 if !bsa.started { 178 return 179 } 180 bsa.cc.UpdateState(bsa.build()) 181 } 182 183 // build combines sub-states into one. The picker will do a child pick. 184 // 185 // Caller must hold bsa.mu. 186 func (bsa *balancerStateAggregator) build() balancer.State { 187 // TODO: the majority of this function (and UpdateState) is exactly the same 188 // as weighted_target's state aggregator. Try to make a general utility 189 // function/struct to handle the logic. 190 // 191 // One option: make a SubBalancerState that handles Update(State), including 192 // handling the special connecting after ready, as in UpdateState(). Then a 193 // function to calculate the aggregated connectivity state as in this 194 // function. 195 // 196 // TODO: use balancer.ConnectivityStateEvaluator to calculate the aggregated 197 // state. 198 var readyN, connectingN, idleN int 199 for _, ps := range bsa.idToPickerState { 200 switch ps.stateToAggregate { 201 case connectivity.Ready: 202 readyN++ 203 case connectivity.Connecting: 204 connectingN++ 205 case connectivity.Idle: 206 idleN++ 207 } 208 } 209 var aggregatedState connectivity.State 210 switch { 211 case readyN > 0: 212 aggregatedState = connectivity.Ready 213 case connectingN > 0: 214 aggregatedState = connectivity.Connecting 215 case idleN > 0: 216 aggregatedState = connectivity.Idle 217 default: 218 aggregatedState = connectivity.TransientFailure 219 } 220 221 // The picker's return error might not be consistent with the 222 // aggregatedState. Because for this LB policy, we want to always build 223 // picker with all sub-pickers (not only ready sub-pickers), so even if the 224 // overall state is Ready, pick for certain RPCs can behave like Connecting 225 // or TransientFailure. 226 bsa.logger.Infof("Child pickers: %+v", bsa.idToPickerState) 227 return balancer.State{ 228 ConnectivityState: aggregatedState, 229 Picker: newPickerGroup(bsa.idToPickerState), 230 } 231 }