使用Step Function编排AWS Lambda[AWS Lambda教程-SST系列]

以一个例子来帮助大家理解AWS Step Functions的典型使用场景并使用SST进行开发。以一个简单的压测Demo为例来进行演示:多个AWS Lambda在一起完成一个任务,首先 getApiInfo读取压测用的一批参数,然后传递给callApiParallel进行具体的压测请求任务,每个请求循环执行50次,全部的任务是并行执行,压测任务完成后进行结果的收集。

具体的如图所示:

前面几种主流的Serverless开发框架(以AWS Lambda为例)介绍了使用框架简化AWS Lambda的选型和优点。其中AWS SAM目前不能很好的支持AWS Step Function,serverlessSST支持的不错,都能非常轻松的帮我们完成Step Function的开发。

AWS Step Functions简介

AWS Step Functions是一项无服务器编排服务,它可以方便的将多个AWS Lambda的连接在一起,集中起来编排为一个整体,最后组成一个统一的业务服务。Step Functions基于状态机和任务,在Step Functions中,工作流被称为状态机,它是一系列事件驱动的步骤,而任务状态代表其他AWS服务(例如AWS Lambda)执行的工作单元。我们知道AWS Lambda是一种serverless计算服务,它基于实践驱动理念设计,适用于需要快速纵向扩展并在不需要时缩减至零的应用程序场景,基本全部的外部事件都可以和Lambda无缝都集成并触发Lambda的执行,比如无状态的HTTPS请求、IoT设备产生的行为事件,文件处理(图片处理或者转码)等。

为什么有时要用AWS Step Functions

我们的架构一般都是要设计为SOLID (面向对象设计),单个Lambda函数虽然可以处理多个不同的逻辑但是最好还是符合单一职责原则开闭原则,也就是一个Lambda函数专门完成一个或者一类功能,多个lambda函数共同组成全部功能的整体。这样无论哪个功能的TPS有变化,对应的Lamdba函数都可以独立的水平扩展而不影响其他的lambda函数,避免了资源的浪费、避免因为软件质量问题扩散到整个服务群中造成的更大范围的影响。

但是对一个业务系统而言,多个Lambda的协调管理的难度和SOLID是一个很难调和的问题,lambda拆分的越细(最细一个api或者功能一个lambda)那么越符合SOLID但是管理却越难,一般拆分的粒度需要自己把握,取得一个平衡。为了简化大量Lambda间关系管理和协调的的工作,AWS Step Functions给我们提供一个非常好的选择,它帮助我们轻松的完成了多Lambda的管理。

使用SST简化Step Function和AWS Lambda的开发

代码样例在Github serverless-way

编写模拟代码

创建getApiInfo.ts作为Step Function的第一个任务,用于指示Step Function开始运行

javascript 复制代码
import {APIGatewayProxyHandlerV2} from "aws-lambda";
import {SFNClient, StartExecutionCommand} from "@aws-sdk/client-sfn";

export const handler: APIGatewayProxyHandlerV2 = async (event) => {
    const client = new SFNClient({}); //Create the Step Function client

    // Send a command to this client to start the state machine which ARN is specified
    await client.send(
        new StartExecutionCommand({
            stateMachineArn: process.env.STATE_MACHINE,
        })
    );
    return {
        statusCode: 200,
        body: "Start machine started",
    };
};

创建getApiInfo.ts作为Step Function的第二个任务,用于模拟获取压测任务的参数

typescript 复制代码
import {APIGatewayProxyHandlerV2, Handler} from "aws-lambda";
import * as console from "console";

export const handler: Handler = async (event: APIGatewayProxyHandlerV2) => {
    console.log("getApiInfo started.")

    return {
        "callApis": [
            {"A": "A1"},
            {"B": "B1"},
            {"C": "C1"},
            {"D": "D1"},
            {"E": "E1"},
        ]
    };
};

创建callApiParallel.ts作为Step Function的第三个任务,用于模拟执行压测任务

typescript 复制代码
import {Handler} from "aws-lambda";
import * as console from "console";

export const handler: Handler = async (event) => {
    console.log("callApiParallel started.")
    return {
        statusCode: 200,
        body: "callApiParallel executed.",
    };
};

创建statisticCalledResults.ts作为Step Function的第四个任务,用于模拟执行压测任务完成后的数据统计

typescript 复制代码
import {Handler} from "aws-lambda";
import * as console from "console";

export const handler: Handler = async (event) => {
    console.log("statisticCalledResults started.")
    return {
        statusCode: 200,
        body: "statisticCalledResults executed.",
    };
};

SST管理Step Function编排逻辑

创建MyStack.ts来管理Step Function编排逻辑

javascript 复制代码
import {Api, Function, StackContext} from "sst/constructs";
import {LambdaInvoke} from "aws-cdk-lib/aws-stepfunctions-tasks";
import {Chain, JsonPath, Map, StateMachine} from "aws-cdk-lib/aws-stepfunctions";

export function MyStack({stack}: StackContext) {
    // Step Function的第二个任务,用于模拟获取压测任务的参数
    const getApiInfo = new LambdaInvoke(stack, "getApiInfo", {
        lambdaFunction: new Function(stack, "getApiInfo-func", {
            handler: "packages/functions/src/getApiInfo.handler",
        }),
    });

    // Step Function的第三个任务,用于模拟执行压测任务
    const callApiParallel = new LambdaInvoke(stack, "callApiParallel", {
        lambdaFunction: new Function(stack, "callApiParallel-func", {
            handler: "packages/functions/src/callApiParallel.handler",
        }),
    });

    // Step Function的第四个任务,用于模拟执行压测任务完成后的数据统计
    const statisticCalledResults = new LambdaInvoke(stack, "statisticCalledResults", {
        lambdaFunction: new Function(stack, "statisticCalledResults-func", {
            handler: "packages/functions/src/statisticCalledResults.handler",
        }),
    });

    // Step Function管理,创建一个Map状态机并行执行的任务,最大并发50
    const map = new Map(stack, "MapCompute", {
        maxConcurrency: 50,
        inputPath: JsonPath.stringAt('$.Payload'),
        itemsPath: JsonPath.stringAt('$.callApis')
    });

    // 创建一个step function的state链,串联前面的map任务和最后的统计任务
    const stateDefinition = Chain.start(getApiInfo)
        .next(map.iterator(callApiParallel))
        .next(statisticCalledResults);

    // 用stateDefinition定义Step function的逻辑
    const stateMachine = new StateMachine(stack, "ApiParallelCallerStepMachine", {
        definition: stateDefinition,
    });

    // Step Function的第一个任务,用于指示Step Function开始运行,当用户访问/start-machine时触发整个step function的启动
    const api = new Api(stack, "apiStartMachine", {
        routes: {
            "GET /start-machine": {
                function: {
                    handler: "packages/functions/src/startMachine.handler",
                    environment: {
                        STATE_MACHINE: stateMachine.stateMachineArn,
                    },
                },
            },
        },
    });

    // To grant the permission to our route to start our state machine
    api.attachPermissionsToRoute("GET /start-machine", [
        [stateMachine, "grantStartExecution"],
    ]);

    //To show the API endpoint in the output
    stack.addOutputs({
        ApiEndpoint: api.url,
    });
}

整体的结构如图

部署

bash 复制代码
# 安装sst框架组件
npm install sst --save-exact
# 部署
npx sst deploy
# 实验完成后移除
npx sst remove

当我们deploy的时候,SST会自动进行编译、打包、资源创建、部署工作 部署完成后会给我们本次Demo项目的可访问链接,方便我们调用它来进行PoC论证 当我们访问这个链接的/start-machine路径时就会触发Step Function的运行,我们可以在AWS Lamdba控制台或者Step Function自己的控制台上看到我们创建的Step Function,并实时的观察到它所处的状态。比如我下面的的图时运行完成的样子。

AWS Lamdba控制台: Step Function控制台:

参考

相关推荐
刘大辉在路上3 小时前
突发!!!GitLab停止为中国大陆、港澳地区提供服务,60天内需迁移账号否则将被删除
git·后端·gitlab·版本管理·源代码管理
追逐时光者5 小时前
免费、简单、直观的数据库设计工具和 SQL 生成器
后端·mysql
初晴~6 小时前
【Redis分布式锁】高并发场景下秒杀业务的实现思路(集群模式)
java·数据库·redis·分布式·后端·spring·
盖世英雄酱581366 小时前
InnoDB 的页分裂和页合并
数据库·后端
小_太_阳6 小时前
Scala_【2】变量和数据类型
开发语言·后端·scala·intellij-idea
直裾6 小时前
scala借阅图书保存记录(三)
开发语言·后端·scala
星就前端叭7 小时前
【开源】一款基于Vue3 + WebRTC + Node + SRS + FFmpeg搭建的直播间项目
前端·后端·开源·webrtc
小林coding8 小时前
阿里云 Java 后端一面,什么难度?
java·后端·mysql·spring·阿里云
AI理性派思考者8 小时前
【保姆教程】手把手教你在Linux系统搭建早期alpha项目cysic的验证者&证明者
后端·github·gpu