How to Configure Retry Logic and Timeouts for gRPC Client Calls in Kratos Services
To configure retry logic and timeouts for gRPC client calls in Kratos services, use grpc.WithTimeout() for per-request deadlines and either gRPC's native service-config retry policies via grpc.WithOptions() or custom unary interceptors via grpc.WithUnaryInterceptor().
The Kratos framework builds its gRPC client on top of the standard google.golang.org/grpc library, wrapping grpc.Dial with helper utilities located in the transport/grpc package. When you need to configure retry logic and timeouts for gRPC client calls, Kratos provides explicit options in transport/grpc/client.go while allowing direct access to underlying gRPC dial options for advanced scenarios.
Configuring Request Timeouts in Kratos gRPC Clients
Kratos provides the WithTimeout(d time.Duration) option to enforce per-request deadlines. This option is defined in transport/grpc/client.go (lines 51–56) and is applied inside the unaryClientInterceptor function found in transport/grpc/interceptor.go (lines 25–30).
When a call exceeds the specified duration, the interceptor cancels the context with context.DeadlineExceeded. Note that this timeout applies only to the RPC call, not to the underlying connection establishment.
import (
"context"
"time"
"github.com/go-kratos/kratos/v2/transport/grpc"
greeterpb "path/to/your/api/helloworld"
)
func NewGreeterClient() greeterpb.GreeterClient {
// 5 seconds deadline for every RPC
conn, _ := grpc.Dial(
context.Background(),
grpc.WithEndpoint("localhost:9000"),
grpc.WithTimeout(5*time.Second),
)
return greeterpb.NewGreeterClient(conn)
}
The timeout is injected inside the unary interceptor via ctx = context.WithTimeout(ctx, timeout), ensuring that any blocking RPC operation respects the deadline.
Implementing Retry Logic for gRPC Calls
Kratos does not ship with a built-in retry interceptor for the client side. You have two idiomatic approaches to add retry capabilities: using gRPC's native service configuration or writing a custom unary interceptor.
Option 1: Native gRPC Retry via Service Config
You can enable automatic retries by passing a JSON service configuration through grpc.WithDefaultServiceConfig, which Kratos forwards to the underlying dialer via the WithOptions wrapper (defined in transport/grpc/client.go, lines 100–104).
import (
"context"
"github.com/go-kratos/kratos/v2/transport/grpc"
"google.golang.org/grpc"
greeterpb "path/to/your/api/helloworld"
)
func NewGreeterClientWithRetry() greeterpb.GreeterClient {
// JSON service config enabling 3 retries with exponential back-off
const svcConfig = `{
"methodConfig": [{
"name": [{"service": "helloworld.Greeter"}],
"retryPolicy": {
"MaxAttempts": 4,
"InitialBackoff": "0.1s",
"MaxBackoff": "1s",
"BackoffMultiplier": 2,
"RetryableStatusCodes": ["UNAVAILABLE","RESOURCE_EXHAUSTED"]
}
}]
}`
conn, _ := grpc.Dial(
context.Background(),
grpc.WithEndpoint("localhost:9000"),
grpc.WithOptions(
grpc.WithDefaultServiceConfig(svcConfig),
),
)
return greeterpb.NewGreeterClient(conn)
}
This approach leverages the gRPC core's built-in retry mechanism, which interprets the policy when the server returns a retryable status code.
Option 2: Custom Unary Interceptor
For custom logic, implement a grpc.UnaryClientInterceptor and register it using WithUnaryInterceptor (handled in transport/grpc/client.go, lines 86–90). This interceptor runs before the built-in timeout interceptor, so the deadline still caps the total elapsed time across all retry attempts.
import (
"context"
"time"
"google.golang.org/grpc"
"github.com/go-kratos/kratos/v2/transport/grpc"
greeterpb "path/to/your/api/helloworld"
)
// Simple retry interceptor: up to 3 attempts, 200 ms pause
func retryUnaryInterceptor(maxAttempts int, backoff time.Duration) grpc.UnaryClientInterceptor {
return func(
ctx context.Context,
method string,
req, reply interface{},
cc *grpc.ClientConn,
invoker grpc.UnaryInvoker,
opts ...grpc.CallOption,
) error {
var err error
for i := 0; i < maxAttempts; i++ {
err = invoker(ctx, method, req, reply, cc, opts...)
if err == nil {
return nil
}
// Adapt to your own retryable error list
time.Sleep(backoff)
}
return err
}
}
func NewClientWithRetry() greeterpb.GreeterClient {
conn, _ := grpc.Dial(
context.Background(),
grpc.WithEndpoint("localhost:9000"),
grpc.WithUnaryInterceptor(
retryUnaryInterceptor(3, 200*time.Millisecond),
),
grpc.WithTimeout(5*time.Second),
)
return greeterpb.NewGreeterClient(conn)
}
Combining Timeouts and Retries
You can safely combine per-call timeouts with retry policies to ensure that the total time spent on all retry attempts does not exceed your service-level objectives. The timeout interceptor wraps the entire invocation chain, including any retry logic you inject.
conn, _ := grpc.Dial(
context.Background(),
grpc.WithEndpoint("localhost:9000"),
grpc.WithTimeout(3*time.Second), // Overall call deadline
grpc.WithOptions(
grpc.WithDefaultServiceConfig(svcConfig), // Native retry policy
),
)
In this configuration, the unaryClientInterceptor applies the deadline first, and the gRPC core handles the retry attempts within that bounded context.
Summary
WithTimeoutsets per-RPC deadlines via theunaryClientInterceptorintransport/grpc/interceptor.go.WithOptionspasses rawgrpc.DialOptionvalues (likeWithDefaultServiceConfig) directly to the underlyinggrpc.DialContext.WithUnaryInterceptorallows you to inject custom retry logic that executes before the timeout enforcement.- Native gRPC retry policies defined in service config JSON are supported through the
WithOptionswrapper. - Reference implementations and unit tests are available in
transport/grpc/client_test.go.
Frequently Asked Questions
How does Kratos apply the timeout to gRPC calls?
Kratos injects the timeout through the unaryClientInterceptor function in transport/grpc/interceptor.go. This interceptor wraps the RPC invocation with context.WithTimeout(ctx, timeout) before delegating to the invoker, ensuring that any call exceeding the duration is cancelled with context.DeadlineExceeded.
Can I use both native gRPC retry and a custom interceptor together?
Yes. You can pass grpc.WithDefaultServiceConfig via WithOptions for native retries while also supplying a custom interceptor via WithUnaryInterceptor. The custom interceptor executes first, followed by the timeout enforcement, while the native retry policy is handled by the gRPC core at the transport layer.
Does the timeout include connection establishment time?
No. The WithTimeout option applies only to the RPC call itself, not to the underlying connection establishment. For connection timeouts, use WithOptions to pass grpc.WithBlock() and grpc.WithTimeout() dial options directly to the gRPC dialer.
Where are the client options defined in the Kratos source code?
All client configuration options, including WithTimeout, WithUnaryInterceptor, and WithOptions, are defined in transport/grpc/client.go. The timeout enforcement logic resides in transport/grpc/interceptor.go, and usage examples are demonstrated in transport/grpc/client_test.go.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →