Skip to content

Commit 4745f6a

Browse files
authored
grpclb: fallback after init (#2681)
regenerate picker when switching between fallback/non-fallback, because new SubConn state might not be updated for cached SubConns
1 parent 955eb8a commit 4745f6a

File tree

4 files changed

+252
-50
lines changed

4 files changed

+252
-50
lines changed

balancer/grpclb/grpclb.go

Lines changed: 70 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,6 @@ func (b *lbBuilder) Build(cc balancer.ClientConn, opt balancer.BuildOptions) bal
172172
doneCh: make(chan struct{}),
173173

174174
manualResolver: r,
175-
csEvltr: &balancer.ConnectivityStateEvaluator{},
176175
subConns: make(map[resolver.Address]balancer.SubConn),
177176
scStates: make(map[balancer.SubConn]connectivity.State),
178177
picker: &errPicker{err: balancer.ErrNoSubConnAvailable},
@@ -238,15 +237,15 @@ type lbBalancer struct {
238237
// but with only READY SCs will be gerenated.
239238
backendAddrs []resolver.Address
240239
// Roundrobin functionalities.
241-
csEvltr *balancer.ConnectivityStateEvaluator
242240
state connectivity.State
243241
subConns map[resolver.Address]balancer.SubConn // Used to new/remove SubConn.
244242
scStates map[balancer.SubConn]connectivity.State // Used to filter READY SubConns.
245243
picker balancer.Picker
246244
// Support fallback to resolved backend addresses if there's no response
247245
// from remote balancer within fallbackTimeout.
248-
fallbackTimerExpired bool
249-
serverListReceived bool
246+
remoteBalancerConnected bool
247+
serverListReceived bool
248+
inFallback bool
250249
// resolvedBackendAddrs is resolvedAddrs minus remote balancers. It's set
251250
// when resolved address updates are received, and read in the goroutine
252251
// handling fallback.
@@ -264,13 +263,16 @@ func (lb *lbBalancer) regeneratePicker(resetDrop bool) {
264263
return
265264
}
266265

266+
if lb.state == connectivity.Connecting {
267+
lb.picker = &errPicker{err: balancer.ErrNoSubConnAvailable}
268+
return
269+
}
270+
267271
var readySCs []balancer.SubConn
268272
if lb.usePickFirst {
269-
if lb.state == connectivity.Ready || lb.state == connectivity.Idle {
270-
for _, sc := range lb.subConns {
271-
readySCs = append(readySCs, sc)
272-
break
273-
}
273+
for _, sc := range lb.subConns {
274+
readySCs = append(readySCs, sc)
275+
break
274276
}
275277
} else {
276278
for _, a := range lb.backendAddrs {
@@ -286,10 +288,13 @@ func (lb *lbBalancer) regeneratePicker(resetDrop bool) {
286288
// If there's no ready SubConns, always re-pick. This is to avoid drops
287289
// unless at least one SubConn is ready. Otherwise we may drop more
288290
// often than want because of drops + re-picks(which become re-drops).
291+
//
292+
// This doesn't seem to be necessary after the connecting check above.
293+
// Kept for safety.
289294
lb.picker = &errPicker{err: balancer.ErrNoSubConnAvailable}
290295
return
291296
}
292-
if len(lb.fullServerList) <= 0 {
297+
if lb.inFallback {
293298
lb.picker = newRRPicker(readySCs)
294299
return
295300
}
@@ -305,6 +310,34 @@ func (lb *lbBalancer) regeneratePicker(resetDrop bool) {
305310
prevLBPicker.updateReadySCs(readySCs)
306311
}
307312

313+
// aggregateSubConnStats calculate the aggregated state of SubConns in
314+
// lb.SubConns. These SubConns are subconns in use (when switching between
315+
// fallback and grpclb). lb.scState contains states for all SubConns, including
316+
// those in cache (SubConns are cached for 10 seconds after remove).
317+
//
318+
// The aggregated state is:
319+
// - If at least one SubConn in Ready, the aggregated state is Ready;
320+
// - Else if at least one SubConn in Connecting, the aggregated state is Connecting;
321+
// - Else the aggregated state is TransientFailure.
322+
func (lb *lbBalancer) aggregateSubConnStates() connectivity.State {
323+
var numConnecting uint64
324+
325+
for _, sc := range lb.subConns {
326+
if state, ok := lb.scStates[sc]; ok {
327+
switch state {
328+
case connectivity.Ready:
329+
return connectivity.Ready
330+
case connectivity.Connecting:
331+
numConnecting++
332+
}
333+
}
334+
}
335+
if numConnecting > 0 {
336+
return connectivity.Connecting
337+
}
338+
return connectivity.TransientFailure
339+
}
340+
308341
func (lb *lbBalancer) HandleSubConnStateChange(sc balancer.SubConn, s connectivity.State) {
309342
if grpclog.V(2) {
310343
grpclog.Infof("lbBalancer: handle SubConn state change: %p, %v", sc, s)
@@ -328,18 +361,33 @@ func (lb *lbBalancer) HandleSubConnStateChange(sc balancer.SubConn, s connectivi
328361
// kept the sc's state in scStates. Remove state for this sc here.
329362
delete(lb.scStates, sc)
330363
}
364+
// Force regenerate picker if
365+
// - this sc became ready from not-ready
366+
// - this sc became not-ready from ready
367+
lb.updateStateAndPicker((oldS == connectivity.Ready) != (s == connectivity.Ready), false)
368+
369+
// Enter fallback when the aggregated state is not Ready and the connection
370+
// to remote balancer is lost.
371+
if lb.state != connectivity.Ready {
372+
if !lb.inFallback && !lb.remoteBalancerConnected {
373+
// Enter fallback.
374+
lb.refreshSubConns(lb.resolvedBackendAddrs, false)
375+
}
376+
}
377+
}
331378

379+
// updateStateAndPicker re-calculate the aggregated state, and regenerate picker
380+
// if overall state is changed.
381+
//
382+
// If forceRegeneratePicker is true, picker will be regenerated.
383+
func (lb *lbBalancer) updateStateAndPicker(forceRegeneratePicker bool, resetDrop bool) {
332384
oldAggrState := lb.state
333-
lb.state = lb.csEvltr.RecordTransition(oldS, s)
334-
385+
lb.state = lb.aggregateSubConnStates()
335386
// Regenerate picker when one of the following happens:
336-
// - this sc became ready from not-ready
337-
// - this sc became not-ready from ready
338-
// - the aggregated state of balancer became TransientFailure from non-TransientFailure
339-
// - the aggregated state of balancer became non-TransientFailure from TransientFailure
340-
if (oldS == connectivity.Ready) != (s == connectivity.Ready) ||
341-
(lb.state == connectivity.TransientFailure) != (oldAggrState == connectivity.TransientFailure) {
342-
lb.regeneratePicker(false)
387+
// - caller wants to regenerate
388+
// - the aggregated state changed
389+
if forceRegeneratePicker || (lb.state != oldAggrState) {
390+
lb.regeneratePicker(resetDrop)
343391
}
344392

345393
lb.cc.UpdateBalancerState(lb.state, lb.picker)
@@ -357,11 +405,11 @@ func (lb *lbBalancer) fallbackToBackendsAfter(fallbackTimeout time.Duration) {
357405
return
358406
}
359407
lb.mu.Lock()
360-
if lb.serverListReceived {
408+
if lb.inFallback || lb.serverListReceived {
361409
lb.mu.Unlock()
362410
return
363411
}
364-
lb.fallbackTimerExpired = true
412+
// Enter fallback.
365413
lb.refreshSubConns(lb.resolvedBackendAddrs, false)
366414
lb.mu.Unlock()
367415
}
@@ -405,10 +453,7 @@ func (lb *lbBalancer) HandleResolvedAddrs(addrs []resolver.Address, err error) {
405453

406454
lb.mu.Lock()
407455
lb.resolvedBackendAddrs = backendAddrs
408-
// If serverListReceived is true, connection to remote balancer was
409-
// successful and there's no need to do fallback anymore.
410-
// If fallbackTimerExpired is false, fallback hasn't happened yet.
411-
if !lb.serverListReceived && lb.fallbackTimerExpired {
456+
if lb.inFallback {
412457
// This means we received a new list of resolved backends, and we are
413458
// still in fallback mode. Need to update the list of backends we are
414459
// using to the new list of backends.

balancer/grpclb/grpclb_remote_balancer.go

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -85,24 +85,26 @@ func (lb *lbBalancer) processServerList(l *lbpb.ServerList) {
8585
backendAddrs = append(backendAddrs, addr)
8686
}
8787

88-
// Call refreshSubConns to create/remove SubConns.
88+
// Call refreshSubConns to create/remove SubConns. If we are in fallback,
89+
// this is also exiting fallback.
8990
lb.refreshSubConns(backendAddrs, true)
90-
// Regenerate and update picker no matter if there's update on backends (if
91-
// any SubConn will be newed/removed). Because since the full serverList was
92-
// different, there might be updates in drops or pick weights(different
93-
// number of duplicates). We need to update picker with the fulllist.
94-
//
95-
// Now with cache, even if SubConn was newed/removed, there might be no
96-
// state changes.
97-
lb.regeneratePicker(true)
98-
lb.cc.UpdateBalancerState(lb.state, lb.picker)
9991
}
10092

101-
// refreshSubConns creates/removes SubConns with backendAddrs. It returns a bool
102-
// indicating whether the backendAddrs are different from the cached
103-
// backendAddrs (whether any SubConn was newed/removed).
93+
// refreshSubConns creates/removes SubConns with backendAddrs, and refreshes
94+
// balancer state and picker.
95+
//
10496
// Caller must hold lb.mu.
10597
func (lb *lbBalancer) refreshSubConns(backendAddrs []resolver.Address, fromGRPCLBServer bool) {
98+
defer func() {
99+
// Regenerate and update picker after refreshing subconns because with
100+
// cache, even if SubConn was newed/removed, there might be no state
101+
// changes (the subconn will be kept in cache, not actually
102+
// newed/removed).
103+
lb.updateStateAndPicker(true, true)
104+
}()
105+
106+
lb.inFallback = !fromGRPCLBServer
107+
106108
opts := balancer.NewSubConnOptions{}
107109
if fromGRPCLBServer {
108110
opts.CredsBundle = lb.grpclbBackendCreds
@@ -218,6 +220,9 @@ func (lb *lbBalancer) callRemoteBalancer() (backoff bool, _ error) {
218220
if err != nil {
219221
return true, fmt.Errorf("grpclb: failed to perform RPC to the remote balancer %v", err)
220222
}
223+
lb.mu.Lock()
224+
lb.remoteBalancerConnected = true
225+
lb.mu.Unlock()
221226

222227
// grpclb handshake on the stream.
223228
initReq := &lbpb.LoadBalanceRequest{
@@ -270,6 +275,17 @@ func (lb *lbBalancer) watchRemoteBalancer() {
270275
// Trigger a re-resolve when the stream errors.
271276
lb.cc.cc.ResolveNow(resolver.ResolveNowOption{})
272277

278+
lb.mu.Lock()
279+
lb.remoteBalancerConnected = false
280+
lb.fullServerList = nil
281+
// Enter fallback when connection to remote balancer is lost, and the
282+
// aggregated state is not Ready.
283+
if !lb.inFallback && lb.state != connectivity.Ready {
284+
// Entering fallback.
285+
lb.refreshSubConns(lb.resolvedBackendAddrs, false)
286+
}
287+
lb.mu.Unlock()
288+
273289
if !doBackoff {
274290
retryCount = 0
275291
continue

balancer/grpclb/grpclb_test.go

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -230,18 +230,21 @@ func (b *remoteBalancer) BalanceLoad(stream lbgrpc.LoadBalancer_BalanceLoadServe
230230
b.stats.merge(req.GetClientStats())
231231
}
232232
}()
233-
for v := range b.sls {
234-
resp = &lbpb.LoadBalanceResponse{
235-
LoadBalanceResponseType: &lbpb.LoadBalanceResponse_ServerList{
236-
ServerList: v,
237-
},
233+
for {
234+
select {
235+
case v := <-b.sls:
236+
resp = &lbpb.LoadBalanceResponse{
237+
LoadBalanceResponseType: &lbpb.LoadBalanceResponse_ServerList{
238+
ServerList: v,
239+
},
240+
}
241+
case <-stream.Context().Done():
242+
return stream.Context().Err()
238243
}
239244
if err := stream.Send(resp); err != nil {
240245
return err
241246
}
242247
}
243-
<-b.done
244-
return nil
245248
}
246249

247250
type testServer struct {
@@ -297,6 +300,9 @@ type testServers struct {
297300
backends []*grpc.Server
298301
beIPs []net.IP
299302
bePorts []int
303+
304+
lbListener net.Listener
305+
beListeners []net.Listener
300306
}
301307

302308
func newLoadBalancer(numberOfBackends int) (tss *testServers, cleanup func(), err error) {
@@ -317,7 +323,7 @@ func newLoadBalancer(numberOfBackends int) (tss *testServers, cleanup func(), er
317323
beIPs = append(beIPs, beLis.Addr().(*net.TCPAddr).IP)
318324
bePorts = append(bePorts, beLis.Addr().(*net.TCPAddr).Port)
319325

320-
beListeners = append(beListeners, beLis)
326+
beListeners = append(beListeners, newRestartableListener(beLis))
321327
}
322328
backends := startBackends(beServerName, false, beListeners...)
323329

@@ -327,6 +333,7 @@ func newLoadBalancer(numberOfBackends int) (tss *testServers, cleanup func(), er
327333
err = fmt.Errorf("failed to create the listener for the load balancer %v", err)
328334
return
329335
}
336+
lbLis = newRestartableListener(lbLis)
330337
lbCreds := &serverNameCheckCreds{
331338
sn: lbServerName,
332339
}
@@ -344,6 +351,9 @@ func newLoadBalancer(numberOfBackends int) (tss *testServers, cleanup func(), er
344351
backends: backends,
345352
beIPs: beIPs,
346353
bePorts: bePorts,
354+
355+
lbListener: lbLis,
356+
beListeners: beListeners,
347357
}
348358
cleanup = func() {
349359
defer stopBackends(backends)
@@ -712,7 +722,7 @@ func TestFallback(t *testing.T) {
712722
testC := testpb.NewTestServiceClient(cc)
713723

714724
r.UpdateState(resolver.State{Addresses: []resolver.Address{{
715-
Addr: "",
725+
Addr: "invalid.address",
716726
Type: resolver.GRPCLB,
717727
ServerName: lbServerName,
718728
}, {
@@ -723,7 +733,7 @@ func TestFallback(t *testing.T) {
723733

724734
var p peer.Peer
725735
if _, err := testC.EmptyCall(context.Background(), &testpb.Empty{}, grpc.WaitForReady(true), grpc.Peer(&p)); err != nil {
726-
t.Fatalf("%v.EmptyCall(_, _) = _, %v, want _, <nil>", testC, err)
736+
t.Fatalf("_.EmptyCall(_, _) = _, %v, want _, <nil>", err)
727737
}
728738
if p.Addr.String() != beLis.Addr().String() {
729739
t.Fatalf("got peer: %v, want peer: %v", p.Addr, beLis.Addr())
@@ -739,16 +749,62 @@ func TestFallback(t *testing.T) {
739749
ServerName: beServerName,
740750
}}})
741751

752+
var backendUsed bool
742753
for i := 0; i < 1000; i++ {
743754
if _, err := testC.EmptyCall(context.Background(), &testpb.Empty{}, grpc.WaitForReady(true), grpc.Peer(&p)); err != nil {
744755
t.Fatalf("%v.EmptyCall(_, _) = _, %v, want _, <nil>", testC, err)
745756
}
746757
if p.Addr.(*net.TCPAddr).Port == tss.bePorts[0] {
747-
return
758+
backendUsed = true
759+
break
748760
}
749761
time.Sleep(time.Millisecond)
750762
}
751-
t.Fatalf("No RPC sent to backend behind remote balancer after 1 second")
763+
if !backendUsed {
764+
t.Fatalf("No RPC sent to backend behind remote balancer after 1 second")
765+
}
766+
767+
// Close backend and remote balancer connections, should use fallback.
768+
tss.beListeners[0].(*restartableListener).stopPreviousConns()
769+
tss.lbListener.(*restartableListener).stopPreviousConns()
770+
time.Sleep(time.Second)
771+
772+
var fallbackUsed bool
773+
for i := 0; i < 1000; i++ {
774+
if _, err := testC.EmptyCall(context.Background(), &testpb.Empty{}, grpc.WaitForReady(true), grpc.Peer(&p)); err != nil {
775+
t.Fatalf("%v.EmptyCall(_, _) = _, %v, want _, <nil>", testC, err)
776+
}
777+
if p.Addr.String() == beLis.Addr().String() {
778+
fallbackUsed = true
779+
break
780+
}
781+
time.Sleep(time.Millisecond)
782+
}
783+
if !fallbackUsed {
784+
t.Fatalf("No RPC sent to fallback after 1 second")
785+
}
786+
787+
// Restart backend and remote balancer, should not use backends.
788+
tss.beListeners[0].(*restartableListener).restart()
789+
tss.lbListener.(*restartableListener).restart()
790+
tss.ls.sls <- sl
791+
792+
time.Sleep(time.Second)
793+
794+
var backendUsed2 bool
795+
for i := 0; i < 1000; i++ {
796+
if _, err := testC.EmptyCall(context.Background(), &testpb.Empty{}, grpc.WaitForReady(true), grpc.Peer(&p)); err != nil {
797+
t.Fatalf("%v.EmptyCall(_, _) = _, %v, want _, <nil>", testC, err)
798+
}
799+
if p.Addr.(*net.TCPAddr).Port == tss.bePorts[0] {
800+
backendUsed2 = true
801+
break
802+
}
803+
time.Sleep(time.Millisecond)
804+
}
805+
if !backendUsed2 {
806+
t.Fatalf("No RPC sent to backend behind remote balancer after 1 second")
807+
}
752808
}
753809

754810
// The remote balancer sends response with duplicates to grpclb client.

0 commit comments

Comments
 (0)