RPC调用必须用context.WithTimeout控制超时,HTTP监控须用HistogramVec而非SummaryVec,QPS限流应在客户端用令牌桶实现,故障隔离需结合开关与降级策略。

rpc.Call 默认不超时,必须自己加 context 控制
Go 标准库 net/rpc 的 client.Call 完全没有超时参数,调用会一直阻塞,直到服务端返回或连接断开。这意味着后端卡死、网络抖动或服务崩溃时,调用方 goroutine 就挂住了,SLA 直接归零。
正确做法不是用 time.AfterFunc 启 goroutine cancel —— 竞态难控,且已发出的请求无法回收;而是用 context.WithTimeout 封装整个调用流程:
func CallWithContext(client *rpc.Client, serviceMethod string, args interface{}, reply interface{}, timeout time.Duration) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
<pre class='brush:php;toolbar:false;'>done := make(chan *rpc.Call, 1)
go func() {
client.Call(serviceMethod, args, reply)
done <- nil
}()
select {
case <-done:
return nil
case <-ctx.Done():
return ctx.Err()
}}
-
client.Go也不能替代 —— 它的Donechannel 同样无超时,仍需额外select - 所有原有
client.Call()可直接替换成该封装函数,业务逻辑零侵入 - 若已用 gRPC,则
context.WithTimeout是每次调用必传项,缺省即无限等待
HTTP 接口耗时监控必须用 HistogramVec,别用 SummaryVec
要准确算 P50/P95/P99 延时,必须用 prometheus.HistogramVec,而不是 SummaryVec。后者不支持 Prometheus 的 rate() 和跨实例聚合,P99 查出来永远不准,服务重启后历史趋势也断掉。
立即学习“go语言免费学习笔记(深入)”;
桶(Buckets)配置最关键:不能沿用默认的微秒级小桶,否则 99% 请求都落在 le="+Inf",分位数完全失真。推荐配置:
[]float64{0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10} // 单位:秒- 中间件里打点必须用
time.Since(start),不用time.Now().Sub()—— 后者受 NTP 调整影响可能回跳 - 真实处理起点是
next.ServeHTTP()开始后,不是中间件入口就time.Now() - 状态码得从包装过的
ResponseWriter获取,不能依赖WriteHeader()参数 —— 它可能被多次调用
QPS 限流必须在客户端做,不能只靠服务端熔断
当调用 Dify 或其他第三方 API 时,遇到 429 Too Many Requests 不代表你没限流,只说明限流没落在调用方。服务端熔断是兜底,但 SLA 损失已经发生。
用 golang.org/x/time/rate 在客户端做令牌桶是最轻量可靠的方案:
limiter := rate.NewLimiter(10, 5) // 每秒 10 次,突发 5 次
if !limiter.Allow() {
// 拒绝或排队,不发请求
}- 固定窗口计数器有临界问题;滑动窗口实现复杂;令牌桶兼顾平滑与突发,适合大多数业务
- 缓存重复请求结果(如相同 prompt 的 LLM 输出)比单纯限流更有效,但需校验语义等价性
- 异步队列(如 Kafka)能削峰,但引入延迟和运维成本,不适合低延迟 SLA 场景
开关可控和故障隔离不是可选项,是 SLA 底线
一个接口因下游 DB 慢查询拖垮整个服务,根本原因不是没加超时,而是没做故障隔离 —— 错误没被收束,反而扩散成雪崩。
开关(feature flag)和降级策略必须编码进主干逻辑:
- DB 查询超时后,立即 fallback 到缓存或默认值,而不是抛 panic 或重试三次
- 开关控制新功能灰度,避免上线即故障;也要控制老功能“一键关停”,比如某支付渠道异常时切到备用通道
- 所有外部依赖(Redis、MySQL、gRPC client)都应有独立超时、重试上限、熔断阈值,且彼此不共享连接池
最易被忽略的是:开关状态和熔断器指标必须暴露为 Prometheus 指标,否则你永远不知道它们是否真在起作用。


















