因为要的是嵌入式、无额外进程、go原生语法的协同编排能力,而非带ui的工作流平台;temporal需部署server,conductor依赖http api,运维复杂,而goroutine+channel+context已足以可靠实现订单创建后的风控、发券、归档等步骤及失败重试/回滚。

为什么不用 Temporal 或 Conductor 直接上手
因为你要的不是“带 UI 的工作流平台”,而是嵌入服务内部、不引入额外进程、能用 Go 原生语法写逻辑的协同编排能力。Temporal 需部署 Server,Conductor 默认走 HTTP API,两者都增加运维面;而你只是想让订单创建后自动触发风控、发券、归档三个步骤,并保证其中一步失败可重试、跳过或回滚——这种场景,goroutine + channel + context.Context 就够用,且可控性更高。
串行 vs 并行:怎么选调度模型
关键看任务间是否有数据依赖或执行约束:
- 串行(顺序调用):用同步函数调用最稳妥,比如
validateEmail()返回用户 ID 后再传给sendWelcomeEmail(userID)—— 无 channel、无 goroutine,调试直观,错误堆栈完整 - 并行(聚合型):如“同时调用风控和库存服务,等两者都返回再决定是否扣减”,必须用
make(chan result, 2)+select+time.After(timeout)控制超时,避免单个下游卡死整个流程 - 混合编排(条件分支):别硬写 if/else 嵌套,把每个分支封装成独立函数,用 map[string]func() error 管理,比如
steps["high-risk"] = func() error { return runManualReview() }
状态管理不能只靠内存变量
本地开发时用 struct 字段存当前步骤名、错误信息没问题,但一旦服务重启,进行中的流程就彻底丢失。生产环境必须落盘:
- 轻量首选
SQLite:单文件、零配置、支持事务,用github.com/mattn/go-sqlite3插入一条记录只需 3 行 SQL,比 Redis 更适合存结构化流程状态(如 step_name、input_data、retry_count) - Redis 适合高频读写场景:比如每秒上千次心跳更新,但要注意
SET workflow:123 state "running" EX 300必须配EX过期,否则僵尸状态堆积 - 千万别用全局 map 存状态:goroutine 并发写会 panic,加
sync.RWMutex后性能瓶颈明显,且无法跨实例共享
错误处理别只 log.Fatal 或 panic
编排引擎里一次失败不该终止整个进程,而应被识别、分类、响应:
- 网络类错误(
net/httptimeout、connection refused):标记为 transient,自动重试(最多 3 次),用time.Sleep(2 * time.Second)指数退避 - 业务类错误(如风控拒绝、库存不足):标记为 permanent,直接跳转到 fallback 步骤,比如发短信通知人工介入
- panic 类错误(空指针、数组越界):用
recover()捕获,转成统一ErrPanic类型,写入状态表并告警,避免 goroutine 泄漏
真正容易被忽略的是重试边界——不是所有错误都该重试,比如支付成功后回调重复,重试会导致资金重复扣除;必须在每个任务函数开头加幂等判断,例如查 payment_log 表确认该订单是否已处理。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











