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