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  }