From 2197c7b0de70765f2b5180ed60acce8992afc308 Mon Sep 17 00:00:00 2001 From: Menghan Li Date: Thu, 27 Dec 2018 14:29:52 -0800 Subject: [PATCH] example: load_balancing (#2504) --- examples/features/load_balancing/README.md | 82 ++++++++++++ .../features/load_balancing/client/main.go | 125 ++++++++++++++++++ .../features/load_balancing/server/main.go | 79 +++++++++++ 3 files changed, 286 insertions(+) create mode 100644 examples/features/load_balancing/README.md create mode 100644 examples/features/load_balancing/client/main.go create mode 100644 examples/features/load_balancing/server/main.go diff --git a/examples/features/load_balancing/README.md b/examples/features/load_balancing/README.md new file mode 100644 index 00000000..dccdf706 --- /dev/null +++ b/examples/features/load_balancing/README.md @@ -0,0 +1,82 @@ +# Load balancing + +This examples shows how `ClientConn` can pick different load balancing policies. + +Note: to show the effect of load balancers, an example resolver is installed in +this example to get the backend addresses. It's suggested to read the name +resolver example before this example. + +## Try it + +``` +go run server/main.go +``` + +``` +go run client/main.go +``` + +## Explanation + +Two echo servers are serving on ":50051" and ":50052". They will include their +serving address in the response. So the server on ":50051" will reply to the RPC +with `this is examples/load_balancing (from :50051)`. + +Two clients are created, to connect to both of these servers (they get both +server addresses from the name resolver). + +Each client picks a different load balancer (using `grpc.WithBalancerName`): +`pick_first` or `round_robin`. (These two policies are supported in gRPC by +default. To add a custom balancing policy, implement the interfaces defined in +https://godoc.org/google.golang.org/grpc/balancer). + +Note that balancers can also be switched using service config, which allows +service owners (instead of client owners) to pick the balancer to use. Service +config doc is available at +https://github.com/grpc/grpc/blob/master/doc/service_config.md. + +### pick_first + +The first client is configured to use `pick_first`. `pick_first` tries to +connect to the first address, uses it for all RPCs if it connects, or try the +next address if it fails (and keep doing that until one connection is +successful). Because of this, all the RPCs will be sent to the same backend. The +responses received all show the same backend address. + +``` +this is examples/load_balancing (from :50051) +this is examples/load_balancing (from :50051) +this is examples/load_balancing (from :50051) +this is examples/load_balancing (from :50051) +this is examples/load_balancing (from :50051) +this is examples/load_balancing (from :50051) +this is examples/load_balancing (from :50051) +this is examples/load_balancing (from :50051) +this is examples/load_balancing (from :50051) +this is examples/load_balancing (from :50051) +``` + +### round_robin + +The second client is configured to use `round_robin`. `round_robin` connects to +all the addresses it sees, and sends an RPC to each backend one at a time in +order. E.g. the first RPC will be sent to backend-1, the second RPC will be be +sent to backend-2, and the third RPC will be be sent to backend-1 again. + +``` +this is examples/load_balancing (from :50051) +this is examples/load_balancing (from :50051) +this is examples/load_balancing (from :50052) +this is examples/load_balancing (from :50051) +this is examples/load_balancing (from :50052) +this is examples/load_balancing (from :50051) +this is examples/load_balancing (from :50052) +this is examples/load_balancing (from :50051) +this is examples/load_balancing (from :50052) +this is examples/load_balancing (from :50051) +``` + +Note that it's possible to see two continues RPC sent to the same backend. +That's because `round_robin` only picks the connections ready for RPCs. So if +one of the two connections is not ready for some reason, all RPCs will be sent +to the ready connection. diff --git a/examples/features/load_balancing/client/main.go b/examples/features/load_balancing/client/main.go new file mode 100644 index 00000000..602b6a86 --- /dev/null +++ b/examples/features/load_balancing/client/main.go @@ -0,0 +1,125 @@ +/* + * + * Copyright 2018 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// Binary client is an example client. +package main + +import ( + "context" + "fmt" + "log" + "time" + + "google.golang.org/grpc" + ecpb "google.golang.org/grpc/examples/features/proto/echo" + "google.golang.org/grpc/resolver" +) + +const ( + exampleScheme = "example" + exampleServiceName = "lb.example.grpc.io" +) + +var addrs = []string{"localhost:50051", "localhost:50052"} + +func callUnaryEcho(c ecpb.EchoClient, message string) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := c.UnaryEcho(ctx, &ecpb.EchoRequest{Message: message}) + if err != nil { + log.Fatalf("could not greet: %v", err) + } + fmt.Println(r.Message) +} + +func makeRPCs(cc *grpc.ClientConn, n int) { + hwc := ecpb.NewEchoClient(cc) + for i := 0; i < n; i++ { + callUnaryEcho(hwc, "this is examples/load_balancing") + } +} + +func main() { + pickfirstConn, err := grpc.Dial( + fmt.Sprintf("%s:///%s", exampleScheme, exampleServiceName), + // grpc.WithBalancerName("pick_first"), // "pick_first" is the default, so this DialOption is not necessary. + grpc.WithInsecure(), + ) + if err != nil { + log.Fatalf("did not connect: %v", err) + } + defer pickfirstConn.Close() + + fmt.Println("--- calling helloworld.Greeter/SayHello with pick_first ---") + makeRPCs(pickfirstConn, 10) + + fmt.Println() + + // Make another ClientConn with round_robin policy. + roundrobinConn, err := grpc.Dial( + fmt.Sprintf("%s:///%s", exampleScheme, exampleServiceName), + grpc.WithBalancerName("round_robin"), // This sets the initial balancing policy. + grpc.WithInsecure(), + ) + if err != nil { + log.Fatalf("did not connect: %v", err) + } + defer roundrobinConn.Close() + + fmt.Println("--- calling helloworld.Greeter/SayHello with round_robin ---") + makeRPCs(roundrobinConn, 10) +} + +// Following is an example name resolver implementation. Read the name +// resolution example to learn more about it. + +type exampleResolverBuilder struct{} + +func (*exampleResolverBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOption) (resolver.Resolver, error) { + r := &exampleResolver{ + target: target, + cc: cc, + addrsStore: map[string][]string{ + exampleServiceName: addrs, + }, + } + r.start() + return r, nil +} +func (*exampleResolverBuilder) Scheme() string { return exampleScheme } + +type exampleResolver struct { + target resolver.Target + cc resolver.ClientConn + addrsStore map[string][]string +} + +func (r *exampleResolver) start() { + addrStrs := r.addrsStore[r.target.Endpoint] + addrs := make([]resolver.Address, len(addrStrs), len(addrStrs)) + for i, s := range addrStrs { + addrs[i] = resolver.Address{Addr: s} + } + r.cc.NewAddress(addrs) +} +func (*exampleResolver) ResolveNow(o resolver.ResolveNowOption) {} +func (*exampleResolver) Close() {} + +func init() { + resolver.Register(&exampleResolverBuilder{}) +} diff --git a/examples/features/load_balancing/server/main.go b/examples/features/load_balancing/server/main.go new file mode 100644 index 00000000..8a1dad51 --- /dev/null +++ b/examples/features/load_balancing/server/main.go @@ -0,0 +1,79 @@ +/* + * + * Copyright 2018 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// Binary server is an example server. +package main + +import ( + "context" + "fmt" + "log" + "net" + "sync" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + ecpb "google.golang.org/grpc/examples/features/proto/echo" + "google.golang.org/grpc/status" +) + +var ( + addrs = []string{":50051", ":50052"} +) + +type ecServer struct { + addr string +} + +func (s *ecServer) UnaryEcho(ctx context.Context, req *ecpb.EchoRequest) (*ecpb.EchoResponse, error) { + return &ecpb.EchoResponse{Message: fmt.Sprintf("%s (from %s)", req.Message, s.addr)}, nil +} +func (s *ecServer) ServerStreamingEcho(*ecpb.EchoRequest, ecpb.Echo_ServerStreamingEchoServer) error { + return status.Errorf(codes.Unimplemented, "not implemented") +} +func (s *ecServer) ClientStreamingEcho(ecpb.Echo_ClientStreamingEchoServer) error { + return status.Errorf(codes.Unimplemented, "not implemented") +} +func (s *ecServer) BidirectionalStreamingEcho(ecpb.Echo_BidirectionalStreamingEchoServer) error { + return status.Errorf(codes.Unimplemented, "not implemented") +} + +func startServer(addr string) { + lis, err := net.Listen("tcp", addr) + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + s := grpc.NewServer() + ecpb.RegisterEchoServer(s, &ecServer{addr: addr}) + log.Printf("serving on %s\n", addr) + if err := s.Serve(lis); err != nil { + log.Fatalf("failed to serve: %v", err) + } +} + +func main() { + var wg sync.WaitGroup + for _, addr := range addrs { + wg.Add(1) + go func(addr string) { + defer wg.Done() + startServer(addr) + }(addr) + } + wg.Wait() +}