From 87b86edc5929330e1b97948ca3cc46199f3c22c8 Mon Sep 17 00:00:00 2001 From: abser Date: Sat, 14 Sep 2019 19:01:52 +0800 Subject: [PATCH 01/15] add retry README.md --- examples/features/retry/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 examples/features/retry/README.md diff --git a/examples/features/retry/README.md b/examples/features/retry/README.md new file mode 100644 index 00000000000..2b16d046c84 --- /dev/null +++ b/examples/features/retry/README.md @@ -0,0 +1,18 @@ +# Reflection + +This example shows how enabling and configuring retry on gRPC client. + +# Try it + +```go +go run server/main.go +``` + +```go +go run client/main.go +``` + +To use `gRPC CLI`, follow +https://github.com/grpc/grpc-go/blob/master/Documentation/server-reflection-tutorial.md#grpc-cli. + +To use `grpcurl`, see https://github.com/fullstorydev/grpcurl. From f387e21ea18abec98cf88f117ff81a8a99aecdcd Mon Sep 17 00:00:00 2001 From: abser Date: Sat, 14 Sep 2019 19:06:51 +0800 Subject: [PATCH 02/15] init example/feature/retry create an example for enabling and configuring retry --- examples/features/retry/README.md | 9 ++++----- examples/features/retry/client/main.go | 0 examples/features/retry/server/main.go | 0 3 files changed, 4 insertions(+), 5 deletions(-) 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 index 2b16d046c84..f08c5824f12 100644 --- a/examples/features/retry/README.md +++ b/examples/features/retry/README.md @@ -2,6 +2,10 @@ This example shows how enabling and configuring retry on gRPC client. +# Read + +Document about this example. a link here + # Try it ```go @@ -11,8 +15,3 @@ go run server/main.go ```go go run client/main.go ``` - -To use `gRPC CLI`, follow -https://github.com/grpc/grpc-go/blob/master/Documentation/server-reflection-tutorial.md#grpc-cli. - -To use `grpcurl`, see https://github.com/fullstorydev/grpcurl. diff --git a/examples/features/retry/client/main.go b/examples/features/retry/client/main.go new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/features/retry/server/main.go b/examples/features/retry/server/main.go new file mode 100644 index 00000000000..e69de29bb2d From 95bb363508be3e5701fd568d555366e49e7928ac Mon Sep 17 00:00:00 2001 From: abser Date: Sat, 14 Sep 2019 23:00:13 +0800 Subject: [PATCH 03/15] example: set retry environment and use error example --- examples/features/retry/client/main.go | 77 ++++++++++++++++++++++++ examples/features/retry/server/main.go | 83 ++++++++++++++++++++++++++ 2 files changed, 160 insertions(+) diff --git a/examples/features/retry/client/main.go b/examples/features/retry/client/main.go index e69de29bb2d..cf81802bf15 100644 --- a/examples/features/retry/client/main.go +++ b/examples/features/retry/client/main.go @@ -0,0 +1,77 @@ +/* + * + * 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" + "flag" + "log" + "os" + "time" + + epb "google.golang.org/genproto/googleapis/rpc/errdetails" + "google.golang.org/grpc" + pb "google.golang.org/grpc/examples/helloworld/helloworld" + "google.golang.org/grpc/status" +) + +var addr = flag.String("addr", "localhost:50052", "the address to connect to") + +func main() { + flag.Parse() + var config string = `{"retryPolicy": { + "maxAttempts": 4, + "initialBackoff": "0.1s", + "maxBackoff": "1s", + "backoffMultiplier": 2, + "retryableStatusCodes": [ + "UNAVAILABLE" + ] + } + }` + // Set up a connection to the server. + conn, err := grpc.Dial(*addr, grpc.WithInsecure(), grpc.WithDefaultServiceConfig(config)) + 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.NewGreeterClient(conn) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "world"}) + if err != nil { + s := status.Convert(err) + for _, d := range s.Details() { + switch info := d.(type) { + case *epb.QuotaFailure: + log.Printf("Quota failure: %s", info) + default: + log.Printf("Unexpected type: %s", info) + } + } + os.Exit(1) + } + log.Printf("Greeting: %s", r.Message) +} diff --git a/examples/features/retry/server/main.go b/examples/features/retry/server/main.go index e69de29bb2d..d306c4eb0cc 100644 --- a/examples/features/retry/server/main.go +++ b/examples/features/retry/server/main.go @@ -0,0 +1,83 @@ +/* + * + * 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" + "flag" + "fmt" + "log" + "net" + "sync" + + epb "google.golang.org/genproto/googleapis/rpc/errdetails" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + pb "google.golang.org/grpc/examples/helloworld/helloworld" + "google.golang.org/grpc/status" +) + +var port = flag.Int("port", 50052, "port number") + +// server is used to implement helloworld.GreeterServer. +type server struct { + mu sync.Mutex + count map[string]int +} + +// SayHello implements helloworld.GreeterServer +func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { + s.mu.Lock() + defer s.mu.Unlock() + // Track the number of times the user has been greeted. + s.count[in.Name]++ + if s.count[in.Name] > 1 { + st := status.New(codes.ResourceExhausted, "Request limit exceeded.") + ds, err := st.WithDetails( + &epb.QuotaFailure{ + Violations: []*epb.QuotaFailure_Violation{{ + Subject: fmt.Sprintf("name:%s", in.Name), + Description: "Limit one greeting per person", + }}, + }, + ) + if err != nil { + return nil, st.Err() + } + return nil, ds.Err() + } + return &pb.HelloReply{Message: "Hello " + in.Name}, nil +} + +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) + } + + s := grpc.NewServer() + pb.RegisterGreeterServer(s, &server{count: make(map[string]int)}) + if err := s.Serve(lis); err != nil { + log.Fatalf("failed to serve: %v", err) + } +} From 87c210a72166f102f1e6bc1ecbc88de9b3439a7d Mon Sep 17 00:00:00 2001 From: abser Date: Sun, 15 Sep 2019 15:25:45 +0800 Subject: [PATCH 04/15] set config raw json --- examples/features/retry/README.md | 119 ++++++++++++++++++++++++- examples/features/retry/client/main.go | 67 +++++++++++--- 2 files changed, 173 insertions(+), 13 deletions(-) diff --git a/examples/features/retry/README.md b/examples/features/retry/README.md index f08c5824f12..4a680882ec9 100644 --- a/examples/features/retry/README.md +++ b/examples/features/retry/README.md @@ -4,7 +4,8 @@ This example shows how enabling and configuring retry on gRPC client. # Read -Document about this example. a link here +[Document about this example](https://github.com/grpc/proposal/blob/master/A6-client-retries.md#retry-policy) + # Try it @@ -15,3 +16,119 @@ go run server/main.go ```go go run client/main.go ``` + +# Integration with Service Config +From [grpc/proposal](https://github.com/grpc/proposal/blob/master/A6-client-retries.md#integration-with-service-config) + +The retry policy is transmitted to the client through the service config mechanism. The following is what the JSON configuration file would look like: + + { + "loadBalancingPolicy": string, + + "methodConfig": [ + { + "name": [ + { + "service": string, + "method": string, + } + ], + + // Only one of retryPolicy or hedgingPolicy may be set. If neither is set, + // RPCs will not be retried or hedged. + + "retryPolicy": { + // The maximum number of RPC attempts, including the original RPC. + // + // This field is required and must be two or greater. + "maxAttempts": number, + + // Exponential backoff parameters. The initial retry attempt will occur at + // random(0, initialBackoff). In general, the nth attempt since the last + // server pushback response (if any), will occur at random(0, + // min(initialBackoff*backoffMultiplier**(n-1), maxBackoff)). + // The following two fields take their form from: + // https://developers.google.com/protocol-buffers/docs/proto3#json + // They are representations of the proto3 Duration type. Note that the + // numeric portion of the string must be a valid JSON number. + // They both must be greater than zero. + "initialBackoff": string, // Required. Long decimal with "s" appended + "maxBackoff": string, // Required. Long decimal with "s" appended + "backoffMultiplier": number // Required. Must be greater than zero. + + // The set of status codes which may be retried. + // + // Status codes are specified in the integer form or the case-insensitive + // string form (eg. [14], ["UNAVAILABLE"] or ["unavailable"]) + // + // This field is required and must be non-empty. + "retryableStatusCodes": [] + } + + "hedgingPolicy": { + // The hedging policy will send up to maxAttempts RPCs. + // This number represents the all RPC attempts, including the + // original and all the hedged RPCs. + // + // This field is required and must be two or greater. + "maxAttempts": number, + + // The original RPC will be sent immediately, but the maxAttempts-1 + // subsequent hedged RPCs will be sent at intervals of every hedgingDelay. + // Set this to "0s", or leave unset, to immediately send all maxAttempts RPCs. + // hedgingDelay takes its form from: + // https://developers.google.com/protocol-buffers/docs/proto3#json + // It is a representation of the proto3 Duration type. Note that the + // numeric portion of the string must be a valid JSON number. + "hedgingDelay": string, + + // The set of status codes which indicate other hedged RPCs may still + // succeed. If a non-fatal status code is returned by the server, hedged + // RPCs will continue. Otherwise, outstanding requests will be canceled and + // the error returned to the client application layer. + // + // Status codes are specified in the integer form or the case-insensitive + // string form (eg. [14], ["UNAVAILABLE"] or ["unavailable"]) + // + // This field is optional. + "nonFatalStatusCodes": [] + } + + "waitForReady": bool, + "timeout": string, + "maxRequestMessageBytes": number, + "maxResponseMessageBytes": number + } + ] + + // If a RetryThrottlingPolicy is provided, gRPC will automatically throttle + // retry attempts and hedged RPCs when the client’s ratio of failures to + // successes exceeds a threshold. + // + // For each server name, the gRPC client will maintain a token_count which is + // initially set to maxTokens, and can take values between 0 and maxTokens. + // + // Every outgoing RPC (regardless of service or method invoked) will change + // token_count as follows: + // + // - Every failed RPC will decrement the token_count by 1. + // - Every successful RPC will increment the token_count by tokenRatio. + // + // If token_count is less than or equal to maxTokens / 2, then RPCs will not + // be retried and hedged RPCs will not be sent. + "retryThrottling": { + // The number of tokens starts at maxTokens. The token_count will always be + // between 0 and maxTokens. + // + // This field is required and must be in the range (0, 1000]. Up to 3 + // decimal places are supported + "maxTokens": number, + + // The amount of tokens to add on each successful RPC. Typically this will + // be some number between 0 and 1, e.g., 0.1. + // + // This field is required and must be greater than zero. Up to 3 decimal + // places are supported. + "tokenRatio": number + } + } \ No newline at end of file diff --git a/examples/features/retry/client/main.go b/examples/features/retry/client/main.go index cf81802bf15..32b2069d36a 100644 --- a/examples/features/retry/client/main.go +++ b/examples/features/retry/client/main.go @@ -32,22 +32,65 @@ import ( "google.golang.org/grpc/status" ) -var addr = flag.String("addr", "localhost:50052", "the address to connect to") +var ( + addr = flag.String("addr", "localhost:50052", "the address to connect to") + retryPolicy = `{ + "methodConfig": [ + { + "name": [ + { + "service": "", + "method": "" + } + ] + } + ], + "retryPolicy": { + "maxAttempts": 4, + "initialBackoff": "0.1s", + "maxBackoff": "1s", + "backoffMultiplier": 2, + "retryableStatusCodes": [ + "UNAVAILABLE" + ] + }, + "retryThrottling": { + "maxTokens": 10, + "tokenRatio": 0.1 + } + }` + hedgingPolicy = `{ + "methodConfig": [ + { + "name": [ + { + "service": "", + "method": "" + } + ] + } + ], + "hedgingPolicy": { + "maxAttempts": 4, + "hedgingDelay": "0.5s", + "nonFatalStatusCodes": [ + "UNAVAILABLE", + "INTERNAL", + "ABORTED" + ] + }, + "retryThrottling": { + "maxTokens": 10, + "tokenRatio": 0.1 + } + }` +) func main() { flag.Parse() - var config string = `{"retryPolicy": { - "maxAttempts": 4, - "initialBackoff": "0.1s", - "maxBackoff": "1s", - "backoffMultiplier": 2, - "retryableStatusCodes": [ - "UNAVAILABLE" - ] - } - }` + // Set up a connection to the server. - conn, err := grpc.Dial(*addr, grpc.WithInsecure(), grpc.WithDefaultServiceConfig(config)) + conn, err := grpc.Dial(*addr, grpc.WithInsecure(), grpc.WithDefaultServiceConfig(retryPolicy)) if err != nil { log.Fatalf("did not connect: %v", err) } From a156a10ab9900a65ab853a63d573a86b3d60397c Mon Sep 17 00:00:00 2001 From: abser Date: Sun, 15 Sep 2019 21:37:24 +0800 Subject: [PATCH 05/15] temporary use go-middleware-retry --- examples/features/retry/client/main.go | 76 +++++++++------------- examples/features/retry/server/main.go | 87 ++++++++++++++++++-------- 2 files changed, 91 insertions(+), 72 deletions(-) diff --git a/examples/features/retry/client/main.go b/examples/features/retry/client/main.go index 32b2069d36a..baba9305879 100644 --- a/examples/features/retry/client/main.go +++ b/examples/features/retry/client/main.go @@ -22,30 +22,19 @@ package main import ( "context" "flag" + "fmt" + "io" "log" - "os" "time" - epb "google.golang.org/genproto/googleapis/rpc/errdetails" + grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry" + pb "github.com/grpc-ecosystem/go-grpc-middleware/testing/testproto" "google.golang.org/grpc" - pb "google.golang.org/grpc/examples/helloworld/helloworld" - "google.golang.org/grpc/status" ) var ( addr = flag.String("addr", "localhost:50052", "the address to connect to") - retryPolicy = `{ - "methodConfig": [ - { - "name": [ - { - "service": "", - "method": "" - } - ] - } - ], - "retryPolicy": { + retryPolicy = `{"retryPolicy": { "maxAttempts": 4, "initialBackoff": "0.1s", "maxBackoff": "1s", @@ -59,20 +48,9 @@ var ( "tokenRatio": 0.1 } }` - hedgingPolicy = `{ - "methodConfig": [ - { - "name": [ - { - "service": "", - "method": "" - } - ] - } - ], - "hedgingPolicy": { + hedgingPolicy = `{"hedgingPolicy": { "maxAttempts": 4, - "hedgingDelay": "0.5s", + "hedgingDelay": "0s", "nonFatalStatusCodes": [ "UNAVAILABLE", "INTERNAL", @@ -86,11 +64,24 @@ var ( }` ) +func retryDial() (*grpc.ClientConn, error) { + return grpc.Dial(*addr, grpc.WithInsecure(), grpc.WithDefaultServiceConfig(retryPolicy)) +} + +func hedgingDial() (*grpc.ClientConn, error) { + return grpc.Dial(*addr, grpc.WithInsecure(), grpc.WithDefaultServiceConfig(hedgingPolicy)) +} + +func newCtx(timeout time.Duration) context.Context { + ctx, _ := context.WithTimeout(context.TODO(), timeout) + return ctx +} + func main() { flag.Parse() // Set up a connection to the server. - conn, err := grpc.Dial(*addr, grpc.WithInsecure(), grpc.WithDefaultServiceConfig(retryPolicy)) + conn, err := retryDial() if err != nil { log.Fatalf("did not connect: %v", err) } @@ -99,22 +90,17 @@ func main() { log.Printf("failed to close connection: %s", e) } }() - c := pb.NewGreeterClient(conn) - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "world"}) - if err != nil { - s := status.Convert(err) - for _, d := range s.Details() { - switch info := d.(type) { - case *epb.QuotaFailure: - log.Printf("Quota failure: %s", info) - default: - log.Printf("Unexpected type: %s", info) - } + c := pb.NewTestServiceClient(conn) + stream, _ := c.PingList(newCtx(1*time.Second), &pb.PingRequest{}, grpc_retry.WithMax(3)) + + for { + pong, err := stream.Recv() // retries happen here + if err == io.EOF { + break + } else if err != nil { + return } - os.Exit(1) + fmt.Printf("got pong: %v", pong) } - log.Printf("Greeting: %s", r.Message) } diff --git a/examples/features/retry/server/main.go b/examples/features/retry/server/main.go index d306c4eb0cc..da63d46493b 100644 --- a/examples/features/retry/server/main.go +++ b/examples/features/retry/server/main.go @@ -26,46 +26,79 @@ import ( "log" "net" "sync" + "time" - epb "google.golang.org/genproto/googleapis/rpc/errdetails" + server "github.com/grpc-ecosystem/go-grpc-middleware/testing" + pb "github.com/grpc-ecosystem/go-grpc-middleware/testing/testproto" "google.golang.org/grpc" "google.golang.org/grpc/codes" - pb "google.golang.org/grpc/examples/helloworld/helloworld" - "google.golang.org/grpc/status" ) var port = flag.Int("port", 50052, "port number") +var ( + retriableErrors = []codes.Code{codes.Unavailable, codes.DataLoss} + goodPing = &pb.PingRequest{Value: "something"} + noSleep = 0 * time.Second + retryTimeout = 50 * time.Millisecond +) + +type failingService struct { + pb.TestServiceServer + mu sync.Mutex + + reqCounter uint + reqModulo uint + reqSleep time.Duration + reqError codes.Code +} + +func (s *failingService) resetFailingConfiguration(modulo uint, errorCode codes.Code, sleepTime time.Duration) { + s.mu.Lock() + defer s.mu.Unlock() -// server is used to implement helloworld.GreeterServer. -type server struct { - mu sync.Mutex - count map[string]int + s.reqCounter = 0 + s.reqModulo = modulo + s.reqError = errorCode + s.reqSleep = sleepTime } -// SayHello implements helloworld.GreeterServer -func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { +func (s *failingService) requestCount() uint { s.mu.Lock() defer s.mu.Unlock() - // Track the number of times the user has been greeted. - s.count[in.Name]++ - if s.count[in.Name] > 1 { - st := status.New(codes.ResourceExhausted, "Request limit exceeded.") - ds, err := st.WithDetails( - &epb.QuotaFailure{ - Violations: []*epb.QuotaFailure_Violation{{ - Subject: fmt.Sprintf("name:%s", in.Name), - Description: "Limit one greeting per person", - }}, - }, - ) - if err != nil { - return nil, st.Err() - } - return nil, ds.Err() + return s.reqCounter +} + +func (s *failingService) maybeFailRequest() error { + s.mu.Lock() + defer s.mu.Unlock() + s.reqCounter++ + if (s.reqModulo > 0) && (s.reqCounter%s.reqModulo == 0) { + return nil } - return &pb.HelloReply{Message: "Hello " + in.Name}, nil + time.Sleep(s.reqSleep) + return grpc.Errorf(s.reqError, "maybeFailRequest: failing it") } +func (s *failingService) Ping(ctx context.Context, ping *pb.PingRequest) (*pb.PingResponse, error) { + if err := s.maybeFailRequest(); err != nil { + return nil, err + } + return s.TestServiceServer.Ping(ctx, ping) +} + +func (s *failingService) PingList(ping *pb.PingRequest, stream pb.TestService_PingListServer) error { + if err := s.maybeFailRequest(); err != nil { + return err + } + return s.TestServiceServer.PingList(ping, stream) +} + +func (s *failingService) PingStream(stream pb.TestService_PingStreamServer) error { + if err := s.maybeFailRequest(); err != nil { + return err + } + return s.TestServiceServer.PingStream(stream) +} func main() { flag.Parse() @@ -76,7 +109,7 @@ func main() { } s := grpc.NewServer() - pb.RegisterGreeterServer(s, &server{count: make(map[string]int)}) + pb.RegisterTestServiceServer(s, &server.TestPingService{}) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } From d39f7cfde759ee8ac1171c079e0f3c236975c88d Mon Sep 17 00:00:00 2001 From: abser Date: Mon, 16 Sep 2019 23:36:24 +0800 Subject: [PATCH 06/15] complete test retry server and client --- examples/features/retry/client/main.go | 23 ++++------- examples/features/retry/server/main.go | 54 ++++++++++++++++---------- 2 files changed, 41 insertions(+), 36 deletions(-) diff --git a/examples/features/retry/client/main.go b/examples/features/retry/client/main.go index baba9305879..fdeb90dc52a 100644 --- a/examples/features/retry/client/main.go +++ b/examples/features/retry/client/main.go @@ -23,13 +23,11 @@ import ( "context" "flag" "fmt" - "io" "log" "time" - grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry" - pb "github.com/grpc-ecosystem/go-grpc-middleware/testing/testproto" "google.golang.org/grpc" + pb "google.golang.org/grpc/examples/features/proto/echo" ) var ( @@ -73,7 +71,8 @@ func hedgingDial() (*grpc.ClientConn, error) { } func newCtx(timeout time.Duration) context.Context { - ctx, _ := context.WithTimeout(context.TODO(), timeout) + ctx, cancel := context.WithTimeout(context.TODO(), timeout) + defer cancel() return ctx } @@ -91,16 +90,10 @@ func main() { } }() - c := pb.NewTestServiceClient(conn) - stream, _ := c.PingList(newCtx(1*time.Second), &pb.PingRequest{}, grpc_retry.WithMax(3)) - - for { - pong, err := stream.Recv() // retries happen here - if err == io.EOF { - break - } else if err != nil { - return - } - fmt.Printf("got pong: %v", pong) + c := pb.NewEchoClient(conn) + reply, err := c.UnaryEcho(newCtx(1*time.Second), &pb.EchoRequest{Message: "Please Success"}) + if err != nil { + fmt.Println(err) } + fmt.Println(reply) } diff --git a/examples/features/retry/server/main.go b/examples/features/retry/server/main.go index da63d46493b..b99155a41f3 100644 --- a/examples/features/retry/server/main.go +++ b/examples/features/retry/server/main.go @@ -28,22 +28,22 @@ import ( "sync" "time" - server "github.com/grpc-ecosystem/go-grpc-middleware/testing" - pb "github.com/grpc-ecosystem/go-grpc-middleware/testing/testproto" "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") + var ( retriableErrors = []codes.Code{codes.Unavailable, codes.DataLoss} - goodPing = &pb.PingRequest{Value: "something"} + goodPing = &pb.EchoRequest{Message: "something"} noSleep = 0 * time.Second retryTimeout = 50 * time.Millisecond ) -type failingService struct { - pb.TestServiceServer +type failingServer struct { mu sync.Mutex reqCounter uint @@ -52,7 +52,7 @@ type failingService struct { reqError codes.Code } -func (s *failingService) resetFailingConfiguration(modulo uint, errorCode codes.Code, sleepTime time.Duration) { +func (s *failingServer) resetFailingConfiguration(modulo uint, errorCode codes.Code, sleepTime time.Duration) { s.mu.Lock() defer s.mu.Unlock() @@ -62,13 +62,13 @@ func (s *failingService) resetFailingConfiguration(modulo uint, errorCode codes. s.reqSleep = sleepTime } -func (s *failingService) requestCount() uint { +func (s *failingServer) requestCount() uint { s.mu.Lock() defer s.mu.Unlock() return s.reqCounter } -func (s *failingService) maybeFailRequest() error { +func (s *failingServer) maybeFailRequest() error { s.mu.Lock() defer s.mu.Unlock() s.reqCounter++ @@ -79,26 +79,28 @@ func (s *failingService) maybeFailRequest() error { return grpc.Errorf(s.reqError, "maybeFailRequest: failing it") } -func (s *failingService) Ping(ctx context.Context, ping *pb.PingRequest) (*pb.PingResponse, error) { +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 } - return s.TestServiceServer.Ping(ctx, ping) + + log.Println("request succeeded count:", s.reqCounter) + return &pb.EchoResponse{Message: req.Message}, nil } -func (s *failingService) PingList(ping *pb.PingRequest, stream pb.TestService_PingListServer) error { - if err := s.maybeFailRequest(); err != nil { - return err - } - return s.TestServiceServer.PingList(ping, stream) +func (s *failingServer) ServerStreamingEcho(req *pb.EchoRequest, stream pb.Echo_ServerStreamingEchoServer) error { + return status.Error(codes.Unimplemented, "RPC unimplemented") } -func (s *failingService) PingStream(stream pb.TestService_PingStreamServer) error { - if err := s.maybeFailRequest(); err != nil { - return err - } - return s.TestServiceServer.PingStream(stream) +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() @@ -107,9 +109,19 @@ func main() { if err != nil { log.Fatalf("failed to listen: %v", err) } + fmt.Println("listen on address", address) s := grpc.NewServer() - pb.RegisterTestServiceServer(s, &server.TestPingService{}) + + // a retry configuration + failingservice := &failingServer{ + reqCounter: 0, + reqModulo: 4, + reqError: codes.Unavailable, /* uint = 14 */ + reqSleep: 0, + } + + pb.RegisterEchoServer(s, failingservice) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } From 25a308d20a99545675567aaca7edae0c9ddefa4a Mon Sep 17 00:00:00 2001 From: abser Date: Wed, 18 Sep 2019 01:40:58 +0800 Subject: [PATCH 07/15] delete not implemented hedgingPolicy --- examples/features/retry/client/main.go | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/examples/features/retry/client/main.go b/examples/features/retry/client/main.go index fdeb90dc52a..ff30b5cc832 100644 --- a/examples/features/retry/client/main.go +++ b/examples/features/retry/client/main.go @@ -38,7 +38,7 @@ var ( "maxBackoff": "1s", "backoffMultiplier": 2, "retryableStatusCodes": [ - "UNAVAILABLE" + "Unavailable" ] }, "retryThrottling": { @@ -46,33 +46,15 @@ var ( "tokenRatio": 0.1 } }` - hedgingPolicy = `{"hedgingPolicy": { - "maxAttempts": 4, - "hedgingDelay": "0s", - "nonFatalStatusCodes": [ - "UNAVAILABLE", - "INTERNAL", - "ABORTED" - ] - }, - "retryThrottling": { - "maxTokens": 10, - "tokenRatio": 0.1 - } - }` ) func retryDial() (*grpc.ClientConn, error) { return grpc.Dial(*addr, grpc.WithInsecure(), grpc.WithDefaultServiceConfig(retryPolicy)) } -func hedgingDial() (*grpc.ClientConn, error) { - return grpc.Dial(*addr, grpc.WithInsecure(), grpc.WithDefaultServiceConfig(hedgingPolicy)) -} - +// use it for one value return func newCtx(timeout time.Duration) context.Context { - ctx, cancel := context.WithTimeout(context.TODO(), timeout) - defer cancel() + ctx, _ := context.WithTimeout(context.TODO(), timeout) return ctx } From 5219ec8349c471862c140540b4bbda8336291de1 Mon Sep 17 00:00:00 2001 From: abser Date: Wed, 18 Sep 2019 11:06:39 +0800 Subject: [PATCH 08/15] complete examples --- examples/features/retry/README.md | 22 ++++++++++++- examples/features/retry/client/main.go | 44 ++++++++++++-------------- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/examples/features/retry/README.md b/examples/features/retry/README.md index 4a680882ec9..cde8b025fa5 100644 --- a/examples/features/retry/README.md +++ b/examples/features/retry/README.md @@ -17,7 +17,27 @@ go run server/main.go go run client/main.go ``` -# Integration with Service Config +# Usage + + { + "methodConfig": [{ + // config per method or all methods, this is for all methods under grpc.example.echo.Echo + // 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" ] + } + }] + } + +# Know all retry policies Integration with Service Config From [grpc/proposal](https://github.com/grpc/proposal/blob/master/A6-client-retries.md#integration-with-service-config) The retry policy is transmitted to the client through the service config mechanism. The following is what the JSON configuration file would look like: diff --git a/examples/features/retry/client/main.go b/examples/features/retry/client/main.go index ff30b5cc832..6dd12f822aa 100644 --- a/examples/features/retry/client/main.go +++ b/examples/features/retry/client/main.go @@ -22,37 +22,35 @@ package main import ( "context" "flag" - "fmt" - "log" "time" "google.golang.org/grpc" pb "google.golang.org/grpc/examples/features/proto/echo" + "google.golang.org/grpc/grpclog" ) var ( - addr = flag.String("addr", "localhost:50052", "the address to connect to") - retryPolicy = `{"retryPolicy": { - "maxAttempts": 4, - "initialBackoff": "0.1s", - "maxBackoff": "1s", - "backoffMultiplier": 2, - "retryableStatusCodes": [ - "Unavailable" - ] - }, - "retryThrottling": { - "maxTokens": 10, - "tokenRatio": 0.1 - } - }` + 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)) } -// use it for one value return func newCtx(timeout time.Duration) context.Context { ctx, _ := context.WithTimeout(context.TODO(), timeout) return ctx @@ -64,18 +62,18 @@ func main() { // Set up a connection to the server. conn, err := retryDial() if err != nil { - log.Fatalf("did not connect: %v", err) + grpclog.Fatalf("did not connect: %v", err) } defer func() { if e := conn.Close(); e != nil { - log.Printf("failed to close connection: %s", e) + grpclog.Printf("failed to close connection: %s", e) } }() c := pb.NewEchoClient(conn) - reply, err := c.UnaryEcho(newCtx(1*time.Second), &pb.EchoRequest{Message: "Please Success"}) + reply, err := c.UnaryEcho(newCtx(1*time.Second), &pb.EchoRequest{Message: "Try and Success"}) if err != nil { - fmt.Println(err) + grpclog.Println(err) } - fmt.Println(reply) + grpclog.Println(reply) } From baa4b0ec6ad43cc9480fc0b4395ae4003872a80b Mon Sep 17 00:00:00 2001 From: abser Date: Wed, 18 Sep 2019 14:34:10 +0800 Subject: [PATCH 09/15] fix for CI/CD check --- examples/features/retry/client/main.go | 10 +++++----- examples/features/retry/server/main.go | 25 +------------------------ 2 files changed, 6 insertions(+), 29 deletions(-) diff --git a/examples/features/retry/client/main.go b/examples/features/retry/client/main.go index 6dd12f822aa..a010bfa4287 100644 --- a/examples/features/retry/client/main.go +++ b/examples/features/retry/client/main.go @@ -22,11 +22,11 @@ package main import ( "context" "flag" + "log" "time" "google.golang.org/grpc" pb "google.golang.org/grpc/examples/features/proto/echo" - "google.golang.org/grpc/grpclog" ) var ( @@ -62,18 +62,18 @@ func main() { // Set up a connection to the server. conn, err := retryDial() if err != nil { - grpclog.Fatalf("did not connect: %v", err) + log.Fatalf("did not connect: %v", err) } defer func() { if e := conn.Close(); e != nil { - grpclog.Printf("failed to close connection: %s", e) + log.Printf("failed to close connection: %s", e) } }() c := pb.NewEchoClient(conn) reply, err := c.UnaryEcho(newCtx(1*time.Second), &pb.EchoRequest{Message: "Try and Success"}) if err != nil { - grpclog.Println(err) + log.Println(err) } - grpclog.Println(reply) + log.Println(reply) } diff --git a/examples/features/retry/server/main.go b/examples/features/retry/server/main.go index b99155a41f3..8dc8e1a00dc 100644 --- a/examples/features/retry/server/main.go +++ b/examples/features/retry/server/main.go @@ -36,13 +36,6 @@ import ( var port = flag.Int("port", 50052, "port number") -var ( - retriableErrors = []codes.Code{codes.Unavailable, codes.DataLoss} - goodPing = &pb.EchoRequest{Message: "something"} - noSleep = 0 * time.Second - retryTimeout = 50 * time.Millisecond -) - type failingServer struct { mu sync.Mutex @@ -52,22 +45,6 @@ type failingServer struct { reqError codes.Code } -func (s *failingServer) resetFailingConfiguration(modulo uint, errorCode codes.Code, sleepTime time.Duration) { - s.mu.Lock() - defer s.mu.Unlock() - - s.reqCounter = 0 - s.reqModulo = modulo - s.reqError = errorCode - s.reqSleep = sleepTime -} - -func (s *failingServer) requestCount() uint { - s.mu.Lock() - defer s.mu.Unlock() - return s.reqCounter -} - func (s *failingServer) maybeFailRequest() error { s.mu.Lock() defer s.mu.Unlock() @@ -76,7 +53,7 @@ func (s *failingServer) maybeFailRequest() error { return nil } time.Sleep(s.reqSleep) - return grpc.Errorf(s.reqError, "maybeFailRequest: failing it") + return status.Errorf(s.reqError, "maybeFailRequest: failing it") } func (s *failingServer) UnaryEcho(ctx context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) { From b167b34ae1ace7e3750c336af05df19e6d2f3bc1 Mon Sep 17 00:00:00 2001 From: abser Date: Fri, 27 Sep 2019 09:51:44 +0800 Subject: [PATCH 10/15] fix with maintainer's advice --- examples/features/retry/README.md | 169 +++++++------------------ examples/features/retry/client/main.go | 12 +- examples/features/retry/server/main.go | 16 +-- 3 files changed, 64 insertions(+), 133 deletions(-) diff --git a/examples/features/retry/README.md b/examples/features/retry/README.md index cde8b025fa5..06ee4975a7f 100644 --- a/examples/features/retry/README.md +++ b/examples/features/retry/README.md @@ -1,6 +1,6 @@ -# Reflection +# Retry -This example shows how enabling and configuring retry on gRPC client. +This example shows how to enable and configure retry on gRPC client. # Read @@ -9,20 +9,40 @@ This example shows how enabling and configuring retry on gRPC client. # Try it -```go +we had set server service that will always return status code Unavailable until the every fourth Request.To demonstrate retry. + +that means client should retry 4 times and 3 times received Unavailable status. + +```bash go run server/main.go ``` -```go +set environment otherwise retry will not work. +```bash +GRPC_GO_RETRY=on +``` + +we had set retry policy to client to enable per RPC retry at most 4 times when response status is Unavailable. +```bash go run client/main.go ``` # Usage - { +### Define your retry policy + +In below, we set retry policy for "grpc.example.echo.Echo" methods. + +and retry policy : +MaxAttempts: how many times to retry until one of them succeeded. +InitialBackoff and MaxBackoff and BackoffMultiplier: time settings for delaying retries. +RetryableStatusCodes: Retry when received following response codes. + +set this as a variable: +```go + var retryPolicy = `{ "methodConfig": [{ - // config per method or all methods, this is for all methods under grpc.example.echo.Echo - // service + // config per method or all methods under service "name": [{"service": "grpc.examples.echo.Echo"}], "waitForReady": true, @@ -35,120 +55,29 @@ go run client/main.go "RetryableStatusCodes": [ "UNAVAILABLE" ] } }] - } + }` +``` -# Know all retry policies Integration with Service Config -From [grpc/proposal](https://github.com/grpc/proposal/blob/master/A6-client-retries.md#integration-with-service-config) +### Use retry policy as DialOption -The retry policy is transmitted to the client through the service config mechanism. The following is what the JSON configuration file would look like: +now you can use `grpc.WithDefaultCallOptions` as `grpc.DialOption` in `grpc.Dial` +to set all clientConnection retry policy - { - "loadBalancingPolicy": string, +```go +conn, err := grpc.Dial(ctx,grpc.WithInsecure(),grpc.WithDefaultCallOptions(retryPolicy)) +``` - "methodConfig": [ - { - "name": [ - { - "service": string, - "method": string, - } - ], - - // Only one of retryPolicy or hedgingPolicy may be set. If neither is set, - // RPCs will not be retried or hedged. - - "retryPolicy": { - // The maximum number of RPC attempts, including the original RPC. - // - // This field is required and must be two or greater. - "maxAttempts": number, - - // Exponential backoff parameters. The initial retry attempt will occur at - // random(0, initialBackoff). In general, the nth attempt since the last - // server pushback response (if any), will occur at random(0, - // min(initialBackoff*backoffMultiplier**(n-1), maxBackoff)). - // The following two fields take their form from: - // https://developers.google.com/protocol-buffers/docs/proto3#json - // They are representations of the proto3 Duration type. Note that the - // numeric portion of the string must be a valid JSON number. - // They both must be greater than zero. - "initialBackoff": string, // Required. Long decimal with "s" appended - "maxBackoff": string, // Required. Long decimal with "s" appended - "backoffMultiplier": number // Required. Must be greater than zero. - - // The set of status codes which may be retried. - // - // Status codes are specified in the integer form or the case-insensitive - // string form (eg. [14], ["UNAVAILABLE"] or ["unavailable"]) - // - // This field is required and must be non-empty. - "retryableStatusCodes": [] - } - - "hedgingPolicy": { - // The hedging policy will send up to maxAttempts RPCs. - // This number represents the all RPC attempts, including the - // original and all the hedged RPCs. - // - // This field is required and must be two or greater. - "maxAttempts": number, - - // The original RPC will be sent immediately, but the maxAttempts-1 - // subsequent hedged RPCs will be sent at intervals of every hedgingDelay. - // Set this to "0s", or leave unset, to immediately send all maxAttempts RPCs. - // hedgingDelay takes its form from: - // https://developers.google.com/protocol-buffers/docs/proto3#json - // It is a representation of the proto3 Duration type. Note that the - // numeric portion of the string must be a valid JSON number. - "hedgingDelay": string, - - // The set of status codes which indicate other hedged RPCs may still - // succeed. If a non-fatal status code is returned by the server, hedged - // RPCs will continue. Otherwise, outstanding requests will be canceled and - // the error returned to the client application layer. - // - // Status codes are specified in the integer form or the case-insensitive - // string form (eg. [14], ["UNAVAILABLE"] or ["unavailable"]) - // - // This field is optional. - "nonFatalStatusCodes": [] - } - - "waitForReady": bool, - "timeout": string, - "maxRequestMessageBytes": number, - "maxResponseMessageBytes": number - } - ] - - // If a RetryThrottlingPolicy is provided, gRPC will automatically throttle - // retry attempts and hedged RPCs when the client’s ratio of failures to - // successes exceeds a threshold. - // - // For each server name, the gRPC client will maintain a token_count which is - // initially set to maxTokens, and can take values between 0 and maxTokens. - // - // Every outgoing RPC (regardless of service or method invoked) will change - // token_count as follows: - // - // - Every failed RPC will decrement the token_count by 1. - // - Every successful RPC will increment the token_count by tokenRatio. - // - // If token_count is less than or equal to maxTokens / 2, then RPCs will not - // be retried and hedged RPCs will not be sent. - "retryThrottling": { - // The number of tokens starts at maxTokens. The token_count will always be - // between 0 and maxTokens. - // - // This field is required and must be in the range (0, 1000]. Up to 3 - // decimal places are supported - "maxTokens": number, - - // The amount of tokens to add on each successful RPC. Typically this will - // be some number between 0 and 1, e.g., 0.1. - // - // This field is required and must be greater than zero. Up to 3 decimal - // places are supported. - "tokenRatio": number - } - } \ No newline at end of file +### Call + +then your gRPC would retry transparent +```go +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.Println(err) + } +``` \ No newline at end of file diff --git a/examples/features/retry/client/main.go b/examples/features/retry/client/main.go index a010bfa4287..458eca6f289 100644 --- a/examples/features/retry/client/main.go +++ b/examples/features/retry/client/main.go @@ -1,6 +1,6 @@ /* * - * Copyright 2018 gRPC authors. + * 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. @@ -71,9 +71,13 @@ func main() { }() c := pb.NewEchoClient(conn) - reply, err := c.UnaryEcho(newCtx(1*time.Second), &pb.EchoRequest{Message: "Try and Success"}) + + 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.Println(err) + log.Fatalf("UnaryEcho error: %v", err) } - log.Println(reply) + log.Printf("UnaryEcho reply: %v", reply) } diff --git a/examples/features/retry/server/main.go b/examples/features/retry/server/main.go index 8dc8e1a00dc..a4450e5b52a 100644 --- a/examples/features/retry/server/main.go +++ b/examples/features/retry/server/main.go @@ -1,6 +1,6 @@ /* * - * Copyright 2018 gRPC authors. + * 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. @@ -26,7 +26,6 @@ import ( "log" "net" "sync" - "time" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -41,10 +40,10 @@ type failingServer struct { reqCounter uint reqModulo uint - reqSleep time.Duration - reqError codes.Code } +// 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() @@ -52,8 +51,8 @@ func (s *failingServer) maybeFailRequest() error { if (s.reqModulo > 0) && (s.reqCounter%s.reqModulo == 0) { return nil } - time.Sleep(s.reqSleep) - return status.Errorf(s.reqError, "maybeFailRequest: failing it") + + return status.Errorf(codes.Unavailable, "maybeFailRequest: failing it") } func (s *failingServer) UnaryEcho(ctx context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) { @@ -90,12 +89,11 @@ func main() { s := grpc.NewServer() - // a retry configuration + // Configure server to pass every fourth RPC; + // client is configured to make four attempts. failingservice := &failingServer{ reqCounter: 0, reqModulo: 4, - reqError: codes.Unavailable, /* uint = 14 */ - reqSleep: 0, } pb.RegisterEchoServer(s, failingservice) From 69820e48905d8d51ba4a0fb5f4c3c82f7b4ffadb Mon Sep 17 00:00:00 2001 From: abser Date: Fri, 27 Sep 2019 10:01:28 +0800 Subject: [PATCH 11/15] hard-code reqError to keep the example as simple as possible --- examples/features/retry/server/main.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/examples/features/retry/server/main.go b/examples/features/retry/server/main.go index a4450e5b52a..ca6bb9c835a 100644 --- a/examples/features/retry/server/main.go +++ b/examples/features/retry/server/main.go @@ -33,13 +33,17 @@ import ( "google.golang.org/grpc/status" ) -var port = flag.Int("port", 50052, "port number") +var ( + port = flag.Int("port", 50052, "port number") + errUnavailable = codes.Unavailable +) type failingServer struct { mu sync.Mutex reqCounter uint reqModulo uint + reqErr codes.Code } // this method will fail reqModulo - 1 times RPCs and return status code Unavailable, @@ -52,7 +56,7 @@ func (s *failingServer) maybeFailRequest() error { return nil } - return status.Errorf(codes.Unavailable, "maybeFailRequest: failing it") + return status.Errorf(s.reqErr, "maybeFailRequest: failing it") } func (s *failingServer) UnaryEcho(ctx context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) { @@ -94,6 +98,7 @@ func main() { failingservice := &failingServer{ reqCounter: 0, reqModulo: 4, + reqErr: errUnavailable, } pb.RegisterEchoServer(s, failingservice) From 3b676a53d48ce7658736396b558c5e0895c08e79 Mon Sep 17 00:00:00 2001 From: abser Date: Fri, 27 Sep 2019 10:20:40 +0800 Subject: [PATCH 12/15] fix CI/CD --- examples/features/retry/client/main.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/examples/features/retry/client/main.go b/examples/features/retry/client/main.go index 458eca6f289..73147cfe0a2 100644 --- a/examples/features/retry/client/main.go +++ b/examples/features/retry/client/main.go @@ -51,11 +51,6 @@ func retryDial() (*grpc.ClientConn, error) { return grpc.Dial(*addr, grpc.WithInsecure(), grpc.WithDefaultServiceConfig(retryPolicy)) } -func newCtx(timeout time.Duration) context.Context { - ctx, _ := context.WithTimeout(context.TODO(), timeout) - return ctx -} - func main() { flag.Parse() From 5c10be121dc571c6e89812cf24d9e715c654f706 Mon Sep 17 00:00:00 2001 From: abser Date: Fri, 27 Sep 2019 12:12:11 +0800 Subject: [PATCH 13/15] don't know why CI/CD fail --- examples/features/retry/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/features/retry/README.md b/examples/features/retry/README.md index 06ee4975a7f..2c27fe940b6 100644 --- a/examples/features/retry/README.md +++ b/examples/features/retry/README.md @@ -2,7 +2,7 @@ This example shows how to enable and configure retry on gRPC client. -# Read +# Document [Document about this example](https://github.com/grpc/proposal/blob/master/A6-client-retries.md#retry-policy) From 44abbb2d933e05c48f3a6bb2bf0bc03cc94e9658 Mon Sep 17 00:00:00 2001 From: Doug Fawley Date: Fri, 27 Sep 2019 10:50:36 -0700 Subject: [PATCH 14/15] Update README.md --- examples/features/retry/README.md | 61 +++++++++++-------------------- 1 file changed, 22 insertions(+), 39 deletions(-) diff --git a/examples/features/retry/README.md b/examples/features/retry/README.md index 2c27fe940b6..f56d438adc2 100644 --- a/examples/features/retry/README.md +++ b/examples/features/retry/README.md @@ -1,44 +1,42 @@ # Retry -This example shows how to enable and configure retry on gRPC client. +This example shows how to enable and configure retry on gRPC clients. -# Document +## Documentation -[Document about this example](https://github.com/grpc/proposal/blob/master/A6-client-retries.md#retry-policy) +[gRFC for client-side retry support](https://github.com/grpc/proposal/blob/master/A6-client-retries.md) +## Try it -# 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. -we had set server service that will always return status code Unavailable until the every fourth Request.To demonstrate retry. - -that means client should retry 4 times and 3 times received Unavailable status. +First start the server: ```bash go run server/main.go ``` -set environment otherwise retry will not work. -```bash -GRPC_GO_RETRY=on -``` +Then run the client. Note that when running the client, `GRPC_GO_RETRY=on` must be set in +your environment: -we had set retry policy to client to enable per RPC retry at most 4 times when response status is Unavailable. ```bash -go run client/main.go +GRPC_GO_RETRY=on go run client/main.go ``` -# Usage +## Usage ### Define your retry policy -In below, we set retry policy for "grpc.example.echo.Echo" methods. +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. -and retry policy : -MaxAttempts: how many times to retry until one of them succeeded. -InitialBackoff and MaxBackoff and BackoffMultiplier: time settings for delaying retries. -RetryableStatusCodes: Retry when received following response codes. +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. -set this as a variable: ```go var retryPolicy = `{ "methodConfig": [{ @@ -58,26 +56,11 @@ set this as a variable: }` ``` -### Use retry policy as DialOption +### Providing the retry policy as a DialOption -now you can use `grpc.WithDefaultCallOptions` as `grpc.DialOption` in `grpc.Dial` -to set all clientConnection retry policy +To use the above service config, pass it with `grpc.WithDefaultServiceConfig` to +`grpc.Dial`. ```go -conn, err := grpc.Dial(ctx,grpc.WithInsecure(),grpc.WithDefaultCallOptions(retryPolicy)) +conn, err := grpc.Dial(ctx,grpc.WithInsecure(), grpc.WithDefaultServiceConfig(retryPolicy)) ``` - -### Call - -then your gRPC would retry transparent -```go -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.Println(err) - } -``` \ No newline at end of file From 69ee36af1eb22a4c46a40ffd4f287412252e0deb Mon Sep 17 00:00:00 2001 From: abser Date: Sat, 28 Sep 2019 02:37:08 +0800 Subject: [PATCH 15/15] hash-code response error status codes.Unavailable --- examples/features/retry/server/main.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/examples/features/retry/server/main.go b/examples/features/retry/server/main.go index ca6bb9c835a..a4450e5b52a 100644 --- a/examples/features/retry/server/main.go +++ b/examples/features/retry/server/main.go @@ -33,17 +33,13 @@ import ( "google.golang.org/grpc/status" ) -var ( - port = flag.Int("port", 50052, "port number") - errUnavailable = codes.Unavailable -) +var port = flag.Int("port", 50052, "port number") type failingServer struct { mu sync.Mutex reqCounter uint reqModulo uint - reqErr codes.Code } // this method will fail reqModulo - 1 times RPCs and return status code Unavailable, @@ -56,7 +52,7 @@ func (s *failingServer) maybeFailRequest() error { return nil } - return status.Errorf(s.reqErr, "maybeFailRequest: failing it") + return status.Errorf(codes.Unavailable, "maybeFailRequest: failing it") } func (s *failingServer) UnaryEcho(ctx context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) { @@ -98,7 +94,6 @@ func main() { failingservice := &failingServer{ reqCounter: 0, reqModulo: 4, - reqErr: errUnavailable, } pb.RegisterEchoServer(s, failingservice)