
为什么 select 配 chan 不适合长周期任务编排
因为 select 本身不记录状态、不重试、不超时传播,一旦某个 case 阻塞或失败,整个流程就卡死或静默丢弃。它适合短平快的协程通信,不是工作流引擎。
- 典型错误:用
select等待多个 HTTP 请求完成,但某请求因网络抖动超时,select无法自动 fallback 到备用通道 - 真实场景:支付回调要等「库存扣减」+「积分更新」+「消息推送」三个服务响应,任一环节失败需重试 + 记录日志 + 触发告警
- 参数差异:
select的每个case是平等竞争,没有优先级、依赖顺序或失败恢复语义 - 性能影响:硬套
select做编排会导致大量 goroutine 泄漏(比如超时未清理的监听协程)
用 github.com/ThreeDotsLabs/watermill 接 Kafka 时怎么避免消息重复消费
关键不是关掉 auto.commit,而是把「业务处理」和「位点提交」绑定在同一个事务里——但 Kafka 本身不支持跨 topic 事务,所以得靠 Watermill 的 Handler 生命周期钩子兜底。
- 常见错误:在
Handle函数里先处理业务逻辑,再调用message.Ack(),但中间 panic 或进程崩溃会导致消息丢失确认 - 正确做法:启用
middleware.Recoverer+middleware.Retry,并在Handler中用message.Metadata.Set("processed", "true")标记幂等性 - 兼容性注意:Watermill 默认使用
confluent-kafka-gov1.x,如果 Kafka 集群是 2.8+,必须升级到 Watermill v1.4+,否则 offset 提交会静默失败 - 示例片段:
handler.AddMiddleware(middleware.Recoverer, middleware.Retry{MaxRetries: 3, Backoff: time.Second})
context.WithTimeout 在异步任务链中为什么经常失效
因为超时 context 只对「直接监听它的 goroutine」生效;下游通过 go fn(ctx) 启动的新协程,如果没显式传递或继承该 ctx,就完全不受控。
- 常见错误:主函数里
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second),然后启动 3 个 goroutine 分别调用不同微服务,但每个 goroutine 内部又新建了独立 context - 正确做法:所有下游调用必须用
ctx作为第一个参数传入,并在 HTTP client、DB query、channel 操作中显式使用它 - 性能影响:滥用
context.WithCancel创建过多 cancel func 会增加 GC 压力,建议只在必要分支(如重试、降级)中动态派生 - 验证方式:在关键路径加
if err := ctx.Err(); err != nil { log.Println("cancelled:", err) },而不是只依赖 defer
用 temporalio/temporal-go 替代自研轮子前必须检查的三件事
Temporal 不是“换个 SDK 就能跑”,它强依赖服务端状态机,本地开发时容易误以为 workflow 执行成功,其实只是 task 被推到了不存在的 worker 队列里。
立即学习“go语言免费学习笔记(深入)”;
- 第一件事:确认
StartWorkflowOptions.TaskQueue和你注册的 worker 名字完全一致(区分大小写),否则任务永远积压在 server,无任何报错 - 第二件事:Workflow 函数不能有全局变量或闭包引用,所有数据必须通过
ExecuteWorkflow的输入参数或GetActivityOptions显式传入 - 第三件事:本地调试必须启动
temporalite(不是 docker run temporalio/auto-setup),且版本要和 SDK 主版本对齐(v1.20 SDK 必须配 v1.20 temporalite) - 容易被忽略的点:Temporal 的 retry 策略默认不包含 network error,需要手动设置
NonRetryableErrorTypes: []string{"connection refused"}










