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  }