【Temproal】快速了解Temproal的核心概念以及使用

592 阅读11分钟

背景

公司使用的任务调度框架是Temproal,最近我在业务中使用其作为定时任务,但我发现其不只是作为定时任务的调度,同时也是一个强大的工作流编排框架,所以就来研究一下。

什么是temproal?

Temporal 是一个开源的分布式工作流编排框架,用于构建可靠、可扩展的分布式应用。它源自 Uber 的 Cadence 项目,由原核心团队于 2019 年创建,已成为云原生工作流编排的事实标准。

📌github:github.com/temporalio/…

解决了什么问题?

传统的微服务的项目中,经常使用比如消息队列来做事件驱动的业务逻辑。但这样会有一个很明显的问题,就是业务过于分散,同一个流程的逻辑会在多个服务中进行业务处理,而这样又要引发诸如:

  1. 消息的时序问题,

  2. 重试幂等问题,

  3. 事件和消息链路追踪问题

  4. 失败补偿机制

...

那么是否存在一种方案,让所有的流程编排集中起来,我们只关心业务流程

什么是编排?

这里直接引用知乎大佬的文章,我认为非常容易理解

服务[编排模式]

在分布式系统中,服务之间的协调主要有两种模式:编排(Orchestration)和编舞(Choreography)。

编排(Orchestration)的特点:

  • 有一个中央控制器(orchestrator)负责协调和管理所有服务
  • 服务之间的交互流程由 orchestrator 明确定义和控制
  • 各个服务只需要执行 orchestrator 的指令,不需要了解整体流程
  • 适合需要强一致性和明确控制流程的场景,注重内聚性

编舞(Choreography)的特点:

  • 没有中央控制器,服务之间通过事件进行松散协作
  • 每个服务独立运行,响应其他服务发出的事件
  • 服务通过发布/订阅事件来实现协作
  • 更灵活和可扩展,但整体流程较难跟踪和调试,注重解耦

举例说明

  • 编排模式:类似于旅行预订系统,由中央服务协调航班、酒店和支付服务
  • [编舞模式]:类似于微服务系统,订单服务发出"订单创建"事件,库存和物流服务各自响应处理

Temporal 采用的是编排模式,这种集中式的方式让我们能够更好地关注业务流程本身,而不是被技术细节分散注意力。这也使得业务人员更容易理解和参与开发过程,因为代码更接近业务语言。

从上述对比中可以看出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 WorkerActivity Worker

  • 通常你用同一个程序注册 workflow + activity;

  • Worker 会从 Temporal Server 拉取任务并执行,然后汇报执行结果;

    • Work会启动多个线程池,用于:
      • Workflow执行
      • Activity执行
      • Poller(拉取任务)
  • 可以弹性扩展多个实例,提高并发执行能力。

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 会自动捕获、重试、记录失败日志、恢复状态

默认的重试机制

  1. Temproal会自动记录异常
  2. 并且按照配置的策略进行 幂等重试( Temporal 自动重试失败的任务,但 不会重复执行已成功的步骤,并且会通过事件日志回放整个工作流状态。需要让 Activity 方法是幂等的

手动处理失败或者补偿:

1. 使用try-catch进行捕获异常
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进行补偿

try {
    activities.reserveInventory(orderId)
} catch (e: Exception) {
    activities.cancelOrder(orderId)
    throw e
}

🤔 Q:什么是Saga

举例说明

假设你有一个电商下单流程:

  1. 创建订单(订单服务)
  2. 扣库存(库存服务)
  3. 扣余额(用户服务)
  4. 发货(物流服务)

这四个步骤跨多个微服务,不能用传统的数据库事务。

Saga 如何处理?

步骤执行操作成功后失败时
T1创建订单继续 T2无需补偿
T2扣库存继续 T3执行 T1 的补偿(取消订单)
T3扣余额继续 T4补偿 T2(回滚库存),补偿 T1(取消订单)
T4发货完成补偿 T3、T2、T1

为什么不用传统事务(2pc)?

问题说明
2PC 会阻塞资源所有服务都要锁定,直到提交或回滚
失败恢复难Coordinator 崩溃可能造成数据不一致
扩展性差适用于同库,微服务架构下基本不适用

Saga 就是为了解决这些问题而诞生的。

🤔 Q:为什么Workflow不直接执行Activity?

基于以下原因:

  1. 如果 Workflow 直接调用 Activity,可能会因外部依赖(如网络延迟、服务宕机)导致非确定性行为。 说明: 其实就是Workflow本身不支持事件的重放,导致Workflow每次执行Activity都会导致再次运行,而这样的结果并不是一致的(比如之前已经下过单了, 但是再执行又会下单)。但是当Activity交给Server,执行后,如果重放Workflow, Server会直接复用重放前的上次的结果,而不是直接调用Activity,而不会不一致。
  2. Activity 执行失败可能导致 Workflow 进程崩溃(如线程阻塞、资源泄漏)。

🤔 Q:Workflow中编排的Activity是怎么执行的?

Workflow中的Activity执行流程

  1. Workflow 代码调用 Activity
  • 你的 Workflow 代码里写调用 Activity 的代码(比如 Java 里通过 ActivityStub 调用)。
  • 这时,Workflow Worker 并不是立即执行 Activity 代码,而是把调用请求封装成一个任务(ActivityTask)。
  1. Workflow Worker 将 Activity 调度请求发送给 Temporal Server
  • Workflow Worker 通过 RPC 调用,将 Activity 执行请求(包括方法名、参数等)发送给 Temporal Server。
  • Temporal Server 把这个 Activity 任务写入事件历史和对应的 Task Queue。
  1. Activity Worker 监听 Task Queue 执行 Activity
  • 专门跑 Activity 代码的 Worker(Activity Worker)会监听这个 Task Queue。
  • 它接收到 Activity 任务后,调用对应的 Activity 实现代码,完成具体业务操作。
  1. Activity 执行结果返回给 Temporal Server
  • Activity 执行完成后,结果(成功或失败)通过 RPC 返回给 Temporal Server。
  • Temporal Server 更新事件历史,记录 Activity 执行状态和结果。
  1. Temporal Server 通知 Workflow Worker 继续执行
  • Temporal Server 通知 Workflow Worker Activity 执行完成。
  • Workflow Worker 继续重放 Workflow 代码,拿到 Activity 结果后,继续往下走。

使用举例

🎯 场景:用户下单支付流程


用户下单后:

  1. 创建订单
  2. 扣减库存
  3. 发起支付
  4. 等待支付结果(可能超时)
  5. 成功 → 发货;失败 → 取消订单、回滚库存

我们使用 Temporal 编排整个流程,并保证每一步具备可靠性、幂等性、可恢复性。

Temporal 优势

特性应用
重试机制网络异常、服务失败自动重试
超时控制支付等待 30 分钟
Signal用户主动取消
子工作流支付流程可以作为子工作流
幂等防止重复扣库存、重复发货

1. Workflow 接口定义

@WorkflowInterface
public interface OrderWorkflow {
    @WorkflowMethod
    void placeOrder(String orderId);
}

2. Workflow 实现类

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 子工作流接口

@WorkflowInterface
public interface PaymentWorkflow {
    @WorkflowMethod
    boolean pay(String orderId);
}

4. 子工作流实现

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 接口示例

@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 接口

@WorkflowInterface
public interface DailySyncWorkflow {

    @WorkflowMethod
    void start(); // 启动工作流
}

2. Workflow 实现类

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 接口

@ActivityInterface
public interface SyncActivities {

    @ActivityMethod
    void syncUserData();
}

4. 实现 Activity

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)

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. 启动工作流(一次性调用)

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);
    }
}

参考