From 3778847a7312396bad7da7404ad5256c11c2d644 Mon Sep 17 00:00:00 2001 From: AbserAri <32089134+yhyddr@users.noreply.github.com> Date: Fri, 4 Oct 2019 07:04:43 +0800 Subject: [PATCH] examples: create an example for enabling and configuring retry (#3028) --- examples/features/retry/README.md | 66 ++++++++++++++++ examples/features/retry/client/main.go | 78 +++++++++++++++++++ examples/features/retry/server/main.go | 103 +++++++++++++++++++++++++ 3 files changed, 247 insertions(+) create mode 100644 examples/features/retry/README.md create mode 100644 examples/features/retry/client/main.go create mode 100644 examples/features/retry/server/main.go diff --git a/examples/features/retry/README.md b/examples/features/retry/README.md new file mode 100644 index 000000000..f56d438ad --- /dev/null +++ b/examples/features/retry/README.md @@ -0,0 +1,66 @@ +# Retry + +This example shows how to enable and configure retry on gRPC clients. + +## Documentation + +[gRFC for client-side retry support](https://github.com/grpc/proposal/blob/master/A6-client-retries.md) + +## Try it + +This example includes a service implementation that fails requests three times with status +code `Unavailable`, then passes the fourth. The client is configured to make four retry attempts +when receiving an `Unavailable` status code. + +First start the server: + +```bash +go run server/main.go +``` + +Then run the client. Note that when running the client, `GRPC_GO_RETRY=on` must be set in +your environment: + +```bash +GRPC_GO_RETRY=on go run client/main.go +``` + +## Usage + +### Define your retry policy + +Retry is enabled via the service config, which can be provided by the name resolver or +a DialOption (described below). In the below config, we set retry policy for the +"grpc.example.echo.Echo" method. + +MaxAttempts: how many times to attempt the RPC before failing. +InitialBackoff, MaxBackoff, BackoffMultiplier: configures delay between attempts. +RetryableStatusCodes: Retry only when receiving these status codes. + +```go + var retryPolicy = `{ + "methodConfig": [{ + // config per method or all methods under service + "name": [{"service": "grpc.examples.echo.Echo"}], + "waitForReady": true, + + "retryPolicy": { + "MaxAttempts": 4, + "InitialBackoff": ".01s", + "MaxBackoff": ".01s", + "BackoffMultiplier": 1.0, + // this value is grpc code + "RetryableStatusCodes": [ "UNAVAILABLE" ] + } + }] + }` +``` + +### Providing the retry policy as a DialOption + +To use the above service config, pass it with `grpc.WithDefaultServiceConfig` to +`grpc.Dial`. + +```go +conn, err := grpc.Dial(ctx,grpc.WithInsecure(), grpc.WithDefaultServiceConfig(retryPolicy)) +``` diff --git a/examples/features/retry/client/main.go b/examples/features/retry/client/main.go new file mode 100644 index 000000000..73147cfe0 --- /dev/null +++ b/examples/features/retry/client/main.go @@ -0,0 +1,78 @@ +/* + * + * Copyright 2019 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" + "flag" + "log" + "time" + + "google.golang.org/grpc" + pb "google.golang.org/grpc/examples/features/proto/echo" +) + +var ( + addr = flag.String("addr", "localhost:50052", "the address to connect to") + // see https://github.com/grpc/grpc/blob/master/doc/service_config.md to know more about service config + retryPolicy = `{ + "methodConfig": [{ + "name": [{"service": "grpc.examples.echo.Echo"}], + "waitForReady": true, + "retryPolicy": { + "MaxAttempts": 4, + "InitialBackoff": ".01s", + "MaxBackoff": ".01s", + "BackoffMultiplier": 1.0, + "RetryableStatusCodes": [ "UNAVAILABLE" ] + } + }]}` +) + +// use grpc.WithDefaultServiceConfig() to set service config +func retryDial() (*grpc.ClientConn, error) { + return grpc.Dial(*addr, grpc.WithInsecure(), grpc.WithDefaultServiceConfig(retryPolicy)) +} + +func main() { + flag.Parse() + + // Set up a connection to the server. + conn, err := retryDial() + if err != nil { + log.Fatalf("did not connect: %v", err) + } + defer func() { + if e := conn.Close(); e != nil { + log.Printf("failed to close connection: %s", e) + } + }() + + c := pb.NewEchoClient(conn) + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + reply, err := c.UnaryEcho(ctx, &pb.EchoRequest{Message: "Try and Success"}) + if err != nil { + log.Fatalf("UnaryEcho error: %v", err) + } + log.Printf("UnaryEcho reply: %v", reply) +} diff --git a/examples/features/retry/server/main.go b/examples/features/retry/server/main.go new file mode 100644 index 000000000..a4450e5b5 --- /dev/null +++ b/examples/features/retry/server/main.go @@ -0,0 +1,103 @@ +/* + * + * Copyright 2019 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" + "flag" + "fmt" + "log" + "net" + "sync" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + pb "google.golang.org/grpc/examples/features/proto/echo" + "google.golang.org/grpc/status" +) + +var port = flag.Int("port", 50052, "port number") + +type failingServer struct { + mu sync.Mutex + + reqCounter uint + reqModulo uint +} + +// this method will fail reqModulo - 1 times RPCs and return status code Unavailable, +// and succeeded RPC on reqModulo times. +func (s *failingServer) maybeFailRequest() error { + s.mu.Lock() + defer s.mu.Unlock() + s.reqCounter++ + if (s.reqModulo > 0) && (s.reqCounter%s.reqModulo == 0) { + return nil + } + + return status.Errorf(codes.Unavailable, "maybeFailRequest: failing it") +} + +func (s *failingServer) UnaryEcho(ctx context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) { + if err := s.maybeFailRequest(); err != nil { + log.Println("request failed count:", s.reqCounter) + return nil, err + } + + log.Println("request succeeded count:", s.reqCounter) + return &pb.EchoResponse{Message: req.Message}, nil +} + +func (s *failingServer) ServerStreamingEcho(req *pb.EchoRequest, stream pb.Echo_ServerStreamingEchoServer) error { + return status.Error(codes.Unimplemented, "RPC unimplemented") +} + +func (s *failingServer) ClientStreamingEcho(stream pb.Echo_ClientStreamingEchoServer) error { + return status.Error(codes.Unimplemented, "RPC unimplemented") +} + +func (s *failingServer) BidirectionalStreamingEcho(stream pb.Echo_BidirectionalStreamingEchoServer) error { + return status.Error(codes.Unimplemented, "RPC unimplemented") +} + +func main() { + flag.Parse() + + address := fmt.Sprintf(":%v", *port) + lis, err := net.Listen("tcp", address) + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + fmt.Println("listen on address", address) + + s := grpc.NewServer() + + // Configure server to pass every fourth RPC; + // client is configured to make four attempts. + failingservice := &failingServer{ + reqCounter: 0, + reqModulo: 4, + } + + pb.RegisterEchoServer(s, failingservice) + if err := s.Serve(lis); err != nil { + log.Fatalf("failed to serve: %v", err) + } +}