Cadence工作流引擎:从入门到生产部署的完整指南 1. 先搞清楚 Cadence Addicted 到底指什么Cadence Addicted 这个说法在技术圈里其实有两种常见指向一种是字面意义上的“节奏上瘾”比如音乐制作、运动节奏或工作流中的节奏依赖另一种是特指 Cadence 这个由 Uber 开源的分布式工作流引擎——当你深度使用后产生的依赖感。从标题和常见搜索来看这里更可能是指后者也就是 Cadence 工作流引擎在实际生产环境中用顺手之后团队对其可靠性、可观测性和容错能力产生的“依赖症”。这种依赖不是贬义而是指一旦你用 Cadence 解决了分布式任务调度、状态持久化、失败重试和可视化追踪这些问题后就很难再退回用数据库队列定时任务的土法炼钢阶段。它最适合的场景是那些有长时间运行、需要保持状态、可能失败需要自动恢复、且需要人工介入或复杂判断的异步任务。比如订单处理、数据流水线、媒体转码、跨服务编排等。但要注意Cadence 不是万能的。它适合有状态、长周期、需要可靠性的工作流但对于简单的一次性任务或高吞吐的短任务反而会引入不必要的复杂度。所以第一步是先判断你的业务是否真的需要工作流引擎——如果你的任务能在几分钟内完成且失败后重跑成本很低可能用消息队列加重试机制就够了。2. 本地开发环境怎么快速搭起来试水如果你还没接触过 Cadence我建议先用 Docker Compose 在本地起一套完整环境。这样能避免生产环境权限和网络问题快速验证基本功能。Cadence 依赖 Cassandra 或 MySQL 作为持久化存储本地测试通常用 Cassandra 更简单。先准备一个docker-compose.ymlversion: 3 services: cassandra: image: cassandra:3.11 ports: - 9042:9042 cadence: image: ubercadence/server:0.22.4 ports: - 7933:7933 # 前端服务端口 - 7934:7934 # 后端服务端口 environment: - CASSANDRA_SEEDScassandra - SKIP_SCHEMA_SETUPfalse - DYNAMIC_CONFIG_FILE_PATHconfig/dynamicconfig/development.yaml cadence-web: image: ubercadence/web:3.2.2 ports: - 8088:8088 environment: - CADENCE_TCHANNEL_PEERScadence:7933 - CADENCE_WEB_PORT8088然后直接docker-compose up启动。这个过程会拉取镜像、初始化 Cassandra 表结构、启动 Cadence 服务端和 Web UI。Web UI 在 http://localhost:8088这里可以看到工作流的历史记录、状态和详细日志。接下来需要装客户端 SDK。Cadence 支持 Go、Java、Python 等以 Go 为例go get go.uber.org/cadence go get go.uber.org/cadence/cmd/tools/cadence-cli装好后用cadence-cli验证连接cadence-cli --address localhost:7933 domain list如果返回default域名说明基础环境通了。这里最容易卡住的地方是 Cassandra 初始化超时或端口冲突第一次启动最好盯着日志确认 Cassandra 表结构初始化完成再测试。3. 第一个工作流怎么定义和执行Cadence 的工作流定义和活动Activity定义是分开的。工作流负责编排逻辑和状态活动负责执行具体任务。比如一个简单的订单处理工作流先定义活动接口Go 版本package order import context type Activities struct {} func (a *Activities) CreateOrder(ctx context.Context, orderID string) error { // 模拟创建订单逻辑 return nil } func (a *Activities) ProcessPayment(ctx context.Context, orderID string) error { // 模拟支付处理 return nil } func (a *Activities) SendConfirmation(ctx context.Context, orderID string) error { // 发送确认邮件 return nil }然后定义工作流package order import ( time go.uber.org/cadence/workflow go.uber.org/zap ) func OrderWorkflow(ctx workflow.Context, orderID string) error { logger : workflow.GetLogger(ctx) logger.Info(Order workflow started, zap.String(orderID, orderID)) // 设置执行选项任务超时、重试策略等 ao : workflow.ActivityOptions{ ScheduleToStartTimeout: time.Minute, StartToCloseTimeout: time.Minute, RetryPolicy: workflow.RetryPolicy{ InitialInterval: time.Second, BackoffCoefficient: 2.0, MaximumInterval: time.Minute, MaximumAttempts: 3, }, } ctx workflow.WithActivityOptions(ctx, ao) // 步骤1创建订单 err : workflow.ExecuteActivity(ctx, a.CreateOrder, orderID).Get(ctx, nil) if err ! nil { return err } // 步骤2处理支付可能失败需要重试 err workflow.ExecuteActivity(ctx, a.ProcessPayment, orderID).Get(ctx, nil) if err ! nil { return err } // 步骤3发送确认 err workflow.ExecuteActivity(ctx, a.SendConfirmation, orderID).Get(ctx, nil) return err }最后启动工作流的 Worker 和 Starter// worker.go package main import ( go.uber.org/cadence/worker go.uber.org/cadence/client ) func main() { // 创建 Cadence 客户端 cadenceClient, _ : client.NewClient(client.Options{ HostPort: localhost:7933, Domain: default, }) // 创建 Worker w : worker.New(cadenceClient, order-task-list, worker.Options{}) w.RegisterWorkflow(order.OrderWorkflow) w.RegisterActivity(order.Activities{}) // 启动 Worker err : w.Run() if err ! nil { panic(err) } } // starter.go package main import ( context go.uber.org/cadence/client ) func main() { cadenceClient, _ : client.NewClient(client.Options{ HostPort: localhost:7933, Domain: default, }) options : client.StartWorkflowOptions{ ID: order_ time.Now().Format(20060102150405), TaskList: order-task-list, } we, err : cadenceClient.StartWorkflow(context.Background(), options, order.OrderWorkflow, order-123) if err ! nil { panic(err) } }先运行worker.go再运行starter.go然后在 Web UI 就能看到工作流执行记录了。这里最容易混淆的是 Worker 的 TaskList 必须和启动工作流时的 TaskList 一致否则 Worker 收不到任务。4. 生产环境部署要注意哪些关键配置本地测试能跑通只是第一步真要上生产有几个配置点必须提前规划。首先是持久化存储选择。Cassandra 适合写多读少的场景但如果团队更熟悉 MySQL也可以用 MySQL 8.0。生产环境一定要配置存储的备份、监控和性能调优。Cadence 的表很多特别是历史记录表容易膨胀需要定期归档或清理。其次是 Cadence 服务本身的高可用。至少部署 3 个节点用负载均衡暴露前端端口。关键配置包括# config/dynamicconfig/production.yaml frontend: rps: 1000 # 前端请求速率限制 maxWorkflowExecutionRate: 100 # 单个工作流最大执行速率 history: cacheMaxSize: 10000 # 历史记录缓存大小 shardCount: 1000 # 分片数影响并发处理能力 matching: rps: 1000 # 匹配服务速率限制分片数shardCount这个参数特别重要它决定了工作流状态的分片数量。如果设置太小高并发时会出现分片竞争设置太大又会增加 Cassandra 压力。一般建议按(预计峰值工作流数 / 1000) * 2来估算。然后是 Worker 的部署。Worker 最好和应用服务分开部署避免资源竞争。每个 Worker 进程可以处理多个 TaskList但要根据 CPU 和内存情况控制并发数w : worker.New(cadenceClient, order-task-list, worker.Options{ MaxConcurrentActivityExecutionSize: 10, // 活动并发数 MaxConcurrentLocalActivityExecutionSize: 10, // 本地活动并发数 MaxConcurrentDecisionExecutionSize: 10, // 决策并发数 })监控方面Cadence 内置了 Metrics 输出可以对接 PrometheusGrafana。关键指标包括工作流启动速率、活动执行成功率、任务队列长度、分片租赁延迟等。报警要设在队列堆积或错误率上升时而不是等完全卡死再处理。5. 常见坑点和排查顺序用 Cadence 最常遇到的问题是工作流卡住或活动执行失败。排查时要按顺序看不要一上来就怀疑 Cadence 本身的问题。先看工作流状态。在 Web UI 找到对应工作流 ID看当前停在哪个步骤。如果是决策任务超时通常是 Worker 忙不过来或网络问题如果是活动执行失败看具体错误信息。活动失败最常见的原因是超时设置不合理。比如一个活动实际需要 5 分钟但你只设置了 1 分钟超时那肯定会失败。超时设置要基于实际执行时间并留出余量ao : workflow.ActivityOptions{ ScheduleToStartTimeout: time.Minute * 2, // 排队到开始的时间 StartToCloseTimeout: time.Minute * 10, // 开始到结束的时间 HeartbeatTimeout: time.Minute, // 心跳超时用于长任务 }对于长时间运行的活动要配置心跳机制这样即使活动执行时间长Cadence 也知道它还在运行func (a *Activities) LongRunningTask(ctx context.Context) error { for i : 0; i 100; i { // 业务逻辑 time.Sleep(time.Second * 10) // 定期发送心跳 activity.RecordHeartbeat(ctx, fmt.Sprintf(progress-%d, i)) } return nil }第二个常见问题是版本兼容性。Cadence 服务端、客户端 SDK、CLI 工具版本要匹配特别是大版本升级时可能有 breaking change。升级前先在测试环境验证工作流定义和执行逻辑是否正常。第三个问题是资源泄漏。工作流历史记录会一直保存除非配置了保留策略。对于完成的工作流可以设置自动归档options : client.StartWorkflowOptions{ ID: workflow-id, TaskList: task-list, WorkflowIDReusePolicy: client.WorkflowIDReusePolicyAllowDuplicate, RetentionPeriod: time.Hour * 24 * 7, // 保留7天 }如果发现 Cassandra 磁盘增长过快可能是历史记录表没有正确清理需要检查归档配置或手动清理过期数据。6. 什么时候该用 Cadence什么时候不该用经过实际项目验证Cadence 在以下场景确实能让你“上瘾”有状态长时间任务比如视频转码需要保持转码进度失败后能从断点继续。需要人工介入的工作流比如审核流程某个环节需要等人工确认后才能继续。复杂业务编排涉及多个微服务调用且需要保证最终一致性。需要完整可观测性老板或业务方经常问“这个任务到哪一步了”。但在这些情况下你可能不需要 Cadence简单异步任务发个邮件、清理缓存这种一次性任务用消息队列更轻量。高吞吐短任务每个任务就几秒钟Cadence 的持久化开销反而成为瓶颈。实时性要求高的场景工作流调度有延迟不适合毫秒级响应的业务。团队技术储备不足Cadence 的概念较多如果团队连消息队列都用不好直接上工作流引擎会增加维护成本。我个人建议可以先在非核心业务上试点一个典型工作流比如数据导出、报表生成这类对实时性要求不高的场景。跑通后再逐步应用到核心业务流程。这样既能积累经验又能控制风险。真正让团队对 Cadence 产生依赖的往往是它在复杂场景下表现出的稳定性和可调试性。一旦经历过用 Cadence 轻松处理掉需要人工干预的重试、状态恢复和进度追踪后就很难再回到手动处理这些问题的时代了。但这种“依赖”是建立在正确理解和合理使用基础上的不是盲目套用。