Go原生并发无法支撑长时任务编排,因缺乏断点恢复、跨进程状态持久化、历史可查、超时重试等能力;Cadence通过确定性工作流+Activity解耦实现可靠编排。

Go 语言本身不提供工作流编排能力,Cadence 是必须引入的独立服务;直接用 goroutine + channel 处理长时间运行、需容错/恢复/可观测的任务,会迅速失控。
为什么不能只靠 Go 原生并发原语做长时任务编排
Go 的 errgroup.Group 或 sync.WaitGroup 只解决“并发启动+错误聚合”问题,但 Cadence 场景下的核心需求它们完全覆盖不了:
-
workflow执行中断后必须从断点恢复(不是重跑),而 goroutine 一旦崩溃就彻底丢失状态 - 任务可能持续数小时甚至数天,期间进程重启、节点故障是常态——
context.Context生命周期无法跨进程延续 - 需要精确查询“第 372 条数据处理到哪一步了”,这不是内存变量能回答的问题,得查持久化历史记录
- 超时、重试、子工作流依赖、信号动态调整流程——这些都得由服务端统一协调,不是客户端代码能自己 hold 住的
Cadence 工作流函数里怎么写 Go 代码才不踩坑
工作流函数(func(ctx workflow.Context, input interface{}) (interface{}, error))不是普通 Go 函数:它会被序列化、持久化、多次反序列化执行,所有操作必须是确定性的。
- 禁止调用非确定性函数:
time.Now()、math.Rand、os.Getpid()、http.Get()等——要用workflow.Now(ctx)、workflow.Sleep()、workflow.ExecuteActivity() - 所有变量必须可序列化;避免闭包捕获不可序列化对象(如
*sql.DB) - 不要在工作流函数里做 I/O 或耗时计算;把实际工作卸载到
Activity中执行 - 循环中必须有
workflow.Sleep()或workflow.Channel操作作为调度点,否则可能被判定为“卡死”而超时
Activity 函数如何安全地做长时间 I/O 操作
Activity 是真正干活的地方,但它也受 Cadence 运行时约束:默认 10 秒超时、不可重入、失败后按策略重试。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 长 I/O 必须显式设置超时:
activity.RegisterWithOptions(myActivity, activity.RegisterOptions{StartToCloseTimeout: time.Hour}) - 避免在 Activity 里开 goroutine 后直接返回——Cadence 不感知子 goroutine,主函数返回即认为 Activity 完成
- 若需轮询或分阶段处理,用
workflow.Sleep()配合心跳(activity.RecordHeartbeat())告知服务端“我还活着” - 数据库连接、HTTP 客户端等资源必须在 Activity 函数内创建并释放,不要复用全局单例(可能跨 worker 共享导致竞态)
取消信号进不来?检查这三处硬性配置
Cadence 的取消不是操作系统级 kill,而是事件驱动的协作式终止,漏掉任一环节都会导致 workflow.SetOnCancelHandler 不触发。
- 工作流启动时未传入带 cancel 支持的
workflow.Context:必须用workflow.WithWorkflowExecutionTimeout()或类似包装器初始化上下文 - Activity 执行时没设置
CancelRequested检查点:在循环体开头加if workflow.IsCanceled(ctx) { return nil, errors.New("canceled") } - Worker 注册时禁用了取消支持:
worker.Options{EnableSessionWorker: false}是默认值,但某些旧版 SDK 会默认关掉取消监听,需显式设为true
最易被忽略的是:Cadence 的“取消”不等于“立刻停止”,它只是发一个事件;工作流和 Activity 必须主动轮询 ctx.Done() 或调用 workflow.IsCanceled() 才能响应——没人替你写这个 if 判断。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










