Go微服务容错必须显式设置context.WithTimeout,所有I/O调用需绑定带超时的context;重试仅针对临时性错误且≤3次;熔断应基于失败率而非单纯计数;降级须零依赖、同步返回确定值。

context.WithTimeout 是 Go 微服务容错的第一道防线,不是可选项,而是必须显式设置的底线。没有超时控制的远程调用,等于在生产环境埋定时炸弹。
超时控制必须绑定到每次外部调用
HTTP、gRPC、数据库查询、Redis 操作——只要涉及 I/O,就必须携带 context.Context,且必须由 context.WithTimeout 或 context.WithDeadline 构造。不传 context 或传 context.Background() 直接调用客户端,等同于放弃控制权。
- 错误写法:
http.DefaultClient.Do(req)(无 context) - 正确写法:
http.DefaultClient.Do(req.WithContext(ctx)),其中ctx来自context.WithTimeout(context.Background(), 3*time.Second) - gRPC 客户端必须用
grpc.DialContext,而非grpc.Dial - 数据库操作如
db.QueryRowContext、redis.Client.GetContext等,均有带 Context 的变体,优先使用
重试不能无条件叠加,必须区分错误类型
对 4xx 错误(如 400 Bad Request、404 Not Found)重试毫无意义;而对连接拒绝、超时、503 Service Unavailable 这类临时性失败,才值得重试。
- 只重试可恢复错误:检查
err是否为net.OpError、context.DeadlineExceeded、rpc.Error中的Code() == codes.Unavailable等 - 最大重试次数建议 ≤ 3,配合指数退避 + 抖动(jitter),例如:100ms → 250ms → 600ms(非固定倍数)
- 避免在 HTTP handler 内部做重试——应下沉到 client 层或封装为带重试逻辑的
DoWithRetry函数 - 重试日志需标记重试序号(
attempt=1),否则无法区分是首次失败还是重试失败
熔断器状态切换依赖真实失败率,不是简单计数
sony/gobreaker 默认用「连续失败次数」触发熔断,但生产环境更需关注「失败率」和「时间窗口」,否则低频调用下极易误熔断。
- 配置示例:
gobreaker.Settings{ReadyToTrip: func(counts gobreaker.Counts) bool { return counts.Total >= 10 && float64(counts.ConsecutiveFailures)/float64(counts.Total) > 0.6 }} - 必须设置
OnStateChange回调,将状态变更(Closed→Open等)上报 Prometheus 或打点日志 - 半开(
HalfOpen)状态下只允许单次探测请求,成功后才恢复,失败则重置计时器——这点常被手动实现者忽略 - 不要为每个下游接口创建独立熔断器再全局管理;推荐按服务粒度(如
"user-service")注册,复用同一实例
降级逻辑必须零依赖、同步执行、有明确兜底值
降级不是“再试一次”,而是立刻返回一个安全、确定、可预测的结果。一旦进入降级路径,不能再调用其他远程服务、不能查数据库、不能加锁。
立即学习“go语言免费学习笔记(深入)”;
- 典型降级值:
return cachedRecommendations, nil(本地内存缓存)、return []Item{}, nil(空列表)、return defaultConfig, nil(硬编码默认配置) - 禁止在降级函数里调用
http.Get、redis.Get或任何可能 panic 的操作 - 降级响应需与正常路径保持相同结构和类型,避免上层
json.Unmarshal失败 - 如果降级本身也失败(比如缓存未初始化),应 fallback 到最简值(如空对象),而不是 panic 或返回 error


















