github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/balancer/grpclb/grpclb.go (about) 1 /* 2 * 3 * Copyright 2016 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 defines a grpclb balancer. 20 // 21 // To install grpclb balancer, import this package as: 22 // import _ "github.com/hxx258456/ccgo/grpc/balancer/grpclb" 23 package grpclb 24 25 import ( 26 "context" 27 "errors" 28 "fmt" 29 "sync" 30 "time" 31 32 grpc "github.com/hxx258456/ccgo/grpc" 33 "github.com/hxx258456/ccgo/grpc/balancer" 34 grpclbstate "github.com/hxx258456/ccgo/grpc/balancer/grpclb/state" 35 "github.com/hxx258456/ccgo/grpc/connectivity" 36 "github.com/hxx258456/ccgo/grpc/credentials" 37 "github.com/hxx258456/ccgo/grpc/grpclog" 38 "github.com/hxx258456/ccgo/grpc/internal" 39 "github.com/hxx258456/ccgo/grpc/internal/backoff" 40 "github.com/hxx258456/ccgo/grpc/internal/resolver/dns" 41 "github.com/hxx258456/ccgo/grpc/resolver" 42 43 durationpb "github.com/golang/protobuf/ptypes/duration" 44 lbpb "github.com/hxx258456/ccgo/grpc/balancer/grpclb/grpc_lb_v1" 45 ) 46 47 const ( 48 lbTokenKey = "lb-token" 49 defaultFallbackTimeout = 10 * time.Second 50 grpclbName = "grpclb" 51 ) 52 53 var errServerTerminatedConnection = errors.New("grpclb: failed to recv server list: server terminated connection") 54 var logger = grpclog.Component("grpclb") 55 56 func convertDuration(d *durationpb.Duration) time.Duration { 57 if d == nil { 58 return 0 59 } 60 return time.Duration(d.Seconds)*time.Second + time.Duration(d.Nanos)*time.Nanosecond 61 } 62 63 // Client API for LoadBalancer service. 64 // Mostly copied from generated pb.go file. 65 // To avoid circular dependency. 66 type loadBalancerClient struct { 67 cc *grpc.ClientConn 68 } 69 70 func (c *loadBalancerClient) BalanceLoad(ctx context.Context, opts ...grpc.CallOption) (*balanceLoadClientStream, error) { 71 desc := &grpc.StreamDesc{ 72 StreamName: "BalanceLoad", 73 ServerStreams: true, 74 ClientStreams: true, 75 } 76 stream, err := c.cc.NewStream(ctx, desc, "/grpc.lb.v1.LoadBalancer/BalanceLoad", opts...) 77 if err != nil { 78 return nil, err 79 } 80 x := &balanceLoadClientStream{stream} 81 return x, nil 82 } 83 84 type balanceLoadClientStream struct { 85 grpc.ClientStream 86 } 87 88 func (x *balanceLoadClientStream) Send(m *lbpb.LoadBalanceRequest) error { 89 return x.ClientStream.SendMsg(m) 90 } 91 92 func (x *balanceLoadClientStream) Recv() (*lbpb.LoadBalanceResponse, error) { 93 m := new(lbpb.LoadBalanceResponse) 94 if err := x.ClientStream.RecvMsg(m); err != nil { 95 return nil, err 96 } 97 return m, nil 98 } 99 100 func init() { 101 balancer.Register(newLBBuilder()) 102 dns.EnableSRVLookups = true 103 } 104 105 // newLBBuilder creates a builder for grpclb. 106 func newLBBuilder() balancer.Builder { 107 return newLBBuilderWithFallbackTimeout(defaultFallbackTimeout) 108 } 109 110 // newLBBuilderWithFallbackTimeout creates a grpclb builder with the given 111 // fallbackTimeout. If no response is received from the remote balancer within 112 // fallbackTimeout, the backend addresses from the resolved address list will be 113 // used. 114 // 115 // Only call this function when a non-default fallback timeout is needed. 116 func newLBBuilderWithFallbackTimeout(fallbackTimeout time.Duration) balancer.Builder { 117 return &lbBuilder{ 118 fallbackTimeout: fallbackTimeout, 119 } 120 } 121 122 type lbBuilder struct { 123 fallbackTimeout time.Duration 124 } 125 126 func (b *lbBuilder) Name() string { 127 return grpclbName 128 } 129 130 func (b *lbBuilder) Build(cc balancer.ClientConn, opt balancer.BuildOptions) balancer.Balancer { 131 // This generates a manual resolver builder with a fixed scheme. This 132 // scheme will be used to dial to remote LB, so we can send filtered 133 // address updates to remote LB ClientConn using this manual resolver. 134 r := &lbManualResolver{scheme: "grpclb-internal", ccb: cc} 135 136 lb := &lbBalancer{ 137 cc: newLBCacheClientConn(cc), 138 dialTarget: opt.Target.Endpoint, 139 target: opt.Target.Endpoint, 140 opt: opt, 141 fallbackTimeout: b.fallbackTimeout, 142 doneCh: make(chan struct{}), 143 144 manualResolver: r, 145 subConns: make(map[resolver.Address]balancer.SubConn), 146 scStates: make(map[balancer.SubConn]connectivity.State), 147 picker: &errPicker{err: balancer.ErrNoSubConnAvailable}, 148 clientStats: newRPCStats(), 149 backoff: backoff.DefaultExponential, // TODO: make backoff configurable. 150 } 151 152 var err error 153 if opt.CredsBundle != nil { 154 lb.grpclbClientConnCreds, err = opt.CredsBundle.NewWithMode(internal.CredsBundleModeBalancer) 155 if err != nil { 156 logger.Warningf("lbBalancer: client connection creds NewWithMode failed: %v", err) 157 } 158 lb.grpclbBackendCreds, err = opt.CredsBundle.NewWithMode(internal.CredsBundleModeBackendFromBalancer) 159 if err != nil { 160 logger.Warningf("lbBalancer: backend creds NewWithMode failed: %v", err) 161 } 162 } 163 164 return lb 165 } 166 167 type lbBalancer struct { 168 cc *lbCacheClientConn 169 dialTarget string // user's dial target 170 target string // same as dialTarget unless overridden in service config 171 opt balancer.BuildOptions 172 173 usePickFirst bool 174 175 // grpclbClientConnCreds is the creds bundle to be used to connect to grpclb 176 // servers. If it's nil, use the TransportCredentials from BuildOptions 177 // instead. 178 grpclbClientConnCreds credentials.Bundle 179 // grpclbBackendCreds is the creds bundle to be used for addresses that are 180 // returned by grpclb server. If it's nil, don't set anything when creating 181 // SubConns. 182 grpclbBackendCreds credentials.Bundle 183 184 fallbackTimeout time.Duration 185 doneCh chan struct{} 186 187 // manualResolver is used in the remote LB ClientConn inside grpclb. When 188 // resolved address updates are received by grpclb, filtered updates will be 189 // send to remote LB ClientConn through this resolver. 190 manualResolver *lbManualResolver 191 // The ClientConn to talk to the remote balancer. 192 ccRemoteLB *remoteBalancerCCWrapper 193 // backoff for calling remote balancer. 194 backoff backoff.Strategy 195 196 // Support client side load reporting. Each picker gets a reference to this, 197 // and will update its content. 198 clientStats *rpcStats 199 200 mu sync.Mutex // guards everything following. 201 // The full server list including drops, used to check if the newly received 202 // serverList contains anything new. Each generate picker will also have 203 // reference to this list to do the first layer pick. 204 fullServerList []*lbpb.Server 205 // Backend addresses. It's kept so the addresses are available when 206 // switching between round_robin and pickfirst. 207 backendAddrs []resolver.Address 208 // All backends addresses, with metadata set to nil. This list contains all 209 // backend addresses in the same order and with the same duplicates as in 210 // serverlist. When generating picker, a SubConn slice with the same order 211 // but with only READY SCs will be gerenated. 212 backendAddrsWithoutMetadata []resolver.Address 213 // Roundrobin functionalities. 214 state connectivity.State 215 subConns map[resolver.Address]balancer.SubConn // Used to new/remove SubConn. 216 scStates map[balancer.SubConn]connectivity.State // Used to filter READY SubConns. 217 picker balancer.Picker 218 // Support fallback to resolved backend addresses if there's no response 219 // from remote balancer within fallbackTimeout. 220 remoteBalancerConnected bool 221 serverListReceived bool 222 inFallback bool 223 // resolvedBackendAddrs is resolvedAddrs minus remote balancers. It's set 224 // when resolved address updates are received, and read in the goroutine 225 // handling fallback. 226 resolvedBackendAddrs []resolver.Address 227 connErr error // the last connection error 228 } 229 230 // regeneratePicker takes a snapshot of the balancer, and generates a picker from 231 // it. The picker 232 // - always returns ErrTransientFailure if the balancer is in TransientFailure, 233 // - does two layer roundrobin pick otherwise. 234 // Caller must hold lb.mu. 235 func (lb *lbBalancer) regeneratePicker(resetDrop bool) { 236 if lb.state == connectivity.TransientFailure { 237 lb.picker = &errPicker{err: fmt.Errorf("all SubConns are in TransientFailure, last connection error: %v", lb.connErr)} 238 return 239 } 240 241 if lb.state == connectivity.Connecting { 242 lb.picker = &errPicker{err: balancer.ErrNoSubConnAvailable} 243 return 244 } 245 246 var readySCs []balancer.SubConn 247 if lb.usePickFirst { 248 for _, sc := range lb.subConns { 249 readySCs = append(readySCs, sc) 250 break 251 } 252 } else { 253 for _, a := range lb.backendAddrsWithoutMetadata { 254 if sc, ok := lb.subConns[a]; ok { 255 if st, ok := lb.scStates[sc]; ok && st == connectivity.Ready { 256 readySCs = append(readySCs, sc) 257 } 258 } 259 } 260 } 261 262 if len(readySCs) <= 0 { 263 // If there's no ready SubConns, always re-pick. This is to avoid drops 264 // unless at least one SubConn is ready. Otherwise we may drop more 265 // often than want because of drops + re-picks(which become re-drops). 266 // 267 // This doesn't seem to be necessary after the connecting check above. 268 // Kept for safety. 269 lb.picker = &errPicker{err: balancer.ErrNoSubConnAvailable} 270 return 271 } 272 if lb.inFallback { 273 lb.picker = newRRPicker(readySCs) 274 return 275 } 276 if resetDrop { 277 lb.picker = newLBPicker(lb.fullServerList, readySCs, lb.clientStats) 278 return 279 } 280 prevLBPicker, ok := lb.picker.(*lbPicker) 281 if !ok { 282 lb.picker = newLBPicker(lb.fullServerList, readySCs, lb.clientStats) 283 return 284 } 285 prevLBPicker.updateReadySCs(readySCs) 286 } 287 288 // aggregateSubConnStats calculate the aggregated state of SubConns in 289 // lb.SubConns. These SubConns are subconns in use (when switching between 290 // fallback and grpclb). lb.scState contains states for all SubConns, including 291 // those in cache (SubConns are cached for 10 seconds after remove). 292 // 293 // The aggregated state is: 294 // - If at least one SubConn in Ready, the aggregated state is Ready; 295 // - Else if at least one SubConn in Connecting or IDLE, the aggregated state is Connecting; 296 // - It's OK to consider IDLE as Connecting. SubConns never stay in IDLE, 297 // they start to connect immediately. But there's a race between the overall 298 // state is reported, and when the new SubConn state arrives. And SubConns 299 // never go back to IDLE. 300 // - Else the aggregated state is TransientFailure. 301 func (lb *lbBalancer) aggregateSubConnStates() connectivity.State { 302 var numConnecting uint64 303 304 for _, sc := range lb.subConns { 305 if state, ok := lb.scStates[sc]; ok { 306 switch state { 307 case connectivity.Ready: 308 return connectivity.Ready 309 case connectivity.Connecting, connectivity.Idle: 310 numConnecting++ 311 } 312 } 313 } 314 if numConnecting > 0 { 315 return connectivity.Connecting 316 } 317 return connectivity.TransientFailure 318 } 319 320 func (lb *lbBalancer) UpdateSubConnState(sc balancer.SubConn, scs balancer.SubConnState) { 321 s := scs.ConnectivityState 322 if logger.V(2) { 323 logger.Infof("lbBalancer: handle SubConn state change: %p, %v", sc, s) 324 } 325 lb.mu.Lock() 326 defer lb.mu.Unlock() 327 328 oldS, ok := lb.scStates[sc] 329 if !ok { 330 if logger.V(2) { 331 logger.Infof("lbBalancer: got state changes for an unknown SubConn: %p, %v", sc, s) 332 } 333 return 334 } 335 lb.scStates[sc] = s 336 switch s { 337 case connectivity.Idle: 338 sc.Connect() 339 case connectivity.Shutdown: 340 // When an address was removed by resolver, b called RemoveSubConn but 341 // kept the sc's state in scStates. Remove state for this sc here. 342 delete(lb.scStates, sc) 343 case connectivity.TransientFailure: 344 lb.connErr = scs.ConnectionError 345 } 346 // Force regenerate picker if 347 // - this sc became ready from not-ready 348 // - this sc became not-ready from ready 349 lb.updateStateAndPicker((oldS == connectivity.Ready) != (s == connectivity.Ready), false) 350 351 // Enter fallback when the aggregated state is not Ready and the connection 352 // to remote balancer is lost. 353 if lb.state != connectivity.Ready { 354 if !lb.inFallback && !lb.remoteBalancerConnected { 355 // Enter fallback. 356 lb.refreshSubConns(lb.resolvedBackendAddrs, true, lb.usePickFirst) 357 } 358 } 359 } 360 361 // updateStateAndPicker re-calculate the aggregated state, and regenerate picker 362 // if overall state is changed. 363 // 364 // If forceRegeneratePicker is true, picker will be regenerated. 365 func (lb *lbBalancer) updateStateAndPicker(forceRegeneratePicker bool, resetDrop bool) { 366 oldAggrState := lb.state 367 lb.state = lb.aggregateSubConnStates() 368 // Regenerate picker when one of the following happens: 369 // - caller wants to regenerate 370 // - the aggregated state changed 371 if forceRegeneratePicker || (lb.state != oldAggrState) { 372 lb.regeneratePicker(resetDrop) 373 } 374 375 lb.cc.UpdateState(balancer.State{ConnectivityState: lb.state, Picker: lb.picker}) 376 } 377 378 // fallbackToBackendsAfter blocks for fallbackTimeout and falls back to use 379 // resolved backends (backends received from resolver, not from remote balancer) 380 // if no connection to remote balancers was successful. 381 func (lb *lbBalancer) fallbackToBackendsAfter(fallbackTimeout time.Duration) { 382 timer := time.NewTimer(fallbackTimeout) 383 defer timer.Stop() 384 select { 385 case <-timer.C: 386 case <-lb.doneCh: 387 return 388 } 389 lb.mu.Lock() 390 if lb.inFallback || lb.serverListReceived { 391 lb.mu.Unlock() 392 return 393 } 394 // Enter fallback. 395 lb.refreshSubConns(lb.resolvedBackendAddrs, true, lb.usePickFirst) 396 lb.mu.Unlock() 397 } 398 399 func (lb *lbBalancer) handleServiceConfig(gc *grpclbServiceConfig) { 400 lb.mu.Lock() 401 defer lb.mu.Unlock() 402 403 // grpclb uses the user's dial target to populate the `Name` field of the 404 // `InitialLoadBalanceRequest` message sent to the remote balancer. But when 405 // grpclb is used a child policy in the context of RLS, we want the `Name` 406 // field to be populated with the value received from the RLS server. To 407 // support this use case, an optional "target_name" field has been added to 408 // the grpclb LB policy's config. If specified, it overrides the name of 409 // the target to be sent to the remote balancer; if not, the target to be 410 // sent to the balancer will continue to be obtained from the target URI 411 // passed to the gRPC client channel. Whenever that target to be sent to the 412 // balancer is updated, we need to restart the stream to the balancer as 413 // this target is sent in the first message on the stream. 414 if gc != nil { 415 target := lb.dialTarget 416 if gc.TargetName != "" { 417 target = gc.TargetName 418 } 419 if target != lb.target { 420 lb.target = target 421 if lb.ccRemoteLB != nil { 422 lb.ccRemoteLB.cancelRemoteBalancerCall() 423 } 424 } 425 } 426 427 newUsePickFirst := childIsPickFirst(gc) 428 if lb.usePickFirst == newUsePickFirst { 429 return 430 } 431 if logger.V(2) { 432 logger.Infof("lbBalancer: switching mode, new usePickFirst: %+v", newUsePickFirst) 433 } 434 lb.refreshSubConns(lb.backendAddrs, lb.inFallback, newUsePickFirst) 435 } 436 437 func (lb *lbBalancer) ResolverError(error) { 438 // Ignore resolver errors. GRPCLB is not selected unless the resolver 439 // works at least once. 440 } 441 442 func (lb *lbBalancer) UpdateClientConnState(ccs balancer.ClientConnState) error { 443 if logger.V(2) { 444 logger.Infof("lbBalancer: UpdateClientConnState: %+v", ccs) 445 } 446 gc, _ := ccs.BalancerConfig.(*grpclbServiceConfig) 447 lb.handleServiceConfig(gc) 448 449 addrs := ccs.ResolverState.Addresses 450 451 var remoteBalancerAddrs, backendAddrs []resolver.Address 452 for _, a := range addrs { 453 if a.Type == resolver.GRPCLB { 454 a.Type = resolver.Backend 455 remoteBalancerAddrs = append(remoteBalancerAddrs, a) 456 } else { 457 backendAddrs = append(backendAddrs, a) 458 } 459 } 460 if sd := grpclbstate.Get(ccs.ResolverState); sd != nil { 461 // Override any balancer addresses provided via 462 // ccs.ResolverState.Addresses. 463 remoteBalancerAddrs = sd.BalancerAddresses 464 } 465 466 if len(backendAddrs)+len(remoteBalancerAddrs) == 0 { 467 // There should be at least one address, either grpclb server or 468 // fallback. Empty address is not valid. 469 return balancer.ErrBadResolverState 470 } 471 472 if len(remoteBalancerAddrs) == 0 { 473 if lb.ccRemoteLB != nil { 474 lb.ccRemoteLB.close() 475 lb.ccRemoteLB = nil 476 } 477 } else if lb.ccRemoteLB == nil { 478 // First time receiving resolved addresses, create a cc to remote 479 // balancers. 480 lb.newRemoteBalancerCCWrapper() 481 // Start the fallback goroutine. 482 go lb.fallbackToBackendsAfter(lb.fallbackTimeout) 483 } 484 485 if lb.ccRemoteLB != nil { 486 // cc to remote balancers uses lb.manualResolver. Send the updated remote 487 // balancer addresses to it through manualResolver. 488 lb.manualResolver.UpdateState(resolver.State{Addresses: remoteBalancerAddrs}) 489 } 490 491 lb.mu.Lock() 492 lb.resolvedBackendAddrs = backendAddrs 493 if len(remoteBalancerAddrs) == 0 || lb.inFallback { 494 // If there's no remote balancer address in ClientConn update, grpclb 495 // enters fallback mode immediately. 496 // 497 // If a new update is received while grpclb is in fallback, update the 498 // list of backends being used to the new fallback backends. 499 lb.refreshSubConns(lb.resolvedBackendAddrs, true, lb.usePickFirst) 500 } 501 lb.mu.Unlock() 502 return nil 503 } 504 505 func (lb *lbBalancer) Close() { 506 select { 507 case <-lb.doneCh: 508 return 509 default: 510 } 511 close(lb.doneCh) 512 if lb.ccRemoteLB != nil { 513 lb.ccRemoteLB.close() 514 } 515 lb.cc.close() 516 } 517 518 func (lb *lbBalancer) ExitIdle() {}