-
Notifications
You must be signed in to change notification settings - Fork 4.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
examples/features/loadbalancing: Add custom lb example #6649
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,14 +21,19 @@ package main | |
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"log" | ||
"sync/atomic" | ||
"time" | ||
|
||
"google.golang.org/grpc" | ||
"google.golang.org/grpc/balancer" | ||
"google.golang.org/grpc/balancer/base" | ||
"google.golang.org/grpc/credentials/insecure" | ||
ecpb "google.golang.org/grpc/examples/features/proto/echo" | ||
"google.golang.org/grpc/resolver" | ||
"google.golang.org/grpc/serviceconfig" | ||
) | ||
|
||
const ( | ||
|
@@ -84,11 +89,24 @@ func main() { | |
|
||
fmt.Println("--- calling helloworld.Greeter/SayHello with round_robin ---") | ||
makeRPCs(roundrobinConn, 10) | ||
// You can also plug in your own custom lb policy, which needs to be | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a blank line above this for better visual separation please. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
// configurable. This n is configurable. Try changing it and see how the | ||
// behavior changes. | ||
customroundrobinConn, err := grpc.Dial( | ||
fmt.Sprintf("%s:///%s", exampleScheme, exampleServiceName), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's pull this out at the top of the function: target := fmt.Sprintf("%s:///%s", exampleScheme, exampleServiceName) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. Good point. |
||
grpc.WithDefaultServiceConfig(`{"loadBalancingConfig": [{"custom_round_robin":{"n": 3}}]}`), | ||
grpc.WithTransportCredentials(insecure.NewCredentials()), | ||
) | ||
if err != nil { | ||
log.Fatalf("did not connect: %v", err) | ||
} | ||
defer customroundrobinConn.Close() | ||
fmt.Println("--- calling helloworld.Greeter/SayHello with custom_round_robin ---") | ||
makeRPCs(customroundrobinConn, 20) | ||
} | ||
|
||
// Following is an example name resolver implementation. Read the name | ||
// resolution example to learn more about it. | ||
|
||
type exampleResolverBuilder struct{} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't believe this example intends to show off custom resolvers - can this be replaced with the manual resolver instead? We may not have had it when this was created. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Switched. |
||
|
||
func (*exampleResolverBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) { | ||
|
@@ -123,4 +141,87 @@ func (*exampleResolver) Close() {} | |
|
||
func init() { | ||
resolver.Register(&exampleResolverBuilder{}) | ||
balancer.Register(&customRoundRobinBuilder{}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe put the custom LB policy into its own file (or even its own sub-package), since that's how we expect it to be used typically? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
} | ||
|
||
const customRRName = "custom_round_robin" | ||
|
||
type customRRConfig struct { | ||
serviceconfig.LoadBalancingConfig `json:"-"` | ||
|
||
// N represents how often pick iterations chose the second SubConn | ||
// in the list. Defaults to 3. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correction: 2 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah good catch. Switched the default in ParseConfig() to 3. |
||
N uint32 `json:"n,omitempty"` | ||
} | ||
|
||
func (customRoundRobinBuilder) ParseConfig(s json.RawMessage) (serviceconfig.LoadBalancingConfig, error) { | ||
lbConfig := &customRRConfig{ | ||
N: 2, | ||
} | ||
if err := json.Unmarshal(s, lbConfig); err != nil { | ||
return nil, fmt.Errorf("custom-round-robin: unable to unmarshal customRRConfig: %v", err) | ||
} | ||
return lbConfig, nil | ||
} | ||
|
||
type customRoundRobinBuilder struct{} | ||
|
||
func (customRoundRobinBuilder) Name() string { | ||
return customRRName | ||
} | ||
|
||
func (customRoundRobinBuilder) Build(cc balancer.ClientConn, bOpts balancer.BuildOptions) balancer.Balancer { | ||
crr := &customRoundRobin{} | ||
baseBuilder := base.NewBalancerBuilder(customRRName, crr, base.Config{HealthCheck: true}) | ||
crr.Balancer = baseBuilder.Build(cc, bOpts) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm really not a fan of the base balancer...are we sure we want users using it as a template for how to do their own LB policy? I think it would be better to show the actual connection management APIs here instead. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Switched to petiole policy wrapping pick first after offline discussion. |
||
return crr | ||
} | ||
|
||
type customRoundRobin struct { | ||
// Embeds balancer.Balancer because needs to intercept UpdateClientConnState | ||
// to learn about N. | ||
balancer.Balancer | ||
n uint32 | ||
} | ||
|
||
func (crr *customRoundRobin) UpdateClientConnState(state balancer.ClientConnState) error { | ||
crrCfg, ok := state.BalancerConfig.(*customRRConfig) | ||
if !ok { | ||
return balancer.ErrBadResolverState | ||
} | ||
crr.n = crrCfg.N | ||
return crr.Balancer.UpdateClientConnState(state) | ||
} | ||
|
||
func (crr *customRoundRobin) Build(info base.PickerBuildInfo) balancer.Picker { | ||
if len(info.ReadySCs) == 0 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you want to make this deterministic, then also do this until There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Made deterministic in new PR based off child balancer connectivity states. |
||
return base.NewErrPicker(balancer.ErrNoSubConnAvailable) | ||
} | ||
|
||
scs := make([]balancer.SubConn, 0, len(info.ReadySCs)) | ||
for sc := range info.ReadySCs { | ||
scs = append(scs, sc) | ||
} | ||
return &customRoundRobinPicker{ | ||
subConns: scs, | ||
n: crr.n, | ||
next: 0, | ||
} | ||
} | ||
|
||
type customRoundRobinPicker struct { | ||
subConns []balancer.SubConn | ||
n uint32 | ||
|
||
next uint32 | ||
} | ||
|
||
func (crrp *customRoundRobinPicker) Pick(info balancer.PickInfo) (balancer.PickResult, error) { | ||
next := atomic.AddUint32(&crrp.next, 1) | ||
index := 0 | ||
if next%crrp.n == 0 { | ||
index = 1 | ||
} | ||
sc := crrp.subConns[index%len(crrp.subConns)] | ||
return balancer.PickResult{SubConn: sc}, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a similar explanation of this case, keeping a similar structure, rather than just starting with output text. "The third client is configured to use a custom ............"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.