聚合器模式必须用context.withtimeout,否则并发调用会因下游无响应而永久阻塞、堆积goroutine,导致资源泄漏和请求链卡死;go的http.client与grpc客户端默认无超时,任一未设超时的子调用都会使wg.wait()无法返回。

为什么聚合器模式在Go里必须用context.WithTimeout
不加超时的并发调用会卡死整个请求链,不是“可能出问题”,而是必然堆积goroutine。Go的http.Client默认无超时,gRPC客户端也一样——一旦下游服务响应慢或挂掉,上游goroutine就永远等下去。
聚合器里三个并行调用,哪怕只有一路没设超时,wg.Wait()就卡住,整个GetOrderDetail函数无法返回。这不是逻辑错误,是资源泄漏。
- 每个goroutine启动前必须派生带超时的子上下文:
ctx, cancel := context.WithTimeout(parentCtx, 3*time.Second) - cancel必须defer调用,否则超时后goroutine仍持有资源
- gRPC调用要传入该ctx:
a.orderClient.GetOrder(ctx, req),HTTP请求要用http.NewRequestWithContext(ctx, ...) - 别复用同一个
context.Background(),它永不超时也不可取消
代理模式下gin路由转发容易漏掉Header和Body
API网关用gin做反向代理时,常见写法是直接c.Request.URL.Path拼接后端地址再http.DefaultClient.Do(),但这样会丢掉原始请求的Authorization、Content-Type、Cookie等关键Header,也容易忽略Body读取和重放。
真正可用的转发逻辑要手动复制Header,并处理Body流式转发:
- 用
c.Request.Header.Clone()复制Header,再删掉Connection、Keep-Alive等代理敏感字段 - Body必须用
io.Copy流式转发,不能io.ReadAll(c.Request.Body)再重写,否则大文件会OOM - 响应Header也要逐个复制:
c.Writer.Header().Set(key, val),不能直接WriteHeader后不管 - 别用
httputil.NewSingleHostReverseProxy省事——它默认不透传Authorization,需额外配置Director函数
服务发现集成Consul时,健康检查路径必须真实可访问
consul注册时填的Check.HTTP地址,不是“随便写个/health就行”。如果服务没实现该endpoint,或者返回非2xx状态码,Consul会把服务标记为critical,后续所有请求都路由不到它——而日志里往往只报“no healthy instances”,根本看不出是健康检查失败。
- 确保
/health端点存在、返回200 OK、且不依赖其他服务(比如不能查数据库) - gRPC服务要额外暴露HTTP健康检查端口,或用
grpc-health-probe工具配合 - 注册代码里
Interval和Timeout要匹配:若Timeout设5s,Interval至少8s,否则检查频繁超时 - Kubernetes环境下优先用liveness/readiness探针,Consul仅作补充
过度拆分服务时,gRPC接口定义最容易失控
一个订单操作,拆成User、Order、Product、Inventory、Payment五个服务后,.proto文件里会出现大量重复的ID、Timestamp、Status定义,不同服务对同一概念的字段名、类型、校验规则不一致,最终导致前端反复适配、DTO膨胀、跨服务数据转换出错。
- 所有服务共用一套基础proto(如
common.proto),定义Id、TimeRange、StatusEnum等通用结构 - 禁止在各服务proto里重复定义用户ID——统一用
string user_id = 1;,而不是int64 uid或string userId - proto编译后生成的Go struct不要直接暴露给handler,用独立的
api/v1层DTO做转换,隔离内部模型变更 - 每次新增字段必须加
optional(proto3.12+)或预留reserved字段,避免版本不兼容











