重试不是加个for循环就能解决网络抖动问题——它必须和错误类型判断、上下文隔离、退避策略、幂等性对齐,否则只会把503变成雪崩;grpc客户端重试必须显式配置retrypolicy,http客户端应自定义transport而非滥用retryablehttp,且所有重试均须确保请求幂等、错误可重试、上下文可控。

重试不是加个 for 循环就能解决网络抖动问题——它必须和错误类型判断、上下文隔离、退避策略、幂等性对齐,否则只会把 503 变成雪崩。
gRPC 客户端重试必须显式配置 retryPolicy
gRPC Go 默认完全关闭重试,即使服务端返回 codes.Unavailable,客户端也不会自动重试。常见错误是以为设置了 grpc.WithTimeout 就自带重试,其实两者毫无关系。
- 必须用
grpc.WithDefaultServiceConfig注入 JSON 格式的重试策略,不能用WithServiceConfig - 策略里
"RetryableStatusCodes"只支持["UNAVAILABLE","DEADLINE_EXCEEDED"]等有限枚举,FAILED_PRECONDITION默认不可重试 - proto 中对应方法必须显式标注
retryable: true,否则策略不生效 - 重试会重新序列化请求体,
proto.Message里不能含sync.Mutex等不可序列化字段
HTTP 客户端重试别碰 retryablehttp 库
retryablehttp 是 HTTP/1.1 专用库,硬套在 gRPC 上会 panic;用在普通 HTTP 调用时也容易误伤健康检查或 4xx 请求。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 正确做法是自定义
http.Transport的RoundTrip方法,只拦截*url.Error和特定状态码 - 只对
err.Timeout()、strings.Contains(err.Error(), "connection refused")或resp.StatusCode >= 500重试 - 避免全局复用
http.DefaultClient并统一加重试逻辑,否则连/healthz都会被重试三次 - 每次重试前必须重建
*http.Request,原 request.Body 已被读取,直接重放会得到空 body
isRetryable 函数必须区分临时错误和语义错误
盲目重试 codes.InvalidArgument 或 400 Bad Request 不仅无效,还会放大下游压力。关键不是“要不要重试”,而是“这个错误是否可能下一秒就好”。
- 可重试:
net.OpError(含Timeout())、context.DeadlineExceeded(注意:通常不重试,但需结合场景判断)、codes.Unavailable、codes.ResourceExhausted - 不可重试:
codes.NotFound、codes.InvalidArgument、codes.AlreadyExists、任何带Temporary()返回 false 的 error - 建议封装
isRetryable函数,用errors.As和errors.Is判断,拒绝字符串匹配 - 对自定义错误,确保实现了
Temporary() bool方法,并在业务层统一返回包装后的 error
backoff.Retry 比手写 for+sleep 更安全
自己写重试循环极易漏掉上下文取消、总耗时控制、抖动(jitter)和错误分类,backoff/v4 能自动处理这些。
- 用
backoff.NewExponentialBackOff()配置,重点设MaxElapsedTime(如10 * time.Second),而非只设最大次数 - 每次调用
bo.NextBackOff()获取延迟,不能复用初始值,否则退避失效 - 重试函数内必须用新
ctx, cancel := context.WithTimeout(parentCtx, timeout),否则原 ctx deadline 会传染 - 若主逻辑失败后要 fallback,fallback 必须用独立的短超时 context 启动,且需校验返回数据有效性(如 Redis ttl > 0)
最常被忽略的是:重试本身不解决重复提交问题。哪怕退避再优雅、熔断再及时,只要服务端没做幂等控制(比如靠 idempotency-key 去重),一次重试就等于一次新请求——转账扣两次款、订单创建两遍,都是真实发生过的线上事故。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










