重试过滤器必须显式传递错误,不能静默吞掉;HTTP客户端重试应封装RoundTripper而非包装Do();gRPC重试需满足拦截器、错误码、请求结构体三条件;backoff.Retry必须配合带timeout的context;错误判断须精确区分可重试与不可重试类型。

重试过滤器必须显式传递错误,不能静默吞掉
Go 没有 try-catch,所有错误都靠返回值传递。如果在中间件或过滤器里 recover() 了 panic 却不 re-panic 或返回 error,后续链路就收不到原始错误——日志断档、监控漏报、重试逻辑失效全由此起。
- 正确做法是 defer 中 recover 后立刻
panic(err),确保异常继续向上冒泡 - 对非 panic 类错误(如
net.OpError、http.Client.Do返回的 error),不要用log.Printf打完就丢,得原样 return 或 wrap 后 return - 别在过滤器里直接调
http.Error或写响应体——这等于提前终止链路,后续重试逻辑根本没机会触发
HTTP 客户端重试必须封装 RoundTripper,而非包装 Do()
业务层写 for 循环套 http.Client.Do() 看似简单,但会重复造轮子:body 重放要手动 Seek,超时要每轮重设,错误分类逻辑散落在各处,改个退避策略就得翻七八个文件。
- 真正可复用的做法是实现
http.RoundTripper,把重试逻辑压进RoundTrip()方法里 - 只对特定路径启用重试,比如
POST /api/transfer需重试,GET /health必须绕过——这只能靠 RoundTripper 内部判断req.URL.Path实现 - 切忌直接改
http.DefaultClient.Transport:健康检查、指标上报等请求也会被拖进重试风暴,放大下游压力
gRPC 客户端重试配置缺一不可
配了 grpc_retry.WithCodes(codes.Unavailable) 却不生效?大概率卡在三个硬性条件上。
- 拦截器必须用
grpc.WithChainUnaryInterceptor(grpc_retry.UnaryClientInterceptor()),不是WithUnaryInterceptor -
grpc_retry.WithCodes()里没包含实际发生的错误码(比如服务返回codes.ResourceExhausted,但配置里只写了codes.Unavailable) - 请求结构体含
sync.Mutex或unsafe.Pointer——重试时序列化失败直接 panic,连错误都看不到
backoff.Retry 必须配合 context,否则可能卡死
backoff.Retry 看起来省事,但漏传带 timeout 的 context.Context 就等于埋雷:最后一次重试若阻塞,整个 goroutine 就永远 hang 住。
立即学习“go语言免费学习笔记(深入)”;
- 永远用
backoff.WithContext(ctx)替代裸调backoff.Retry - 错误判断必须精确:
errors.Is(err, context.DeadlineExceeded)和errors.Is(err, os.ErrNotExist)处理方式天差地别——前者可重试,后者重试一百次也是错 - 别信“默认重试 3 次”这种模糊说法:
backoff.WithMaxRetries才控制次数,backoff.WithMaxInterval只限制间隔上限,不阻止重试本身


















