| /* | |
| * | |
| * Copyright 2023 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 a client for the custom load balancer example. | |
| package main | |
| import ( | |
| "context" | |
| "fmt" | |
| "log" | |
| "strings" | |
| "time" | |
| "google.golang.org/grpc" | |
| "google.golang.org/grpc/credentials/insecure" | |
| _ "google.golang.org/grpc/examples/features/customloadbalancer/client/customroundrobin" // To register custom_round_robin. | |
| pb "google.golang.org/grpc/examples/features/proto/echo" | |
| "google.golang.org/grpc/internal" | |
| "google.golang.org/grpc/peer" | |
| "google.golang.org/grpc/resolver" | |
| "google.golang.org/grpc/resolver/manual" | |
| "google.golang.org/grpc/serviceconfig" | |
| ) | |
| var ( | |
| addr1 = "localhost:50050" | |
| addr2 = "localhost:50051" | |
| ) | |
| func main() { | |
| mr := manual.NewBuilderWithScheme("example") | |
| defer mr.Close() | |
| // You can also plug in your own custom lb policy, which needs to be | |
| // configurable. This n is configurable. Try changing n and see how the | |
| // behavior changes. | |
| json := `{"loadBalancingConfig": [{"custom_round_robin":{"chooseSecond": 3}}]}` | |
| sc := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(json) | |
| mr.InitialState(resolver.State{ | |
| Endpoints: []resolver.Endpoint{ | |
| {Addresses: []resolver.Address{{Addr: addr1}}}, | |
| {Addresses: []resolver.Address{{Addr: addr2}}}, | |
| }, | |
| ServiceConfig: sc, | |
| }) | |
| cc, err := grpc.NewClient(mr.Scheme()+":///", grpc.WithResolvers(mr), grpc.WithTransportCredentials(insecure.NewCredentials())) | |
| if err != nil { | |
| log.Fatalf("grpc.NewClient() failed: %v", err) | |
| } | |
| defer cc.Close() | |
| ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) | |
| defer cancel() | |
| ec := pb.NewEchoClient(cc) | |
| if err := waitForDistribution(ctx, ec); err != nil { | |
| log.Fatal(err) | |
| } | |
| fmt.Println("Successful multiple iterations of 1:2 ratio") | |
| } | |
| // waitForDistribution makes RPC's on the echo client until 3 RPC's follow the | |
| // same 1:2 address ratio for the peer. Returns an error if fails to do so | |
| // before context timeout. | |
| func waitForDistribution(ctx context.Context, ec pb.EchoClient) error { | |
| for { | |
| results := make(map[string]uint32) | |
| InnerLoop: | |
| for { | |
| if ctx.Err() != nil { | |
| return fmt.Errorf("timeout waiting for 1:2 distribution between addresses %v and %v", addr1, addr2) | |
| } | |
| for i := 0; i < 3; i++ { | |
| res := make(map[string]uint32) | |
| for j := 0; j < 3; j++ { | |
| var peer peer.Peer | |
| r, err := ec.UnaryEcho(ctx, &pb.EchoRequest{Message: "this is examples/customloadbalancing"}, grpc.Peer(&peer)) | |
| if err != nil { | |
| return fmt.Errorf("UnaryEcho failed: %v", err) | |
| } | |
| fmt.Println(r) | |
| peerAddr := peer.Addr.String() | |
| if !strings.HasSuffix(peerAddr, "50050") && !strings.HasSuffix(peerAddr, "50051") { | |
| return fmt.Errorf("peer address was not one of %v or %v, got: %v", addr1, addr2, peerAddr) | |
| } | |
| res[peerAddr]++ | |
| time.Sleep(time.Millisecond) | |
| } | |
| // Make sure the addresses come in a 1:2 ratio for this | |
| // iteration. | |
| var seen1, seen2 bool | |
| for addr, count := range res { | |
| if count != 1 && count != 2 { | |
| break InnerLoop | |
| } | |
| if count == 1 { | |
| if seen1 { | |
| break InnerLoop | |
| } | |
| seen1 = true | |
| } | |
| if count == 2 { | |
| if seen2 { | |
| break InnerLoop | |
| } | |
| seen2 = true | |
| } | |
| results[addr] = results[addr] + count | |
| } | |
| if !seen1 || !seen2 { | |
| break InnerLoop | |
| } | |
| } | |
| // Make sure iteration is 3 and 6 for addresses seen. This makes | |
| // sure the distribution is the same 1:2 ratio for each iteration. | |
| var seen3, seen6 bool | |
| for _, count := range results { | |
| if count != 3 && count != 6 { | |
| break InnerLoop | |
| } | |
| if count == 3 { | |
| if seen3 { | |
| break InnerLoop | |
| } | |
| seen3 = true | |
| } | |
| if count == 6 { | |
| if seen6 { | |
| break InnerLoop | |
| } | |
| seen6 = true | |
| } | |
| return nil | |
| } | |
| if !seen3 || !seen6 { | |
| break InnerLoop | |
| } | |
| } | |
| } | |
| } |