sync.WaitGroup可实现多数据源并发拉取,避免串行累加耗时;需配合context.WithTimeout为各请求单独设超时、结构体收结果、区分临时错误与业务错误,确保稳定高效。

用 sync.WaitGroup 并发拉取多个数据源,避免串行阻塞
单个请求串行查 MySQL、Redis、HTTP API,耗时是累加的;并发发起才能压低整体延迟。但直接开 goroutine 不控制生命周期,容易漏掉错误或 panic 导致主流程卡死。sync.WaitGroup 是最轻量可控的方式——它不负责传值,只管“等齐所有任务”,配合 channel 或结构体字段收结果更稳妥。
- 每个数据源查询封装成独立函数,接受
*sync.WaitGroup并在末尾调用wg.Done() - 主流程先
wg.Add(n),再启动 goroutine,最后wg.Wait()阻塞等待全部完成 - 不要在 goroutine 里 recover —— 错误应由调用方统一处理,否则会掩盖超时、连接失败等关键信号
用 context.WithTimeout 统一控制各数据源超时,防止一个拖垮全部
不同数据源响应时间差异大:Redis 通常几毫秒,外部 HTTP 接口可能几百毫秒。如果只给整体设 timeout,某个慢接口占满时间,其他快的也得陪等。正确做法是对每个子请求单独套 context.WithTimeout,且 timeout 值按实际 SLA 设置(比如 Redis 设 50ms,第三方 API 设 800ms)。
- 别复用同一个
context.Context实例传给所有子请求——子 cancel 会互相干扰 - HTTP 客户端必须显式传入 context:
http.DefaultClient.Do(req.WithContext(ctx)) - database/sql 的
QueryContext、ExecContext同理,老式Query不受 context 控制
聚合结果时优先用结构体字段赋值,少用 map[string]interface{}
map 方式看似灵活,但实际带来三类问题:类型断言频繁易 panic、IDE 无法跳转字段、JSON 序列化时字段名大小写难统一。真实业务中,聚合字段是确定的(如用户基础信息 + 订单数 + 最近登录 IP),直接定义结构体更安全。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 为每个数据源定义返回结构体(如
type UserDBResp struct { ID int; Name string }),聚合层只做字段映射 - 若需动态字段(如配置化扩展字段),用
map[string]json.RawMessage而非interface{},保留原始 JSON 字节流,延后解析 - 注意空值处理:数据库 NULL 对应 Go 的
sql.NullString等类型,直接扫进 struct 会报错,要用Scan或sqlx.StructScan
错误处理必须区分「可重试」和「不可重试」,别把网络抖动当业务失败
聚合查询失败不等于业务失败。MySQL 连接超时、Redis 拒绝连接、HTTP 503 等属于临时故障,应标记为 partial failure 并继续返回其余数据;而 JSON 解析失败、字段缺失、业务逻辑校验不通过才是真错误,需要中断并返回 error。
- 用自定义错误类型区分:
var ErrTemporary = errors.New("temporary failure"),上层根据 error.Is 判断是否降级 - 不要用
errors.Wrap包裹底层错误——会丢失原始 error 类型判断能力 - 日志里记录每个数据源的耗时和错误码(如
"redis: dial timeout after 120ms"),方便定位瓶颈点
真正麻烦的是字段语义冲突:比如两个数据源都叫 status,但一个是数字码,一个是字符串描述。这种没法靠工具自动解决,得在聚合层手动映射,而且要写清楚注释——下次加第三个数据源时,这里就是第一个爆雷点。

















