gRPC 微服务不可用 retryablehttp,因其仅支持 HTTP/1.1;应使用 gRPC 原生拦截器实现带 jitter 的指数退避重试,并配合熔断器与服务端幂等校验。

Go 的 retryablehttp 客户端不适用于 gRPC 微服务
gRPC 调用走的是 HTTP/2 协议栈,而 retryablehttp 是为 RESTful HTTP/1.1 设计的,底层依赖 net/http.Client,无法处理 gRPC 的流控、状态码映射(如 codes.Unavailable)、元数据透传等。直接套用会导致重试逻辑失效或 panic。
正确做法是使用 gRPC 原生的 grpc.WithUnaryInterceptor 或 grpc.WithStreamInterceptor 配合重试拦截器。官方推荐方案是基于 google.golang.org/grpc/codes 和 google.golang.org/grpc/status 判断是否可重试:
-
codes.Unavailable、codes.ResourceExhausted、codes.Aborted通常可重试 -
codes.InvalidArgument、codes.NotFound、codes.AlreadyExists绝对不可重试(语义错误) - 需排除
codes.DeadlineExceeded—— 它本身已是超时结果,再重试会加剧雪崩
自定义重试拦截器必须控制最大重试次数和退避时间
无限制重试等于拒绝服务攻击。gRPC 默认不提供重试策略,必须手动实现拦截器并注入退避逻辑。常见错误是只做固定间隔重试(如每次 sleep 100ms),这在高并发下会引发请求风暴。
推荐使用带 jitter 的指数退避(exponential backoff with jitter)。示例中使用 github.com/cenkalti/backoff/v4 库,配置如下:
立即学习“go语言免费学习笔记(深入)”;
bo := backoff.NewExponentialBackOff() bo.InitialInterval = 100 * time.Millisecond bo.MaxInterval = 2 * time.Second bo.MaxElapsedTime = 10 * time.Second // 总耗时上限 bo.Multiplier = 2.0 bo.RandomizationFactor = 0.5 // 加入抖动,避免同步重试
关键点:
-
MaxElapsedTime比单纯设MaxRetries更可靠 —— 网络延迟波动大,固定次数易超时或不足 - 每次重试前必须调用
bo.NextBackOff()获取当前等待时长,不能复用初始值 - 拦截器内要 clone
ctx并设置新 deadline,否则原 ctx 的 deadline 会传染到重试请求
重试时必须跳过幂等性被破坏的操作
不是所有 RPC 都能重试。例如 CreateOrder 接口若重试两次,可能生成两个订单;PayOrder 若重试,可能扣两次款。这类操作必须由业务层标记为「不可重试」,或通过 idempotency key + 服务端幂等校验兜底。
该系统采用先进的HTML5+CSS3结构,既有手机APP的良好体验,又有智能建站系统的操作方便。在中国,企业网站建设在已有20年,但表现方式基本是一成不变,此产品进行了与众不同的偿试。一切以小微企业实际情况出发,注重核心产品的塑造以及企业文化展示。让小微企业及个人都能找准自身的细分化定位,服务好客户。
建议在客户端统一加注解式控制(非语言原生,可用结构体字段或 context.Value):
- 给每个 RPC 方法定义重试策略常量,如
RetryPolicyNone/RetryPolicyIdempotent - 拦截器读取该策略,仅对
RetryPolicyIdempotent类型方法启用重试 - 服务端收到含
idempotency-keyheader 的请求,先查缓存/DB 是否已执行,避免重复落库
没有服务端配合的客户端重试,本质是空中楼阁。
熔断器应独立于重试逻辑部署
重试解决的是临时性故障(如网络抖动),熔断解决的是持续性故障(如下游服务宕机)。两者必须解耦,否则重试失败会快速触发熔断,导致本可恢复的节点被误判隔离。
推荐组合方案:github.com/sony/gobreaker + 独立指标采集:
- 熔断器只监听
codes.Unavailable和codes.DeadlineExceeded的失败率(不包含重试中间态) - 重试拦截器不感知熔断状态,但应在重试前检查
cb.Ready() == true,跳过已打开的熔断器 - 熔断窗口期(如 60 秒)内失败率 > 50% 且失败数 ≥ 5 次才触发 OPEN 状态
最容易被忽略的是:熔断器的状态变更需要跨 goroutine 同步 —— 如果多个 RPC 共享同一个 gobreaker.CircuitBreaker 实例,必须确保其内部状态更新是线程安全的(该库已实现,但自研时极易出错)。









