十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

CompletableFuture实现顺序工作流异步编排与异常中断机制

CompletableFuture实现顺序工作流异步编排与异常中断机制 之前在业务系统改造中我遇到一个很典型的场景多个任务必须按预先定义好的顺序执行比如先校验订单、再扣减库存、再计算价格、最后发送通知而每个任务又都可能是耗时的 I/O 操作。如果同步执行整体耗时就是所有任务耗时的总和如果为了提速直接开多线程并行又无法保证任务之间的先后依赖。更麻烦的是一旦某个环节失败后续任务必须立即终止否则就会出现“库存没扣成功但通知已经发出去”这类脏数据。一开始我尝试用线程池 状态机硬编码结果状态流转散落在各个业务方法里代码越写越难维护。后来切换到 CompletableFuture把整个工作流拆成多个异步 stage 串联起来顺序、异步、异常中断的问题一次性都解决了。这篇文章就把顺序工作流异步执行的完整思路拆解一遍包含核心概念、CompletableFuture 的串联方式、异常后中断后续任务的关键机制、可运行的实战代码以及我在项目中总结出的排错清单和工程建议。1. 背景与核心概念1.1 什么是顺序工作流顺序工作流Sequential Workflow指的是多个任务之间存在明确的先后依赖关系前一个任务必须完全成功后后一个任务才能开始。举个例子一个标准的订单处理流程通常是这样步骤任务依赖关系1校验订单参数无2扣减库存依赖步骤 13计算订单金额依赖步骤 24发送通知依赖步骤 3每个步骤的执行结果会作为下一个步骤的输入。如果步骤 2 扣减库存失败那么步骤 3 和步骤 4 都没有意义必须中断后续任务。这种工作流在电商、支付、审批、任务调度系统中非常常见。它的核心诉求是控制任务顺序、传递中间结果、异常及时终止。1.2 什么是异步执行异步执行指的是调用方发起一个任务后不阻塞等待结果而是继续做其他事情任务完成后通过回调、通知或轮询等方式获取结果。与同步执行的对比维度同步执行异步执行调用方式阻塞等待返回立即返回任务后台执行耗时所有任务耗时累加调用本身几乎不耗时编程复杂度简单直观需要处理回调和异常传播适用场景任务少、耗时短任务多、耗时长、并发要求高在顺序工作流中引入异步执行最直接的好处是工作流中耗时长的任务不会阻塞主线程主线程可以继续接收新的请求。而 CompletableFuture 进一步解决了异步任务的编排问题让我能够用类似同步代码的方式描述任务之间的依赖关系。1.3 CompletableFuture 在顺序工作流中的定位CompletableFuture 是 JDK 8 引入的异步编程工具本质上是 Future 的增强版本。它解决了传统 Future 的两个痛点传统 Future 只能通过 get() 阻塞获取结果无法主动感知任务完成。传统 Future 无法直接描述任务之间的依赖关系。CompletableFuture 则提供了丰富的编排方法可以把多个异步任务串联成一条链每个阶段stage执行完自动触发下一个阶段同时支持异常穿透、超时控制和线程池切换。在顺序工作流中CompletableFuture 起到的作用相当于“流水线控制器”它既负责异步执行又负责把每个任务的结果传递给下一个任务还负责在异常发生时把整条流水线停下来。2. 环境准备与版本说明2.1 运行环境本文示例基于以下环境实际项目请以你的运行环境为准环境项说明JDKJDK 8 及以上建议 JDK 8 即可JDK 9 有更强的新 API构建工具Maven 3.6 或 Gradle 6IDEIntelliJ IDEA / Eclipse / VS Code 均可操作系统Windows / macOS / Linux 均可CompletableFuture 是 JDK 自带类不需要引入任何第三方依赖这也是它作为工作流编排工具的先天优势。2.2 核心依赖如果是 Maven 项目基础配置只需要一个空的 pom不需要额外依赖project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion groupIdcom.example/groupId artifactIdworkflow-async-demo/artifactId version1.0-SNAPSHOT/version packagingjar/packaging properties maven.compiler.source8/maven.compiler.source maven.compiler.target8/maven.compiler.target /properties /project如果使用 Lombok 简化实体类代码可以额外引入dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId version1.18.30/version scopeprovided/scope /dependency如果不想引入 Lombok完全可以用手写 getter/setter 替代不影响核心逻辑。2.3 线程池配置说明这是容易踩坑的地方。CompletableFuture 如果不指定线程池会使用公共的 ForkJoinPool.commonPool()它的线程数默认是 CPU 核心数减 1。在 IO 密集型任务多的场景下公共线程池很容易被占满任务互相排队最终拖垮整个应用。因此实际项目中必须为工作流定义独立的线程池并在创建 CompletableFuture 时显式传入。3. CompletableFuture 核心原理与顺序编排3.1 CompletableFuture 的创建CompletableFuture 最常用的两个创建方式方法返回结果说明supplyAsync(Supplier)CompletableFuture异步执行有返回值的任务runAsync(Runnable)CompletableFuture异步执行无返回值的任务// 有返回值的异步任务 CompletableFutureString future CompletableFuture.supplyAsync(() - { return task result; }, executor); // 无返回值的异步任务 CompletableFutureVoid future CompletableFuture.runAsync(() - { System.out.println(task executed); }, executor);需要注意的是第二个参数是线程池。如果没有传第二个参数任务会跑在 ForkJoinPool.commonPool() 上。3.2 顺序串联的关键方法CompletableFuture 提供了多个方法用于描述依赖关系它们的语义不同使用时要区分清楚。方法是否异步执行输入输出thenApply同线程同步执行上一个阶段结果新的结果thenApplyAsync在线程池异步执行上一个阶段结果新的结果thenCompose同线程同步执行上一个阶段结果一个新的 CompletableFuturethenAccept同线程同步执行上一个阶段结果voidthenRun同线程同步执行无需上一个阶段结果void在顺序工作流中最常用的是 thenApplyAsync 和 thenComposethenApplyAsync把上一步的结果转换为下一步的输入适合处理“结果需要转换”的场景。thenCompose适合“下一步本身是异步任务”的场景能避免 CompletableFuture 嵌套。第一个容易踩坑的点thenApply 和 thenApplyAsync 的区别。thenApply 是在前一个任务完成的当前线程上同步执行不具备真正的异步效果thenApplyAsync 才会把任务重新提交到线程池执行。如果希望工作流的每个 stage 都能被线程池调度就应该使用带 Async 后缀的方法。3.3 异常处理与异常传播机制这是本文最核心的一个知识点也是标题里“completablefuture 异常后不再执行其他的异步任务”要解决的核心问题。CompletableFuture 的异常传播机制可以概括为一句话当一个 stage 异常完成时它的异常会向下游传递下游未处理的 stage 会直接跳过直到遇到一个能处理该异常的 stage。先看一个直观示例CompletableFuture.supplyAsync(() - { throw new RuntimeException(step1 failed); }, executor) .thenApplyAsync(v - { System.out.println(step2 execute: v); return v - step2; }, executor) .thenApplyAsync(v - { System.out.println(step3 execute: v); return v - step3; }, executor) .exceptionally(ex - { System.out.println(caught: ex.getMessage()); return fallback; }, executor);输出结果caught: step1 failed这里最关键的是step2 和 step3 都没有打印执行日志。因为 step1 抛出了异常异常沿着链式调用向下传递step2 和 step3 作为普通依赖阶段感知到上游异常后直接跳过。这正好满足“异常后不再执行其他异步任务”的需求。但如果把 exceptionally 移动到链条中间问题就出现了CompletableFuture.supplyAsync(() - { throw new RuntimeException(step1 failed); }, executor) .exceptionally(ex - { System.out.println(caught: ex.getMessage()); return fallback; }, executor) .thenApplyAsync(v - { System.out.println(step2 execute: v); return v - step2; }, executor);输出结果caught: step1 failed step2 execute: fallback看到问题了吗exceptionally 在 step2 之前捕获了异常并返回了兜底值于是 step2 感知到的上游结果是正常的 fallback它会继续执行。这意味着如果希望异常发生后整条链立即停止exceptionally 必须放在链的末尾或者干脆不在链上配置恢复逻辑只在外层统一捕获。如果希望某个中间环节出异常后后续环节还能用兜底值继续跑那可以把 exceptionally 放在对应的位置。3.4 中间结果传递与异常中断的完整语义顺序工作流中的依赖不只是“是否继续”还包含“上一个结果怎么传给下一个”。thenApplyAsync 的实现机制决定了它可以自动接收上游结果并传给下游。下面用一个简单模型描述stage1 成功 - 结果传给 stage2 stage1 失败 - 异常传给 stage2stage2 跳过继续流向 stage3 stage2 成功 - 结果传给 stage3 stage2 失败 - 异常传给 stage3stage3 跳过每个 stage 的状态只有两种正常完成携带结果或异常完成携带异常。下游 stage 根据上游状态决定是执行还是跳过。理解了这个模型就可以解释很多看似“奇怪”的行为比如为什么异常以后后面的 thenApplyAsync 有时不执行、有时又执行了因为如果中间存在 exceptionally/handle 等恢复方法异常会被“消化”恢复后的正常值会继续传递给后面的 stage。为什么日志有时候能看到同步异常堆栈因为 thenApply 默认与上游在同一线程执行异常可能直接抛出在调用线程中导致日志打印位置和异步线程不同。3.5 常用异常处理方法的对比CompletableFuture 提供三个异常处理相关方法语义有区别选择之前要先弄清楚。方法触发条件是否可以返回新结果后续 stage 是否执行exceptionally只有异常时触发可以返回兜底值会执行因为异常被恢复handle无论成功还是失败都触发可以根据结果或异常返回新值会执行whenComplete无论成功还是失败都触发返回值固定为之前的阶段结果或抛出异常取决于是否重新抛出异常在“异常后中断后续任务”的场景中最稳妥的做法有两个异常完全交给链条末尾的 exceptionally 处理中间不做任何恢复。中间需要记录上下文时使用 whenComplete 观察但不要吞掉异常即不要在 whenComplete 中返回正常值。推荐的中间观察写法CompletableFuture.supplyAsync(() - step1(), executor) .thenApplyAsync(v - step2(v), executor) .whenComplete((result, ex) - { if (ex ! null) { log.warn(workflow failed at step2: {}, ex.getMessage()); } }) .thenApplyAsync(v - step3(v), executor) .exceptionally(ex - { // 最终统一处理 return fallbackValue; });whenComplete 只观察、不恢复异常会继续向下传播因此 step3 仍然不会执行。4. 完整实战案例订单履约顺序工作流4.1 需求分析我们用订单履约场景来模拟真实工作流。业务流程如下校验订单验证订单号、用户信息、商品状态。扣减库存将订单中的商品数量从库存减去。计算金额根据商品单价、数量、优惠计算应付金额。发送通知发送下单成功通知给用户。四个步骤顺序执行任何一步失败后续步骤都不执行。最终返回一个包含状态和处理结果的对象。4.2 创建项目结构项目结构如下workflow-async-demo ├── pom.xml └── src/main/java/com/example/workflow ├── Order.java ├── OrderResult.java ├── WorkflowExecutor.java ├── OrderWorkflow.java └── WorkflowApp.java4.3 定义实体类Order 对象作为工作流的输入// 文件路径src/main/java/com/example/workflow/Order.java public class Order { private String orderId; private String userId; private String productId; private int quantity; private double price; public Order() { } public Order(String orderId, String userId, String productId, int quantity, double price) { this.orderId orderId; this.userId userId; this.productId productId; this.quantity quantity; this.price price; } // getter / setter 省略实际开发建议用 Lombok 或 IDE 自动生成 public String getOrderId() { return orderId; } public void setOrderId(String orderId) { this.orderId orderId; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId userId; } public String getProductId() { return productId; } public void setProductId(String productId) { this.productId productId; } public int getQuantity() { return quantity; } public void setQuantity(int quantity) { this.quantity quantity; } public double getPrice() { return price; } public void setPrice(double price) { this.price price; } }OrderResult 对象作为工作流的输出// 文件路径src/main/java/com/example/workflow/OrderResult.java public class OrderResult { private boolean success; private String message; private double amount; private String notifyContent; public OrderResult() { } public OrderResult(boolean success, String message, double amount, String notifyContent) { this.success success; this.message message; this.amount amount; this.notifyContent notifyContent; } public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success success; } public String getMessage() { return message; } public void setMessage(String message) { this.message message; } public double getAmount() { return amount; } public void setAmount(double amount) { this.amount amount; } public String getNotifyContent() { return notifyContent; } public void setNotifyContent(String notifyContent) { this.notifyContent notifyContent; } Override public String toString() { return OrderResult{ success success , message message \ , amount amount , notifyContent notifyContent \ }; } }4.4 定义线程池管理类工作流必须使用独立的线程池不能依赖默认公共线程池// 文件路径src/main/java/com/example/workflow/WorkflowExecutor.java import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; public class WorkflowExecutor { private static final int CORE_POOL_SIZE 4; private static final int MAX_POOL_SIZE 8; private static final long KEEP_ALIVE_TIME 60L; private WorkflowExecutor() { } public static ExecutorService createExecutor() { ThreadFactory threadFactory new ThreadFactory() { private final AtomicInteger counter new AtomicInteger(1); Override public Thread newThread(Runnable r) { Thread thread new Thread(r, workflow- counter.getAndIncrement()); thread.setDaemon(false); return thread; } }; return new ThreadPoolExecutor( CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, new LinkedBlockingQueue(1000), threadFactory, new ThreadPoolExecutor.CallerRunsPolicy() ); } }这里几个参数值得解释corePoolSize核心线程数设置为 4表示同时最多有 4 个核心线程处理任务。maximumPoolSize最大线程数设置为 8队列满后可以扩容到 8 个线程。LinkedBlockingQueue有界队列容量 1000避免任务无限堆积造成内存压力。CallerRunsPolicy拒绝策略当线程池和队列都满时由调用方线程执行任务起到天然限流作用同时不会丢弃任务。4.5 编写核心工作流这是整篇文章的核心代码。OrderWorkflow 通过 CompletableFuture 把四个业务步骤串成链式异步工作流// 文件路径src/main/java/com/example/workflow/OrderWorkflow.java import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; public class OrderWorkflow { private final ExecutorService executor; public OrderWorkflow(ExecutorService executor) { this.executor executor; } public CompletableFutureOrderResult process(Order order) { return CompletableFuture.supplyAsync(() - validate(order), executor) .thenApplyAsync(checkedOrder - deductStock(checkedOrder), executor) .thenApplyAsync(stockOrder - calcAmount(stockOrder), executor) .thenApplyAsync(amountOrder - sendNotify(amountOrder), executor) .exceptionally(ex - { // 注意异常在这里统一处理链条中间没有恢复逻辑 // 因此任一步骤异常后后续步骤全部跳过。 return new OrderResult(false, 工作流执行失败: ex.getMessage(), 0.0, null); }); } /** * 步骤 1校验订单 */ private Order validate(Order order) { log(开始校验订单: order.getOrderId()); if (order.getOrderId() null || order.getOrderId().isEmpty()) { throw new IllegalArgumentException(订单号不能为空); } if (order.getQuantity() 0) { throw new IllegalArgumentException(商品数量必须大于0); } log(订单校验通过); return order; } /** * 步骤 2扣减库存 */ private Order deductStock(Order order) { log(开始扣减库存: order.getProductId()); // 模拟库存不足场景 if (P10086.equals(order.getProductId())) { throw new IllegalStateException(商品库存不足); } log(库存扣减完成); return order; } /** * 步骤 3计算金额 */ private Order calcAmount(Order order) { log(开始计算金额); double amount order.getPrice() * order.getQuantity(); order.setPrice(amount); log(金额计算完成: amount); return order; } /** * 步骤 4发送通知 */ private Order sendNotify(Order order) { log(开始发送通知); // 模拟通知发送耗时 try { Thread.sleep(200); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IllegalStateException(通知发送被中断, e); } log(通知发送完成); return order; } private void log(String message) { System.out.println([ Thread.currentThread().getName() ] message); } }在这段代码里每一步都是纯同步业务方法但通过 CompletableFuture 包装后整体变成了异步执行。每一步的返回值都是 Order天然满足“上一步结果作为下一步输入”的要求。重点看一下异常处理的设计链条中间没有任何 exceptionally、handle 恢复逻辑。任意一步抛出运行时异常异常会穿透整条链后续 thenApplyAsync 全部跳过。最终由链条末尾的 exceptionally 统一捕获并返回失败结果。4.6 编写启动类// 文件路径src/main/java/com/example/workflow/WorkflowApp.java import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; public class WorkflowApp { public static void main(String[] args) throws Exception { ExecutorService executor WorkflowExecutor.createExecutor(); OrderWorkflow workflow new OrderWorkflow(executor); System.out.println(主线程开始提交任务: Thread.currentThread().getName()); // 正常场景 Order normalOrder new Order(A001, U001, P10001, 2, 99.0); CompletableFutureOrderResult normalFuture workflow.process(normalOrder); normalFuture.thenAccept(result - { System.out.println(正常订单结果: result); }); // 异常场景库存不足 Order stockOrder new Order(A002, U002, P10086, 1, 199.0); CompletableFutureOrderResult stockFuture workflow.process(stockOrder); stockFuture.thenAccept(result - { System.out.println(库存不足订单结果: result); }); // 等待所有任务完成避免主线程提前退出 CompletableFuture.allOf(normalFuture, stockFuture).get(10, TimeUnit.SECONDS); System.out.println(主线程执行完成准备关闭线程池); executor.shutdown(); } }4.7 运行结果与验证运行 WorkflowApp预期输出如下主线程开始提交任务: main [workflow-1] 开始校验订单: A001 [workflow-1] 订单校验通过 [workflow-2] 开始校验订单: A002 [workflow-2] 订单校验通过 [workflow-1] 开始扣减库存: P10001 [workflow-1] 库存扣减完成 [workflow-2] 开始扣减库存: P10086 [workflow-2] 库存扣减失败 [workflow-1] 开始计算金额 [workflow-1] 金额计算完成: 198.0 [workflow-1] 开始发送通知 [workflow-1] 通知发送完成 正常订单结果: OrderResult{successtrue, messagenull, amount198.0, notifyContentnull} 库存不足订单结果: OrderResult{successfalse, message工作流执行失败: 商品库存不足, amount0.0, notifyContentnull}注意观察两点库存不足的订单在“扣减库存”步骤抛出异常后日志中再也没有出现“计算金额”和“发送通知”的日志说明后续异步任务确实被中断了。正常订单和异常订单是并行执行的两个订单的任务分别跑在 workflow-1 和 workflow-2 两个线程上互不阻塞。这个案例完整地演示了顺序工作流、异步执行、异常后中断后续任务的三个核心诉求。5. 常见问题与排查思路5.1 异常后后续任务还是执行了问题现象常见原因解决思路某个步骤抛出异常但后面的 thenApply 依然执行链条中间存在 exceptionally/handle/whenComplete 恢复了异常检查链条上是否有恢复方法或确认恢复方法是否重新抛出了异常排查工具在链条每个阶段打印上游结果或异常.thenApplyAsync(v - { System.out.println(当前输入: v); return step(v); }, executor)如果异常已经被恢复成兜底值日志中会观察到后续阶段的输入变成了非预期值。5.2 exceptionally 放在链尾还是链中这是设计问题不是 bug。需要明确需求需求 A异常后立即中断后续任务最终统一处理异常。此时 exceptionally 放在链尾或者不放在链上外层调用时再处理。需求 B某个步骤失败后希望用兜底数据继续后续流程。此时可以在该步骤后放置 exceptionally。实际项目中大多数顺序工作流属于 A所以建议把异常处理放到链尾或者交给调用方。5.3 主线程提前退出导致任务消失问题现象常见原因解决思路控制台没有打印全部任务日志程序就退出了主线程在 CompletableFuture 完成前已经结束非守护线程池被强制回收在 main 方法中使用 get/join/allOf 等待任务完成或使用 CountDownLatch注意ForkJoinPool.commonPool() 中的线程是守护线程主线程退出后任务可能被直接终止所以明确传入自定义线程池并等待任务完成是更稳妥的做法。5.4 get 和 join 的区别方法受检异常返回结果get()抛 ExecutionException / InterruptedExceptionTjoin()不抛受检异常包装为 CompletionExceptionT在工作流链路内部推荐用 join()代码更简洁异常类型是运行时异常不影响链式写法。在边界等待处可以用 get(timeout, TimeUnit) 加上超时控制避免无限等待。5.5 线程池耗尽导致死锁如果工作流中嵌套使用了 CompletableFuture并且都使用同一个有界线程池那么外层任务占满线程后内层任务无法获取线程就会出现死锁。典型场景CompletableFuture.supplyAsync(() - { // 外层任务占住线程 return CompletableFuture.supplyAsync(() - { // 内层任务等待线程池空闲 return doInnerTask(); }, executor).join(); }, executor);当线程池所有线程都被外层任务占满时内层任务永远得不到执行。避免方案工作流嵌套时使用不同线程池。线程池最大线程数留有余量。避免在 CompletableFuture 回调中使用 join() 等待同一线程池中的另一个任务。6. 最佳实践与工程建议6.1 线程池独立配置隔离环境隔离不同业务的工作流不要共用一个线程池否则一个业务的高并发任务可能耗尽线程影响其他业务。建议按业务域创建独立线程池并设置合理的核心线程数、最大线程数、队列容量、拒绝策略和线程命名。线程命名非常重要一旦出现线程泄漏或死锁按照日志中的线程名可以快速定位到具体业务。6.2 异常处理策略顺序工作流的异常处理要遵循两个原则中间阶段只观察、不恢复或者显式抛出。边界处统一处理结果中携带失败原因和上下文。推荐在每个业务方法中记录业务日志但不要把业务日志和链路异常混在一起。异常对象建议包装为统一的业务异常携带步骤编码和错误码方便管理端监控和告警。6.3 超时控制CompletableFuture 本身支持 orTimeout 方法JDK 9在 JDK 8 中可以通过 get(timeout) 实现超时。工程上更推荐在提交任务时给工作流整体设置超时时间避免某个环节挂死导致整个工作流迟迟不结束。CompletableFutureOrderResult future workflow.process(order); OrderResult result future.get(3, TimeUnit.SECONDS);6.4 日志与链路追踪异步工作流最大的排查痛点在于多个任务跑在不同线程传统日志很难把多个步骤关联起来。解决方案是引入 TraceId工作流入口生成一个唯一 TraceId。每个业务方法输出日志时把 TraceId 和当前步骤号一起打印。配合 MDC 或自定义日志过滤器把 TraceId 放入线程变量。这样排查问题时只需要按 TraceId 搜索日志就能完整还原整个工作流的执行顺序和失败点。6.5 优雅关闭线程池系统关闭时线程池如果不优雅关闭可能导致正在执行的工作流任务被强制终止产生数据不一致。建议使用 shutdown 和 awaitTermination 组合executor.shutdown(); try { if (!executor.awaitTermination(10, TimeUnit.SECONDS)) { executor.shutdownNow(); } } catch (InterruptedException e) { executor.shutdownNow(); Thread.currentThread().interrupt(); }shutdown 会等待已提交任务执行完成shutdownNow 会尝试中断正在执行的任务。先等待再强杀是最稳妥的顺序。6.6 数据库操作必须考虑事务边界顺序工作流中如果包含数据库操作要特别小心事务边界。CompletableFuture 的每个 stage 可能运行在不同线程中Spring 的 Transactional 默认不能跨线程传播事务。正确做法是每个数据库操作自己管理本地事务。如果需要整体事务考虑使用本地消息表、事务消息或 Saga 模式来保证最终一致性。不要在异步链路的中间方法上直接依赖事务性注解传播到下一个 stage。顺序工作流的本质是业务步骤编排它不是分布式事务框架不要把多个数据库操作硬塞进一个跨线程的 CompletableFuture 链条中。7. 总结与学习路线到这里顺序工作流异步执行的核心内容已经完整梳理了一遍。可以从头回顾一下关键知识点顺序工作流是一系列有依赖关系的任务组合CompletableFuture 通过链式方法把多个异步任务串联起来在异常处理上异常会穿透未处理的 stage因此异常后中断后续任务的关键就是不要在中途恢复异常完整实战案例演示了订单校验、库存扣减、金额计算、通知发送的异步串联流程常见的坑包括异常被中间处理器吞掉、主线程提前退出、线程池耗尽死锁等。下一步可以继续深入的方向包括CompletableFuture 与 reactive 流如 Project Reactor的对比选型、分布式场景下的 Saga 编排、工作流引擎如 Flowable、Camunda的引入方式以及异步链路下的可观测性建设。如果是团队项目建议先把线程池规范、TraceId 透传和异常处理策略固化下来这些是异步工作流真正跑得稳的基础。最后提醒一句CompletableFuture 是工具不是银弹。它擅长解决单进程内的异步编排问题但一旦涉及跨服务、跨数据库的复杂业务还是要结合消息队列、分布式事务和状态机去设计。希望这篇文章能帮你在实际项目中少踩几个坑。
返回列表