gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/grpc/balancer/weightedtarget/weightedaggregator/aggregator.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 weightedaggregator implements state aggregator for weighted_target
    20  // balancer.
    21  //
    22  // This is a separate package so it can be shared by weighted_target and eds.
    23  // The eds balancer will be refactored to use weighted_target directly. After
    24  // that, all functions and structs in this package can be moved to package
    25  // weightedtarget and unexported.
    26  package weightedaggregator
    27  
    28  import (
    29  	"fmt"
    30  	"sync"
    31  
    32  	"gitee.com/ks-custle/core-gm/grpc/balancer"
    33  	"gitee.com/ks-custle/core-gm/grpc/balancer/base"
    34  	"gitee.com/ks-custle/core-gm/grpc/connectivity"
    35  	"gitee.com/ks-custle/core-gm/grpc/internal/grpclog"
    36  	"gitee.com/ks-custle/core-gm/grpc/internal/wrr"
    37  )
    38  
    39  type weightedPickerState struct {
    40  	weight uint32
    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 *weightedPickerState) String() string {
    51  	return fmt.Sprintf("weight:%v,picker:%p,state:%v,stateToAggregate:%v", s.weight, s.state.Picker, s.state.ConnectivityState, s.stateToAggregate)
    52  }
    53  
    54  // Aggregator is the weighted balancer state aggregator.
    55  type Aggregator struct {
    56  	cc     balancer.ClientConn
    57  	logger *grpclog.PrefixLogger
    58  	newWRR func() wrr.WRR
    59  
    60  	mu sync.Mutex
    61  	// If started is false, no updates should be sent to the parent cc. A closed
    62  	// sub-balancer could still send pickers to this aggregator. This makes sure
    63  	// that no updates will be forwarded to parent when the whole balancer group
    64  	// and states aggregator is closed.
    65  	started bool
    66  	// All balancer IDs exist as keys in this map, even if balancer group is not
    67  	// started.
    68  	//
    69  	// If an ID is not in map, it's either removed or never added.
    70  	idToPickerState map[string]*weightedPickerState
    71  }
    72  
    73  // New creates a new weighted balancer state aggregator.
    74  func New(cc balancer.ClientConn, logger *grpclog.PrefixLogger, newWRR func() wrr.WRR) *Aggregator {
    75  	return &Aggregator{
    76  		cc:              cc,
    77  		logger:          logger,
    78  		newWRR:          newWRR,
    79  		idToPickerState: make(map[string]*weightedPickerState),
    80  	}
    81  }
    82  
    83  // Start starts the aggregator. It can be called after Close to restart the
    84  // aggretator.
    85  func (wbsa *Aggregator) Start() {
    86  	wbsa.mu.Lock()
    87  	defer wbsa.mu.Unlock()
    88  	wbsa.started = true
    89  }
    90  
    91  // Stop stops the aggregator. When the aggregator is closed, it won't call
    92  // parent ClientConn to update balancer state.
    93  func (wbsa *Aggregator) Stop() {
    94  	wbsa.mu.Lock()
    95  	defer wbsa.mu.Unlock()
    96  	wbsa.started = false
    97  	wbsa.clearStates()
    98  }
    99  
   100  // Add adds a sub-balancer state with weight. It adds a place holder, and waits for
   101  // the real sub-balancer to update state.
   102  func (wbsa *Aggregator) Add(id string, weight uint32) {
   103  	wbsa.mu.Lock()
   104  	defer wbsa.mu.Unlock()
   105  	wbsa.idToPickerState[id] = &weightedPickerState{
   106  		weight: weight,
   107  		// Start everything in CONNECTING, so if one of the sub-balancers
   108  		// reports TransientFailure, the RPCs will still wait for the other
   109  		// sub-balancers.
   110  		state: balancer.State{
   111  			ConnectivityState: connectivity.Connecting,
   112  			Picker:            base.NewErrPicker(balancer.ErrNoSubConnAvailable),
   113  		},
   114  		stateToAggregate: connectivity.Connecting,
   115  	}
   116  }
   117  
   118  // Remove removes the sub-balancer state. Future updates from this sub-balancer,
   119  // if any, will be ignored.
   120  func (wbsa *Aggregator) Remove(id string) {
   121  	wbsa.mu.Lock()
   122  	defer wbsa.mu.Unlock()
   123  	if _, ok := wbsa.idToPickerState[id]; !ok {
   124  		return
   125  	}
   126  	// Remove id and picker from picker map. This also results in future updates
   127  	// for this ID to be ignored.
   128  	delete(wbsa.idToPickerState, id)
   129  }
   130  
   131  // UpdateWeight updates the weight for the given id. Note that this doesn't
   132  // trigger an update to the parent ClientConn. The caller should decide when
   133  // it's necessary, and call BuildAndUpdate.
   134  func (wbsa *Aggregator) UpdateWeight(id string, newWeight uint32) {
   135  	wbsa.mu.Lock()
   136  	defer wbsa.mu.Unlock()
   137  	pState, ok := wbsa.idToPickerState[id]
   138  	if !ok {
   139  		return
   140  	}
   141  	pState.weight = newWeight
   142  }
   143  
   144  // UpdateState is called to report a balancer state change from sub-balancer.
   145  // It's usually called by the balancer group.
   146  //
   147  // It calls parent ClientConn's UpdateState with the new aggregated state.
   148  func (wbsa *Aggregator) UpdateState(id string, newState balancer.State) {
   149  	wbsa.mu.Lock()
   150  	defer wbsa.mu.Unlock()
   151  	oldState, ok := wbsa.idToPickerState[id]
   152  	if !ok {
   153  		// All state starts with an entry in pickStateMap. If ID is not in map,
   154  		// it's either removed, or never existed.
   155  		return
   156  	}
   157  	if !(oldState.state.ConnectivityState == connectivity.TransientFailure && newState.ConnectivityState == connectivity.Connecting) {
   158  		// If old state is TransientFailure, and new state is Connecting, don't
   159  		// update the state, to prevent the aggregated state from being always
   160  		// CONNECTING. Otherwise, stateToAggregate is the same as
   161  		// state.ConnectivityState.
   162  		oldState.stateToAggregate = newState.ConnectivityState
   163  	}
   164  	oldState.state = newState
   165  
   166  	if !wbsa.started {
   167  		return
   168  	}
   169  	wbsa.cc.UpdateState(wbsa.build())
   170  }
   171  
   172  // clearState Reset everything to init state (Connecting) but keep the entry in
   173  // map (to keep the weight).
   174  //
   175  // Caller must hold wbsa.mu.
   176  func (wbsa *Aggregator) clearStates() {
   177  	for _, pState := range wbsa.idToPickerState {
   178  		pState.state = balancer.State{
   179  			ConnectivityState: connectivity.Connecting,
   180  			Picker:            base.NewErrPicker(balancer.ErrNoSubConnAvailable),
   181  		}
   182  		pState.stateToAggregate = connectivity.Connecting
   183  	}
   184  }
   185  
   186  // BuildAndUpdate combines the sub-state from each sub-balancer into one state,
   187  // and update it to parent ClientConn.
   188  func (wbsa *Aggregator) BuildAndUpdate() {
   189  	wbsa.mu.Lock()
   190  	defer wbsa.mu.Unlock()
   191  	if !wbsa.started {
   192  		return
   193  	}
   194  	wbsa.cc.UpdateState(wbsa.build())
   195  }
   196  
   197  // build combines sub-states into one.
   198  //
   199  // Caller must hold wbsa.mu.
   200  func (wbsa *Aggregator) build() balancer.State {
   201  	wbsa.logger.Infof("Child pickers with config: %+v", wbsa.idToPickerState)
   202  	m := wbsa.idToPickerState
   203  	// TODO: use balancer.ConnectivityStateEvaluator to calculate the aggregated
   204  	// state.
   205  	var readyN, connectingN, idleN int
   206  	readyPickerWithWeights := make([]weightedPickerState, 0, len(m))
   207  	for _, ps := range m {
   208  		switch ps.stateToAggregate {
   209  		case connectivity.Ready:
   210  			readyN++
   211  			readyPickerWithWeights = append(readyPickerWithWeights, *ps)
   212  		case connectivity.Connecting:
   213  			connectingN++
   214  		case connectivity.Idle:
   215  			idleN++
   216  		}
   217  	}
   218  	var aggregatedState connectivity.State
   219  	switch {
   220  	case readyN > 0:
   221  		aggregatedState = connectivity.Ready
   222  	case connectingN > 0:
   223  		aggregatedState = connectivity.Connecting
   224  	case idleN > 0:
   225  		aggregatedState = connectivity.Idle
   226  	default:
   227  		aggregatedState = connectivity.TransientFailure
   228  	}
   229  
   230  	// Make sure picker's return error is consistent with the aggregatedState.
   231  	var picker balancer.Picker
   232  	switch aggregatedState {
   233  	case connectivity.TransientFailure:
   234  		picker = base.NewErrPicker(balancer.ErrTransientFailure)
   235  	case connectivity.Connecting:
   236  		picker = base.NewErrPicker(balancer.ErrNoSubConnAvailable)
   237  	default:
   238  		picker = newWeightedPickerGroup(readyPickerWithWeights, wbsa.newWRR)
   239  	}
   240  	return balancer.State{ConnectivityState: aggregatedState, Picker: picker}
   241  }
   242  
   243  type weightedPickerGroup struct {
   244  	w wrr.WRR
   245  }
   246  
   247  // newWeightedPickerGroup takes pickers with weights, and groups them into one
   248  // picker.
   249  //
   250  // Note it only takes ready pickers. The map shouldn't contain non-ready
   251  // pickers.
   252  func newWeightedPickerGroup(readyWeightedPickers []weightedPickerState, newWRR func() wrr.WRR) *weightedPickerGroup {
   253  	w := newWRR()
   254  	for _, ps := range readyWeightedPickers {
   255  		w.Add(ps.state.Picker, int64(ps.weight))
   256  	}
   257  
   258  	return &weightedPickerGroup{
   259  		w: w,
   260  	}
   261  }
   262  
   263  func (pg *weightedPickerGroup) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
   264  	p, ok := pg.w.Next().(balancer.Picker)
   265  	if !ok {
   266  		return balancer.PickResult{}, balancer.ErrNoSubConnAvailable
   267  	}
   268  	return p.Pick(info)
   269  }