背景
公司使用的任务调度框架是Temproal,最近我在业务中使用其作为定时任务,但我发现其不只是作为定时任务的调度,同时也是一个强大的工作流编排框架,所以就来研究一下。
什么是temproal?
Temporal 是一个开源的分布式工作流编排框架,用于构建可靠、可扩展的分布式应用。它源自 Uber 的 Cadence 项目,由原核心团队于 2019 年创建,已成为云原生工作流编排的事实标准。
📌github:github.com/temporalio/...
解决了什么问题?
传统的微服务的项目中,经常使用比如消息队列来做事件驱动的业务逻辑。但这样会有一个很明显的问题,就是业务过于分散,同一个流程的逻辑会在多个服务中进行业务处理,而这样又要引发诸如:
-
消息的时序问题,
-
重试幂等问题,
-
事件和消息链路追踪问题
-
失败补偿机制
...
那么是否存在一种方案,让所有的流程编排集中起来,我们只关心业务流程。
什么是编排?
这里直接引用知乎大佬的文章,我认为非常容易理解
服务[编排模式]
在分布式系统中,服务之间的协调主要有两种模式:编排(Orchestration)和编舞(Choreography)。
编排(Orchestration)的特点:
- 有一个中央控制器(orchestrator)负责协调和管理所有服务
- 服务之间的交互流程由 orchestrator 明确定义和控制
- 各个服务只需要执行 orchestrator 的指令,不需要了解整体流程
- 适合需要强一致性和明确控制流程的场景,注重内聚性
编舞(Choreography)的特点:
- 没有中央控制器,服务之间通过事件进行松散协作
- 每个服务独立运行,响应其他服务发出的事件
- 服务通过发布/订阅事件来实现协作
- 更灵活和可扩展,但整体流程较难跟踪和调试,注重解耦
举例说明
- 编排模式:类似于旅行预订系统,由中央服务协调航班、酒店和支付服务
**编舞模式**\]:类似于微服务系统,订单服务发出"订单创建"事件,库存和物流服务各自响应处理
从上述对比中可以看出temproal的主要功能就是作为,流程的编排以及协调。
Temporal 本质上 是一个分布式状态机的抽象层,它通过巧妙的确定性编程模型,让开发人员可以用直观的方式处理复杂的分布式业务流程,同时保证了系统的可靠性和可维护性。
这种实现机制使得 Temporal 特别适合处理:
- 复杂的业务流程编排
- 需要保证可靠性的关键业务流程
- 跨服务的分布式事务
- 长时运行的异步操作 pipeline
核心概念
要了解Temproal,我们必须要先了解其核心的一些概念
1. Workflow(工作流)
- 是一个长时间运行的过程,可能持续数分钟、数小时甚至数月;
- 必须是确定性的(deterministic) ------ 相同输入、相同执行顺序必须产生相同结果;
- 工作流不能执行 IO 操作,所有的外部操作必须交给 Activity;
- 工作流是可以被暂停、恢复、重放的;
- 工作流中的所有状态、步骤都会被记录在执行历史中(用于溯源和重放)。
示例:
伪代码
workflow {
sendEmail();
waitForUserReply();
chargeCreditCard();
}
2. Activity(活动)
Activity 是工作流中调用的实际执行任务,比如发送邮件、调用 HTTP 接口、写数据库等。
- 可以包含非确定性代码,如网络请求、数据库操作;
- 运行失败会自动重试;
- 可以配置超时、重试策略、幂等性保障;
- Activity 的执行是由 Temporal Worker 执行的,可以分布式并行部署。
- 长时间运行的 Activity 可以通过心跳机制报告其进度,这些信息可以在 Temporal UI 中查看。
📌 一个工作流通常会包含多个 Activity 调用。
3. Worker(工作节点)🧵
Worker 是真正运行工作流代码或 Activity 代码的程序进程。
-
有两类 Worker:Workflow Worker 和 Activity Worker;
-
通常你用同一个程序注册 workflow + activity;
-
Worker 会从 Temporal Server 拉取任务并执行,然后汇报执行结果;
- Work会启动多个线程池,用于:
- Workflow执行
- Activity执行
- Poller(拉取任务)
- Work会启动多个线程池,用于:
-
可以弹性扩展多个实例,提高并发执行能力。
4. Task Queue(任务队列)
工作流或活动的执行请求是放入某个 Task Queue 中,由 Worker 拉取任务执行。
- 每个 Worker 监听一个或多个任务队列;
- 支持将不同的 Workflow/Activity 绑定到不同的 Task Queue;
- 支持路由、负载均衡。
5. Temporal Server(核心引擎)
Temporal 的后端服务,负责协调整个系统:
- 负责调度工作流、持久化历史、发送任务给 Worker;
- 是高可用的服务集群,包含 Frontend、History、Matching、Worker 等子模块;
- 数据存储在数据库中(MySQL/Postgres/Cassandra 等);
- 你只需写业务逻辑,Server 负责调度、重放、持久化等系统复杂性。
6. Signal(信号)
Signal 是一种机制,允许外部事件或系统向正在运行的工作流发送数据或通知。
- 工作流可以
Workflow.await()
等待某个 Signal; - 实现了事件驱动型工作流;
- 类似于"向正在执行的流程推送消息"。
- 比如可以在审批过程中,让前端传递参数作为Singnal,而工作流会在收到Signal后才会继续向后执行
7. Query(查询)
允许从外部查询工作流的当前状态,不会影响其执行状态。
- 是只读操作;
- 常用于前端 UI 查询进度、状态。
8. Child Workflow(子工作流)
一个工作流可以启动另一个工作流作为"子工作流"。
- 子工作流是独立的,有自己的执行历史;
- 可并行运行多个子工作流;
- 主工作流可以监听子工作流完成、失败等事件。
9. Execution History(执行历史)
Temporal 会将工作流的每一步操作记录为事件,形成完整的执行日志。
- 用于恢复(crash-safe)、重放、Debug;
- 工作流恢复时会从历史中"回放"之前的操作,重新构建当前状态;
- 开发者无需手动管理。
基本概念的Q&A
🤔 Q: Activity执行失败了,如何保证异常恢复。
Activity 抛出异常,Temporal 会自动捕获、重试、记录失败日志、恢复状态。
默认的重试机制
- Temproal会自动记录异常
- 并且按照配置的策略进行 幂等重试( Temporal 自动重试失败的任务,但 不会重复执行已成功的步骤,并且会通过事件日志回放整个工作流状态。需要让 Activity 方法是幂等的。 )
手动处理失败或者补偿:
1. 使用try-catch进行捕获异常
kotlin
override fun placeOrder(userId: String): String {
val orderId = activities.createOrder(userId)
try {
activities.sendNotification(orderId)
} catch (e: Exception) {
logger.warn("通知发送失败,将记录待补偿: $e")
activities.logFailure(orderId)
}
return orderId
}
2. 调用补偿的Activity(Saga模式)
对当前或者当前以前的所有的Activity进行补偿
php
try {
activities.reserveInventory(orderId)
} catch (e: Exception) {
activities.cancelOrder(orderId)
throw e
}
🤔 Q:什么是Saga
举例说明
假设你有一个电商下单流程:
- 创建订单(订单服务)
- 扣库存(库存服务)
- 扣余额(用户服务)
- 发货(物流服务)
这四个步骤跨多个微服务,不能用传统的数据库事务。
Saga 如何处理?
步骤 | 执行操作 | 成功后 | 失败时 |
---|---|---|---|
T1 | 创建订单 | 继续 T2 | 无需补偿 |
T2 | 扣库存 | 继续 T3 | 执行 T1 的补偿(取消订单) |
T3 | 扣余额 | 继续 T4 | 补偿 T2(回滚库存),补偿 T1(取消订单) |
T4 | 发货 | 完成 | 补偿 T3、T2、T1 |
为什么不用传统事务(2pc)?
问题 | 说明 |
---|---|
2PC 会阻塞资源 | 所有服务都要锁定,直到提交或回滚 |
失败恢复难 | Coordinator 崩溃可能造成数据不一致 |
扩展性差 | 适用于同库,微服务架构下基本不适用 |
Saga 就是为了解决这些问题而诞生的。
🤔 Q:为什么Workflow不直接执行Activity?
基于以下原因:
- 如果 Workflow 直接调用 Activity,可能会因外部依赖(如网络延迟、服务宕机)导致非确定性行为。 说明: 其实就是Workflow本身不支持事件的重放,导致Workflow每次执行Activity都会导致再次运行,而这样的结果并不是一致的(比如之前已经下过单了, 但是再执行又会下单)。但是当Activity交给Server,执行后,如果重放Workflow, Server会直接复用重放前的上次的结果,而不是直接调用Activity,而不会不一致。
- Activity 执行失败可能导致 Workflow 进程崩溃(如线程阻塞、资源泄漏)。
🤔 Q:Workflow中编排的Activity是怎么执行的?
Workflow中的Activity执行流程
- Workflow 代码调用 Activity
- 你的 Workflow 代码里写调用 Activity 的代码(比如 Java 里通过
ActivityStub
调用)。 - 这时,Workflow Worker 并不是立即执行 Activity 代码,而是把调用请求封装成一个任务(ActivityTask)。
- Workflow Worker 将 Activity 调度请求发送给 Temporal Server
- Workflow Worker 通过 RPC 调用,将 Activity 执行请求(包括方法名、参数等)发送给 Temporal Server。
- Temporal Server 把这个 Activity 任务写入事件历史和对应的 Task Queue。
- Activity Worker 监听 Task Queue 执行 Activity
- 专门跑 Activity 代码的 Worker(Activity Worker)会监听这个 Task Queue。
- 它接收到 Activity 任务后,调用对应的 Activity 实现代码,完成具体业务操作。
- Activity 执行结果返回给 Temporal Server
- Activity 执行完成后,结果(成功或失败)通过 RPC 返回给 Temporal Server。
- Temporal Server 更新事件历史,记录 Activity 执行状态和结果。
- Temporal Server 通知 Workflow Worker 继续执行
- Temporal Server 通知 Workflow Worker Activity 执行完成。
- Workflow Worker 继续重放 Workflow 代码,拿到 Activity 结果后,继续往下走。
使用举例
🎯 场景:用户下单支付流程
用户下单后:
- 创建订单
- 扣减库存
- 发起支付
- 等待支付结果(可能超时)
- 成功 → 发货;失败 → 取消订单、回滚库存
我们使用 Temporal 编排整个流程,并保证每一步具备可靠性、幂等性、可恢复性。
Temporal 优势
特性 | 应用 |
---|---|
重试机制 | 网络异常、服务失败自动重试 |
超时控制 | 支付等待 30 分钟 |
Signal | 用户主动取消 |
子工作流 | 支付流程可以作为子工作流 |
幂等 | 防止重复扣库存、重复发货 |
1. Workflow 接口定义
java
@WorkflowInterface
public interface OrderWorkflow {
@WorkflowMethod
void placeOrder(String orderId);
}
2. Workflow 实现类
java
public class OrderWorkflowImpl implements OrderWorkflow {
private final OrderActivities activities = Workflow.newActivityStub(
OrderActivities.class,
ActivityOptions.newBuilder()
.setStartToCloseTimeout(Duration.ofMinutes(5))
.build()
);
@Override
public void placeOrder(String orderId) {
// 创建订单
activities.createOrder(orderId);
// 扣库存
activities.reserveInventory(orderId);
// 调用子工作流进行支付
PaymentWorkflow paymentWorkflow = Workflow.newChildWorkflowStub(PaymentWorkflow.class);
boolean paymentSuccess = paymentWorkflow.pay(orderId);
if (paymentSuccess) {
activities.shipOrder(orderId);
} else {
activities.cancelOrder(orderId);
activities.releaseInventory(orderId);
}
}
}
3. Payment 子工作流接口
java
@WorkflowInterface
public interface PaymentWorkflow {
@WorkflowMethod
boolean pay(String orderId);
}
4. 子工作流实现
java
public class PaymentWorkflowImpl implements PaymentWorkflow {
private final PaymentActivities activities = Workflow.newActivityStub(
PaymentActivities.class,
ActivityOptions.newBuilder()
.setStartToCloseTimeout(Duration.ofMinutes(5))
.build()
);
@Override
public boolean pay(String orderId) {
String paymentId = activities.initiatePayment(orderId);
// 等待支付结果(30分钟)
return Workflow.await(Duration.ofMinutes(30), () ->
"SUCCESS".equals(activities.checkPaymentStatus(paymentId))
);
}
}
5. Activity 接口示例
java
@ActivityInterface
public interface OrderActivities {
void createOrder(String orderId);
void reserveInventory(String orderId);
void cancelOrder(String orderId);
void releaseInventory(String orderId);
void shipOrder(String orderId);
}
@ActivityInterface
public interface PaymentActivities {
String initiatePayment(String orderId);
String checkPaymentStatus(String paymentId);
}
🎯 场景:每天凌晨 3 点同步用户数据
1. 定义 Workflow 接口
java
@WorkflowInterface
public interface DailySyncWorkflow {
@WorkflowMethod
void start(); // 启动工作流
}
2. Workflow 实现类
java
public class DailySyncWorkflowImpl implements DailySyncWorkflow {
private final SyncActivities activities =
Workflow.newActivityStub(SyncActivities.class,
ActivityOptions.newBuilder()
.setStartToCloseTimeout(Duration.ofMinutes(10))
.setRetryOptions(RetryOptions.newBuilder()
.setMaximumAttempts(3)
.build())
.build());
@Override
public void start() {
while (true) {
try {
activities.syncUserData();
} catch (Exception e) {
Workflow.getLogger(DailySyncWorkflowImpl.class)
.error("同步失败: " + e.getMessage());
}
// 当前时间
Instant now = Instant.ofEpochMilli(Workflow.currentTimeMillis());
ZonedDateTime next3am = now.atZone(ZoneId.of("Asia/Shanghai"))
.plusDays(1)
.withHour(3).withMinute(0).withSecond(0).withNano(0);
Duration sleepDuration = Duration.between(now, next3am.toInstant());
Workflow.sleep(sleepDuration);
}
}
}
3. 定义 Activity 接口
java
@ActivityInterface
public interface SyncActivities {
@ActivityMethod
void syncUserData();
}
4. 实现 Activity
java
public class SyncActivitiesImpl implements SyncActivities {
@Override
public void syncUserData() {
System.out.println("同步用户数据开始...");
// 模拟失败
if (new Random().nextInt(10) < 2) {
throw new RuntimeException("同步失败,网络异常");
}
System.out.println("同步成功 ✅");
}
}
5. 启动 Worker(注册工作流和 Activity)
java
public class WorkerStarter {
public static void main(String[] args) {
WorkflowService service = WorkflowServiceStubs.newInstance();
WorkflowClient client = WorkflowClient.newInstance(service);
WorkerFactory factory = WorkerFactory.newInstance(client);
Worker worker = factory.newWorker("DAILY_SYNC_TASK_QUEUE");
worker.registerWorkflowImplementationTypes(DailySyncWorkflowImpl.class);
worker.registerActivitiesImplementations(new SyncActivitiesImpl());
factory.start();
}
}
6. 启动工作流(一次性调用)
java
public class WorkflowStarter {
public static void main(String[] args) {
WorkflowService service = WorkflowServiceStubs.newInstance();
WorkflowClient client = WorkflowClient.newInstance(service);
DailySyncWorkflow workflow = client.newWorkflowStub(
DailySyncWorkflow.class,
WorkflowOptions.newBuilder()
.setTaskQueue("DAILY_SYNC_TASK_QUEUE")
.setWorkflowId("daily-sync-workflow") // 保证幂等启动
.build());
WorkflowClient.start(workflow::start);
}
}