github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/balancer/grpclb/grpclb_picker.go (about)

     1  /*
     2   *
     3   * Copyright 2017 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 grpclb
    20  
    21  import (
    22  	"sync"
    23  	"sync/atomic"
    24  
    25  	"github.com/hxx258456/ccgo/grpc/balancer"
    26  	lbpb "github.com/hxx258456/ccgo/grpc/balancer/grpclb/grpc_lb_v1"
    27  	"github.com/hxx258456/ccgo/grpc/codes"
    28  	"github.com/hxx258456/ccgo/grpc/internal/grpcrand"
    29  	"github.com/hxx258456/ccgo/grpc/status"
    30  )
    31  
    32  // rpcStats is same as lbpb.ClientStats, except that numCallsDropped is a map
    33  // instead of a slice.
    34  type rpcStats struct {
    35  	// Only access the following fields atomically.
    36  	numCallsStarted                        int64
    37  	numCallsFinished                       int64
    38  	numCallsFinishedWithClientFailedToSend int64
    39  	numCallsFinishedKnownReceived          int64
    40  
    41  	mu sync.Mutex
    42  	// map load_balance_token -> num_calls_dropped
    43  	numCallsDropped map[string]int64
    44  }
    45  
    46  func newRPCStats() *rpcStats {
    47  	return &rpcStats{
    48  		numCallsDropped: make(map[string]int64),
    49  	}
    50  }
    51  
    52  func isZeroStats(stats *lbpb.ClientStats) bool {
    53  	return len(stats.CallsFinishedWithDrop) == 0 &&
    54  		stats.NumCallsStarted == 0 &&
    55  		stats.NumCallsFinished == 0 &&
    56  		stats.NumCallsFinishedWithClientFailedToSend == 0 &&
    57  		stats.NumCallsFinishedKnownReceived == 0
    58  }
    59  
    60  // toClientStats converts rpcStats to lbpb.ClientStats, and clears rpcStats.
    61  func (s *rpcStats) toClientStats() *lbpb.ClientStats {
    62  	stats := &lbpb.ClientStats{
    63  		NumCallsStarted:                        atomic.SwapInt64(&s.numCallsStarted, 0),
    64  		NumCallsFinished:                       atomic.SwapInt64(&s.numCallsFinished, 0),
    65  		NumCallsFinishedWithClientFailedToSend: atomic.SwapInt64(&s.numCallsFinishedWithClientFailedToSend, 0),
    66  		NumCallsFinishedKnownReceived:          atomic.SwapInt64(&s.numCallsFinishedKnownReceived, 0),
    67  	}
    68  	s.mu.Lock()
    69  	dropped := s.numCallsDropped
    70  	s.numCallsDropped = make(map[string]int64)
    71  	s.mu.Unlock()
    72  	for token, count := range dropped {
    73  		stats.CallsFinishedWithDrop = append(stats.CallsFinishedWithDrop, &lbpb.ClientStatsPerToken{
    74  			LoadBalanceToken: token,
    75  			NumCalls:         count,
    76  		})
    77  	}
    78  	return stats
    79  }
    80  
    81  func (s *rpcStats) drop(token string) {
    82  	atomic.AddInt64(&s.numCallsStarted, 1)
    83  	s.mu.Lock()
    84  	s.numCallsDropped[token]++
    85  	s.mu.Unlock()
    86  	atomic.AddInt64(&s.numCallsFinished, 1)
    87  }
    88  
    89  func (s *rpcStats) failedToSend() {
    90  	atomic.AddInt64(&s.numCallsStarted, 1)
    91  	atomic.AddInt64(&s.numCallsFinishedWithClientFailedToSend, 1)
    92  	atomic.AddInt64(&s.numCallsFinished, 1)
    93  }
    94  
    95  func (s *rpcStats) knownReceived() {
    96  	atomic.AddInt64(&s.numCallsStarted, 1)
    97  	atomic.AddInt64(&s.numCallsFinishedKnownReceived, 1)
    98  	atomic.AddInt64(&s.numCallsFinished, 1)
    99  }
   100  
   101  type errPicker struct {
   102  	// Pick always returns this err.
   103  	err error
   104  }
   105  
   106  func (p *errPicker) Pick(balancer.PickInfo) (balancer.PickResult, error) {
   107  	return balancer.PickResult{}, p.err
   108  }
   109  
   110  // rrPicker does roundrobin on subConns. It's typically used when there's no
   111  // response from remote balancer, and grpclb falls back to the resolved
   112  // backends.
   113  //
   114  // It guaranteed that len(subConns) > 0.
   115  type rrPicker struct {
   116  	mu           sync.Mutex
   117  	subConns     []balancer.SubConn // The subConns that were READY when taking the snapshot.
   118  	subConnsNext int
   119  }
   120  
   121  func newRRPicker(readySCs []balancer.SubConn) *rrPicker {
   122  	return &rrPicker{
   123  		subConns:     readySCs,
   124  		subConnsNext: grpcrand.Intn(len(readySCs)),
   125  	}
   126  }
   127  
   128  func (p *rrPicker) Pick(balancer.PickInfo) (balancer.PickResult, error) {
   129  	p.mu.Lock()
   130  	defer p.mu.Unlock()
   131  	sc := p.subConns[p.subConnsNext]
   132  	p.subConnsNext = (p.subConnsNext + 1) % len(p.subConns)
   133  	return balancer.PickResult{SubConn: sc}, nil
   134  }
   135  
   136  // lbPicker does two layers of picks:
   137  //
   138  // First layer: roundrobin on all servers in serverList, including drops and backends.
   139  // - If it picks a drop, the RPC will fail as being dropped.
   140  // - If it picks a backend, do a second layer pick to pick the real backend.
   141  //
   142  // Second layer: roundrobin on all READY backends.
   143  //
   144  // It's guaranteed that len(serverList) > 0.
   145  type lbPicker struct {
   146  	mu             sync.Mutex
   147  	serverList     []*lbpb.Server
   148  	serverListNext int
   149  	subConns       []balancer.SubConn // The subConns that were READY when taking the snapshot.
   150  	subConnsNext   int
   151  
   152  	stats *rpcStats
   153  }
   154  
   155  func newLBPicker(serverList []*lbpb.Server, readySCs []balancer.SubConn, stats *rpcStats) *lbPicker {
   156  	return &lbPicker{
   157  		serverList:   serverList,
   158  		subConns:     readySCs,
   159  		subConnsNext: grpcrand.Intn(len(readySCs)),
   160  		stats:        stats,
   161  	}
   162  }
   163  
   164  func (p *lbPicker) Pick(balancer.PickInfo) (balancer.PickResult, error) {
   165  	p.mu.Lock()
   166  	defer p.mu.Unlock()
   167  
   168  	// Layer one roundrobin on serverList.
   169  	s := p.serverList[p.serverListNext]
   170  	p.serverListNext = (p.serverListNext + 1) % len(p.serverList)
   171  
   172  	// If it's a drop, return an error and fail the RPC.
   173  	if s.Drop {
   174  		p.stats.drop(s.LoadBalanceToken)
   175  		return balancer.PickResult{}, status.Errorf(codes.Unavailable, "request dropped by grpclb")
   176  	}
   177  
   178  	// If not a drop but there's no ready subConns.
   179  	if len(p.subConns) <= 0 {
   180  		return balancer.PickResult{}, balancer.ErrNoSubConnAvailable
   181  	}
   182  
   183  	// Return the next ready subConn in the list, also collect rpc stats.
   184  	sc := p.subConns[p.subConnsNext]
   185  	p.subConnsNext = (p.subConnsNext + 1) % len(p.subConns)
   186  	done := func(info balancer.DoneInfo) {
   187  		if !info.BytesSent {
   188  			p.stats.failedToSend()
   189  		} else if info.BytesReceived {
   190  			p.stats.knownReceived()
   191  		}
   192  	}
   193  	return balancer.PickResult{SubConn: sc, Done: done}, nil
   194  }
   195  
   196  func (p *lbPicker) updateReadySCs(readySCs []balancer.SubConn) {
   197  	p.mu.Lock()
   198  	defer p.mu.Unlock()
   199  
   200  	p.subConns = readySCs
   201  	p.subConnsNext = p.subConnsNext % len(readySCs)
   202  }