我用 Lambda Durable Functions 把五个 Lambda 缩成了一个,代码量砍半

Lambda 15 分钟超时,搞不了长流程------这事儿我相信不少人都遇到过。

之前接了个需求:订单处理流水线,要过库存校验、支付扣款、等仓库拣货(两小时起步)、发物流。我一开始用 SQS + DynamoDB + 五个 Lambda 拼了一套状态机。能跑,但五个函数之间传状态、处理异常、保证幂等......折腾半天,光胶水代码就写了几百行。

现在亚马逊云科技出了 Lambda Durable Functions,我重写了一版------一个函数搞定,代码量直接砍半。这篇文章分享下实战过程和踩的坑。

啥是 Durable Functions

说白了,就是让 Lambda 支持断点续跑

核心机制叫检查点(Checkpoint)。你把业务逻辑拆成一个个 step,每个 step 执行完 SDK 自动存结果。如果中间函数被打断(超时、OOM、平台回收),下次唤醒时 SDK 会"回放"------已经完成的 step 直接返回存好的结果,没完成的接着跑。

几个亮点:

  • ctx.wait() 可以挂起函数等几小时甚至几天,不产生计算费用
  • 内置重试,支持指数退避 + 随机抖动
  • ctx.map() 并发处理集合,单个元素失败不影响其他
  • ctx.createCallback() 等外部事件,比如人工审批
  • 函数可以跑到一年

SDK 是 Apache 2.0 开源的:github.com/aws/aws-dur...

实战:订单处理工作流

Maven 依赖

xml 复制代码
<dependency>
    <groupId>software.amazon.lambda.durable</groupId>
    <artifactId>aws-durable-execution-sdk-java</artifactId>
    <version>VERSION</version>
</dependency>

Java 17+,这是硬性要求。

核心代码

java 复制代码
public class OrderProcessor extends DurableHandler<Order, OrderResult> {

    private final InventoryService inventoryService = new InventoryService();
    private final PaymentService paymentService = new PaymentService();
    private final ShippingService shippingService = new ShippingService();

    @Override
    protected OrderResult handleRequest(Order order, DurableContext ctx) {
        // 扣库存
        var reservation = ctx.step("reserve-inventory", Reservation.class,
            stepCtx -> inventoryService.reserve(order.getItems()));

        // 扣款(带重试 + 至多一次语义)
        var payment = ctx.step("process-payment", Payment.class,
            stepCtx -> paymentService.charge(
                order.getPaymentMethod(), order.getTotal()),
            StepConfig.builder()
                .retryStrategy(RetryStrategies.exponentialBackoff(
                    3, Duration.ofSeconds(2),
                    Duration.ofSeconds(15), 2.0,
                    JitterStrategy.FULL))
                .semantics(StepSemantics.AT_MOST_ONCE_PER_RETRY)
                .build());

        // 等仓库处理 2 小时(零成本挂起)
        ctx.wait("wait-for-warehouse", Duration.ofHours(2));

        // 发货
        var shipment = ctx.step("confirm-shipment", Shipment.class,
            stepCtx -> shippingService.ship(
                reservation, order.getAddress()));

        return new OrderResult(order.getId(), shipment.getTrackingNumber());
    }
}

这代码看着就是普通的顺序执行逻辑,但背后 SDK 帮你做了检查点、回放、重试、挂起恢复。跟之前五个 Lambda + SQS 的方案比,维护成本不在一个量级。

踩坑记录

坑 1:扣款被执行了两次

这个坑比较隐蔽。默认的执行语义是 AT_LEAST_ONCE_PER_RETRY------如果 step 执行成功了但检查点没存住(运行环境突然被回收),回放时会再执行一次。

扣款这种操作绝对不能重复。解决方案:

java 复制代码
var payment = ctx.step("charge-payment", Payment.class,
    stepCtx -> paymentService.charge(amount),
    StepConfig.builder()
        .semantics(StepSemantics.AT_MOST_ONCE_PER_RETRY)
        .retryStrategy(RetryStrategies.Presets.NO_RETRY)
        .build());

AT_MOST_ONCE_PER_RETRY 会在执行前先记录一个检查点。如果执行完但结果没存上,回放时不会重新执行,而是抛 StepInterruptedException。你需要自己查外部状态来处理。

坑 2:泛型返回值反序列化炸了

我有一个 step 返回 List<Order>,直接写 List.class

java 复制代码
// ❌ 这样不行,回放时反序列化会出错
var orders = ctx.step("fetch-orders", List.class,
    stepCtx -> orderService.getOrders(userId));

Java 类型擦除的老问题。要用 TypeToken

java 复制代码
// ✅ 正确写法
var orders = ctx.step("fetch-orders", new TypeToken<List<Order>>() {},
    stepCtx -> orderService.getOrders(userId));

坑 3:改了 step 顺序导致回放失败

有一次我重构代码,把两个 step 的顺序调了一下。结果线上正在运行的 durable function 回放时直接报 NonDeterministicExecutionException

原因:回放是从头开始跑的,它按顺序匹配 step 名称。你改了顺序,它就对不上了。

教训:对已经在运行的 durable function,不要改 step 的名称和顺序。 要改就等所有运行中的实例跑完再改。

坑 4:map 用了 HashSet 导致回放不一致

java 复制代码
// ❌ HashSet 迭代顺序不确定
var items = new HashSet<>(Arrays.asList("a", "b", "c"));
ctx.map("process", items, String.class, fn);
// 抛 IllegalArgumentException

ctx.map() 要求输入集合必须有确定的迭代顺序。用 List 就行:

java 复制代码
// ✅ List 有确定顺序
var items = List.of("a", "b", "c");
ctx.map("process", items, String.class, fn);

批量处理:ctx.map() 实战

真实场景里经常要批量处理数据。比如批量发送通知:

java 复制代码
var userIds = List.of("user-1", "user-2", "user-3",
    "user-4", "user-5");

var result = ctx.map("send-notifications", userIds, 
    NotifyResult.class,
    (userId, index, childCtx) -> {
        return childCtx.step("notify-" + index, 
            NotifyResult.class,
            stepCtx -> notificationService.send(userId));
    },
    MapConfig.builder()
        .maxConcurrency(3)
        .completionConfig(
            CompletionConfig.toleratedFailureCount(2))
        .build());

System.out.println("成功: " + result.succeeded().size());
System.out.println("失败: " + result.failed().size());

toleratedFailureCount(2) 表示最多容忍 2 个失败。超过 2 个就停止。每个元素跑在隔离的子上下文里,一个炸了不影响其他。

等外部事件:审批场景

大额订单需要主管审批:

java 复制代码
DurableCallbackFuture<String> callback = ctx.createCallback(
    "manager-approval", String.class,
    CallbackConfig.builder()
        .timeout(Duration.ofHours(24))
        .build());

ctx.step("request-approval", String.class,
    stepCtx -> {
        approvalService.requestApproval(
            callback.callbackId(), orderDetails);
        return "requested";
    });

try {
    String decision = callback.get();
    if ("rejected".equals(decision)) {
        // 走拒绝逻辑
    }
} catch (CallbackTimeoutException e) {
    // 24 小时没审批,自动取涊
}

函数在 callback.get() 处挂起,不占计算资源。审批系统通过 API 把结果发回来,函数继续跑。

异步并行执行

多个互不依赖的操作可以并行:

java 复制代码
DurableFuture<User> userFuture = ctx.stepAsync(
    "fetch-user", User.class,
    stepCtx -> userService.getUser(userId));

DurableFuture<List<Order>> ordersFuture = ctx.stepAsync(
    "fetch-orders", new TypeToken<List<Order>>() {},
    stepCtx -> orderService.getOrders(userId));

// 并行执行,这里等结果
User user = userFuture.get();
List<Order> orders = ordersFuture.get();

和之前方案的对比

SQS + 多 Lambda 状态机 Lambda Durable Functions
代码分布 5-6 个函数 + 胶水代码 1 个函数,顺序写
状态管理 自己写 DynamoDB 读写 SDK 自动检查点
长时间等待 SQS 延迟消息(上限 15 分钟) ctx.wait() 挂起,等多久都行
重试逻辑 自己实现 内置指数退避
错误处理 每个函数单独处理 统一 try-catch

参考资源


如果你也在用多个 Lambda 拼状态机,建议试试 Durable Functions。开发体验好太多了。

相关推荐
Java面试题总结12 小时前
证书 47 天就过期,还在手动续?聊聊我在 AWS 上的自动化方案
自动化·云计算·aws
yyuuuzz16 小时前
aws注册过程中的常见问题梳理
运维·服务器·网络·云计算·github·aws
yyuuuzz2 天前
aws注册过程中的常见注意事项
云计算·aws
yyuuuzz3 天前
aws 基础认知与实践注意点
运维·服务器·网络·云计算·github·aws
亚马逊云开发者3 天前
Aurora PG 14 快 EOL 了,我用蓝绿部署 52 秒切到了 PG 16,聊聊全过程
aws
亚马逊云开发者3 天前
证书 47 天就过期,还在手动续?聊聊我在 AWS 上的自动化方案
aws
云天AI实战派5 天前
Agentic AI 全流程实战:用 OpenAI on AWS 搭一个餐饮补货智能体,从 API 调用到容器化上线
人工智能·云计算·aws
yunson_Liu6 天前
aws EKS集群pvc存储扩容
k8s·aws
卷卷说风控6 天前
【卷卷观察】OpenAI扑进AWS怀里:AI平台战争进入贴身肉搏期
人工智能·云计算·aws
SNOWPIAOP8 天前
Claude Code + CCR + AWS Bedrock 踩坑复盘:上下文超限、模型路由、Mantle 端点与 Qwen3 Coder Next
云计算·claude·aws·上下文·ccr