以一个例子来帮助大家理解AWS Step Functions的典型使用场景并使用SST进行开发。以一个简单的压测Demo为例来进行演示:多个AWS Lambda在一起完成一个任务,首先 getApiInfo
读取压测用的一批参数,然后传递给callApiParallel
进行具体的压测请求任务,每个请求循环执行50次,全部的任务是并行执行,压测任务完成后进行结果的收集。
具体的如图所示:
前面几种主流的Serverless开发框架(以AWS Lambda为例)介绍了使用框架简化AWS Lambda的选型和优点。其中AWS SAM目前不能很好的支持AWS Step Function,serverless和SST支持的不错,都能非常轻松的帮我们完成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的开发
编写模拟代码
创建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控制台: