go标准库net/rpc不支持原生重试,需手动封装context超时、channel和指数退避;grpc则通过grpc_retry包提供可靠重试,须区分可重试错误、限制总耗时并保障服务端幂等性。

Go 语言标准库 net/rpc 本身不支持重试,直接套 for 循环调用 Call 是危险的——它既无法中断挂起的调用,又容易在服务端超时时引发连接堆积和 goroutine 泄漏。真正可用的重试必须绑定 context.Context 超时,并按错误类型精准过滤、配合指数退避。
net/rpc 必须手动封装超时 + channel 才能安全重试
net/rpc.Client.Call 签名不接收 context.Context,所以无法原生取消正在阻塞的调用。强行重试前不设超时,等于主动制造雪崩。
- 每次调用需启动独立 goroutine,把
client.Call结果写入donechannel - 用
select等待ctx.Done()或done,超时则丢弃 goroutine(靠defer关闭 channel 不够,但比无保护强) - 连接层也要设超时:
rpc.DialHTTP("tcp", "host:port")前先用net.DialTimeout控制建连时间 - 别用
time.Sleep硬等:第 1 次失败后等100ms,第 2 次等200ms,第 3 次等400ms,上限建议1s
gRPC 用 grpc_retry 包配置重试更可靠
gRPC 原生支持 context,且 google.golang.org/grpc/retry 提供声明式重试配置,比手写循环更少出错。
- 必须通过
grpc.WithDefaultCallOptions注入重试选项,不能只在Dial时配全局参数 -
grpc_retry.WithMax(3)表示最多重试 3 次(即总共最多 4 次请求),不是“最多发 3 次” -
grpc_retry.WithBackoff(grpc_retry.BackoffExponential(100*time.Millisecond))启用指数退避,但默认不含抖动,生产环境建议自己 wrap 一层加jitter - 错误判断函数要解包:
s, ok := status.FromError(err); ok && (s.Code() == codes.Unavailable || s.Code() == codes.DeadlineExceeded),不能依赖err.Error()字符串匹配
重试前必须区分可重试与不可重试错误
对 codes.InvalidArgument 或 rpc.ErrShutdown 重试毫无意义,只会放大问题。错误分类比重试逻辑本身更重要。
- 可重试:网络类错误(
codes.Unavailable、codes.DeadlineExceeded、codes.ResourceExhausted)、部分codes.Internal(需结合监控确认是否瞬时) - 不可重试:
codes.NotFound、codes.PermissionDenied、codes.AlreadyExists、codes.InvalidArgument,以及所有带明确业务语义的自定义错误码 -
net/rpc中检查err是否为*rpc.Error类型,再看.Code字段;不是所有err != nil都该重试 - 即使错误可重试,也要限制总耗时:外层
context.WithTimeout应覆盖整个重试周期,比如 3 秒内最多重试 3 次,而非每次 3 秒
最易被忽略的一点:重试和幂等性必须一起设计。没有服务端幂等保障的重试,等于在生产环境埋雷——比如支付接口重试两次,可能扣款两次。request_id 查重、状态机校验、数据库唯一约束,三者至少用一种。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











