dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/balancer/ringhash/ringhash.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 2021 gRPC authors. 21 * 22 */ 23 24 // Package ringhash implements the ringhash balancer. 25 package ringhash 26 27 import ( 28 "encoding/json" 29 "errors" 30 "fmt" 31 "sync" 32 ) 33 34 import ( 35 dubbogoLogger "github.com/dubbogo/gost/log/logger" 36 37 "google.golang.org/grpc/balancer" 38 "google.golang.org/grpc/balancer/base" 39 "google.golang.org/grpc/balancer/weightedroundrobin" 40 41 "google.golang.org/grpc/connectivity" 42 43 "google.golang.org/grpc/resolver" 44 45 "google.golang.org/grpc/serviceconfig" 46 ) 47 48 import ( 49 "dubbo.apache.org/dubbo-go/v3/xds/utils/pretty" 50 ) 51 52 // Name is the name of the ring_hash balancer. 53 const Name = "ring_hash_experimental" 54 55 func init() { 56 balancer.Register(bb{}) 57 } 58 59 type bb struct{} 60 61 func (bb) Build(cc balancer.ClientConn, bOpts balancer.BuildOptions) balancer.Balancer { 62 b := &ringhashBalancer{ 63 cc: cc, 64 subConns: make(map[resolver.Address]*subConn), 65 scStates: make(map[balancer.SubConn]*subConn), 66 csEvltr: &connectivityStateEvaluator{}, 67 } 68 b.logger = dubbogoLogger.GetLogger() 69 b.logger.Infof("Created") 70 return b 71 } 72 73 func (bb) Name() string { 74 return Name 75 } 76 77 func (bb) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, error) { 78 return parseConfig(c) 79 } 80 81 type subConn struct { 82 addr string 83 sc balancer.SubConn 84 85 mu sync.RWMutex 86 // This is the actual state of this SubConn (as updated by the ClientConn). 87 // The effective state can be different, see comment of attemptedToConnect. 88 state connectivity.State 89 // failing is whether this SubConn is in a failing state. A subConn is 90 // considered to be in a failing state if it was previously in 91 // TransientFailure. 92 // 93 // This affects the effective connectivity state of this SubConn, e.g. 94 // - if the actual state is Idle or Connecting, but this SubConn is failing, 95 // the effective state is TransientFailure. 96 // 97 // This is used in pick(). E.g. if a subConn is Idle, but has failing as 98 // true, pick() will 99 // - consider this SubConn as TransientFailure, and check the state of the 100 // next SubConn. 101 // - trigger Connect() (note that normally a SubConn in real 102 // TransientFailure cannot Connect()) 103 // 104 // A subConn starts in non-failing (failing is false). A transition to 105 // TransientFailure sets failing to true (and it stays true). A transition 106 // to Ready sets failing to false. 107 failing bool 108 // connectQueued is true if a Connect() was queued for this SubConn while 109 // it's not in Idle (most likely was in TransientFailure). A Connect() will 110 // be triggered on this SubConn when it turns Idle. 111 // 112 // When connectivity state is updated to Idle for this SubConn, if 113 // connectQueued is true, Connect() will be called on the SubConn. 114 connectQueued bool 115 } 116 117 // setState updates the state of this SubConn. 118 // 119 // It also handles the queued Connect(). If the new state is Idle, and a 120 // Connect() was queued, this SubConn will be triggered to Connect(). 121 func (sc *subConn) setState(s connectivity.State) { 122 sc.mu.Lock() 123 defer sc.mu.Unlock() 124 switch s { 125 case connectivity.Idle: 126 // Trigger Connect() if new state is Idle, and there is a queued connect. 127 if sc.connectQueued { 128 sc.connectQueued = false 129 sc.sc.Connect() 130 } 131 case connectivity.Connecting: 132 // Clear connectQueued if the SubConn isn't failing. This state 133 // transition is unlikely to happen, but handle this just in case. 134 sc.connectQueued = false 135 case connectivity.Ready: 136 // Clear connectQueued if the SubConn isn't failing. This state 137 // transition is unlikely to happen, but handle this just in case. 138 sc.connectQueued = false 139 // Set to a non-failing state. 140 sc.failing = false 141 case connectivity.TransientFailure: 142 // Set to a failing state. 143 sc.failing = true 144 } 145 sc.state = s 146 } 147 148 // effectiveState returns the effective state of this SubConn. It can be 149 // different from the actual state, e.g. Idle while the subConn is failing is 150 // considered TransientFailure. Read comment of field failing for other cases. 151 func (sc *subConn) effectiveState() connectivity.State { 152 sc.mu.RLock() 153 defer sc.mu.RUnlock() 154 if sc.failing && (sc.state == connectivity.Idle || sc.state == connectivity.Connecting) { 155 return connectivity.TransientFailure 156 } 157 return sc.state 158 } 159 160 // queueConnect sets a boolean so that when the SubConn state changes to Idle, 161 // it's Connect() will be triggered. If the SubConn state is already Idle, it 162 // will just call Connect(). 163 func (sc *subConn) queueConnect() { 164 sc.mu.Lock() 165 defer sc.mu.Unlock() 166 if sc.state == connectivity.Idle { 167 sc.sc.Connect() 168 return 169 } 170 // Queue this connect, and when this SubConn switches back to Idle (happens 171 // after backoff in TransientFailure), it will Connect(). 172 sc.connectQueued = true 173 } 174 175 type ringhashBalancer struct { 176 cc balancer.ClientConn 177 logger dubbogoLogger.Logger 178 179 config *LBConfig 180 181 subConns map[resolver.Address]*subConn // `attributes` is stripped from the keys of this map (the addresses) 182 scStates map[balancer.SubConn]*subConn 183 184 // ring is always in sync with subConns. When subConns change, a new ring is 185 // generated. Note that address weights updates (they are keys in the 186 // subConns map) also regenerates the ring. 187 ring *ring 188 picker balancer.Picker 189 csEvltr *connectivityStateEvaluator 190 state connectivity.State 191 192 resolverErr error // the last error reported by the resolver; cleared on successful resolution 193 connErr error // the last connection error; cleared upon leaving TransientFailure 194 } 195 196 // updateAddresses creates new SubConns and removes SubConns, based on the 197 // address update. 198 // 199 // The return value is whether the new address list is different from the 200 // previous. True if 201 // - an address was added 202 // - an address was removed 203 // - an address's weight was updated 204 // 205 // Note that this function doesn't trigger SubConn connecting, so all the new 206 // SubConn states are Idle. 207 func (b *ringhashBalancer) updateAddresses(addrs []resolver.Address) bool { 208 var addrsUpdated bool 209 // addrsSet is the set converted from addrs, it's used for quick lookup of 210 // an address. 211 // 212 // Addresses in this map all have attributes stripped, but metadata set to 213 // the weight. So that weight change can be detected. 214 // 215 // TODO: this won't be necessary if there are ways to compare address 216 // attributes. 217 addrsSet := make(map[resolver.Address]struct{}) 218 for _, a := range addrs { 219 aNoAttrs := a 220 // Strip attributes but set Metadata to the weight. 221 aNoAttrs.Attributes = nil 222 w := weightedroundrobin.GetAddrInfo(a).Weight 223 if w == 0 { 224 // If weight is not set, use 1. 225 w = 1 226 } 227 aNoAttrs.Metadata = w 228 addrsSet[aNoAttrs] = struct{}{} 229 if scInfo, ok := b.subConns[aNoAttrs]; !ok { 230 // When creating SubConn, the original address with attributes is 231 // passed through. So that connection configurations in attributes 232 // (like creds) will be used. 233 sc, err := b.cc.NewSubConn([]resolver.Address{a}, balancer.NewSubConnOptions{HealthCheckEnabled: true}) 234 if err != nil { 235 dubbogoLogger.Warnf("base.baseBalancer: failed to create new SubConn: %v", err) 236 continue 237 } 238 scs := &subConn{addr: a.Addr, sc: sc} 239 scs.setState(connectivity.Idle) 240 b.state = b.csEvltr.recordTransition(connectivity.Shutdown, connectivity.Idle) 241 b.subConns[aNoAttrs] = scs 242 b.scStates[sc] = scs 243 addrsUpdated = true 244 } else { 245 // Always update the subconn's address in case the attributes 246 // changed. The SubConn does a reflect.DeepEqual of the new and old 247 // addresses. So this is a noop if the current address is the same 248 // as the old one (including attributes). 249 b.subConns[aNoAttrs] = scInfo 250 b.cc.UpdateAddresses(scInfo.sc, []resolver.Address{a}) 251 } 252 } 253 for a, scInfo := range b.subConns { 254 // a was removed by resolver. 255 if _, ok := addrsSet[a]; !ok { 256 b.cc.RemoveSubConn(scInfo.sc) 257 delete(b.subConns, a) 258 addrsUpdated = true 259 // Keep the state of this sc in b.scStates until sc's state becomes Shutdown. 260 // The entry will be deleted in UpdateSubConnState. 261 } 262 } 263 return addrsUpdated 264 } 265 266 func (b *ringhashBalancer) UpdateClientConnState(s balancer.ClientConnState) error { 267 b.logger.Infof("Received update from resolver, balancer config: %+v", pretty.ToJSON(s.BalancerConfig)) 268 if b.config == nil { 269 newConfig, ok := s.BalancerConfig.(*LBConfig) 270 if !ok { 271 return fmt.Errorf("unexpected balancer config with type: %T", s.BalancerConfig) 272 } 273 b.config = newConfig 274 } 275 276 // Successful resolution; clear resolver error and ensure we return nil. 277 b.resolverErr = nil 278 if b.updateAddresses(s.ResolverState.Addresses) { 279 // If addresses were updated, no matter whether it resulted in SubConn 280 // creation/deletion, or just weight update, we will need to regenerate 281 // the ring. 282 var err error 283 b.ring, err = newRing(b.subConns, b.config.MinRingSize, b.config.MaxRingSize) 284 if err != nil { 285 panic(err) 286 } 287 b.regeneratePicker() 288 b.cc.UpdateState(balancer.State{ConnectivityState: b.state, Picker: b.picker}) 289 } 290 291 // If resolver state contains no addresses, return an error so ClientConn 292 // will trigger re-resolve. Also records this as an resolver error, so when 293 // the overall state turns transient failure, the error message will have 294 // the zero address information. 295 if len(s.ResolverState.Addresses) == 0 { 296 b.ResolverError(errors.New("produced zero addresses")) 297 return balancer.ErrBadResolverState 298 } 299 return nil 300 } 301 302 func (b *ringhashBalancer) ResolverError(err error) { 303 b.resolverErr = err 304 if len(b.subConns) == 0 { 305 b.state = connectivity.TransientFailure 306 } 307 308 if b.state != connectivity.TransientFailure { 309 // The picker will not change since the balancer does not currently 310 // report an error. 311 return 312 } 313 b.regeneratePicker() 314 b.cc.UpdateState(balancer.State{ 315 ConnectivityState: b.state, 316 Picker: b.picker, 317 }) 318 } 319 320 // UpdateSubConnState updates the per-SubConn state stored in the ring, and also 321 // the aggregated state. 322 // 323 // It triggers an update to cc when: 324 // - the new state is TransientFailure, to update the error message 325 // - it's possible that this is a noop, but sending an extra update is easier 326 // than comparing errors 327 // 328 // - the aggregated state is changed 329 // - the same picker will be sent again, but this update may trigger a re-pick 330 // for some RPCs. 331 func (b *ringhashBalancer) UpdateSubConnState(sc balancer.SubConn, state balancer.SubConnState) { 332 s := state.ConnectivityState 333 b.logger.Infof("handle SubConn state change: %p, %v", sc, s) 334 scs, ok := b.scStates[sc] 335 if !ok { 336 b.logger.Infof("got state changes for an unknown SubConn: %p, %v", sc, s) 337 return 338 } 339 oldSCState := scs.effectiveState() 340 scs.setState(s) 341 newSCState := scs.effectiveState() 342 343 var sendUpdate bool 344 oldBalancerState := b.state 345 b.state = b.csEvltr.recordTransition(oldSCState, newSCState) 346 if oldBalancerState != b.state { 347 sendUpdate = true 348 } 349 350 switch s { 351 case connectivity.Idle: 352 // When the overall state is TransientFailure, this will never get picks 353 // if there's a lower priority. Need to keep the SubConns connecting so 354 // there's a chance it will recover. 355 if b.state == connectivity.TransientFailure { 356 scs.queueConnect() 357 } 358 // No need to send an update. No queued RPC can be unblocked. If the 359 // overall state changed because of this, sendUpdate is already true. 360 case connectivity.Connecting: 361 // No need to send an update. No queued RPC can be unblocked. If the 362 // overall state changed because of this, sendUpdate is already true. 363 case connectivity.Ready: 364 // Resend the picker, there's no need to regenerate the picker because 365 // the ring didn't change. 366 sendUpdate = true 367 case connectivity.TransientFailure: 368 // Save error to be reported via picker. 369 b.connErr = state.ConnectionError 370 // Regenerate picker to update error message. 371 b.regeneratePicker() 372 sendUpdate = true 373 case connectivity.Shutdown: 374 // When an address was removed by resolver, b called RemoveSubConn but 375 // kept the sc's state in scStates. Remove state for this sc here. 376 delete(b.scStates, sc) 377 } 378 379 if sendUpdate { 380 b.cc.UpdateState(balancer.State{ConnectivityState: b.state, Picker: b.picker}) 381 } 382 } 383 384 // mergeErrors builds an error from the last connection error and the last 385 // resolver error. Must only be called if b.state is TransientFailure. 386 func (b *ringhashBalancer) mergeErrors() error { 387 // connErr must always be non-nil unless there are no SubConns, in which 388 // case resolverErr must be non-nil. 389 if b.connErr == nil { 390 return fmt.Errorf("last resolver error: %v", b.resolverErr) 391 } 392 if b.resolverErr == nil { 393 return fmt.Errorf("last connection error: %v", b.connErr) 394 } 395 return fmt.Errorf("last connection error: %v; last resolver error: %v", b.connErr, b.resolverErr) 396 } 397 398 func (b *ringhashBalancer) regeneratePicker() { 399 if b.state == connectivity.TransientFailure { 400 b.picker = base.NewErrPicker(b.mergeErrors()) 401 return 402 } 403 b.picker = newPicker(b.ring, b.logger) 404 } 405 406 func (b *ringhashBalancer) Close() {} 407 408 // connectivityStateEvaluator takes the connectivity states of multiple SubConns 409 // and returns one aggregated connectivity state. 410 // 411 // It's not thread safe. 412 type connectivityStateEvaluator struct { 413 nums [5]uint64 414 } 415 416 // recordTransition records state change happening in subConn and based on that 417 // it evaluates what aggregated state should be. 418 // 419 // - If there is at least one subchannel in READY state, report READY. 420 // - If there are 2 or more subchannels in TRANSIENT_FAILURE state, report TRANSIENT_FAILURE. 421 // - If there is at least one subchannel in CONNECTING state, report CONNECTING. 422 // - If there is at least one subchannel in Idle state, report Idle. 423 // - Otherwise, report TRANSIENT_FAILURE. 424 // 425 // Note that if there are 1 connecting, 2 transient failure, the overall state 426 // is transient failure. This is because the second transient failure is a 427 // fallback of the first failing SubConn, and we want to report transient 428 // failure to failover to the lower priority. 429 func (cse *connectivityStateEvaluator) recordTransition(oldState, newState connectivity.State) connectivity.State { 430 // Update counters. 431 for idx, state := range []connectivity.State{oldState, newState} { 432 updateVal := 2*uint64(idx) - 1 // -1 for oldState and +1 for new. 433 cse.nums[state] += updateVal 434 } 435 436 if cse.nums[connectivity.Ready] > 0 { 437 return connectivity.Ready 438 } 439 if cse.nums[connectivity.TransientFailure] > 1 { 440 return connectivity.TransientFailure 441 } 442 if cse.nums[connectivity.Connecting] > 0 { 443 return connectivity.Connecting 444 } 445 if cse.nums[connectivity.Idle] > 0 { 446 return connectivity.Idle 447 } 448 return connectivity.TransientFailure 449 }