跳到主要内容

AWS Lambda

DeepSeek V3 中英对照 AWS Lambda

AWS 适配器将一个 Spring Cloud Function 应用转换为可以在 AWS Lambda 中运行的形式。

如何开始使用 AWS Lambda 的细节超出了本文档的范围,因此期望用户对 AWS 和 AWS Lambda 有一定的了解,并希望了解 Spring 提供的额外价值。

快速开始

Spring Cloud Function 框架的目标之一是提供必要的基础设施元素,以使一个简单的函数应用能够在特定环境中以某种方式交互。一个简单的函数应用(在 Spring 的上下文中)是一个包含 SupplierFunctionConsumer 类型 bean 的应用。因此,在 AWS 中,这意味着一个简单的函数 bean 应该以某种方式在 AWS Lambda 环境中被识别并执行。

让我们来看一个例子:

@SpringBootApplication
public class FunctionConfiguration {

public static void main(String[] args) {
SpringApplication.run(FunctionConfiguration.class, args);
}

@Bean
public Function<String, String> uppercase() {
return value -> value.toUpperCase();
}
}
java

它展示了一个完整的 Spring Boot 应用程序,其中定义了一个函数 bean。有趣的是,表面上这只是一个普通的 Spring Boot 应用程序,但在 AWS Adapter 的上下文中,它也是一个完全有效的 AWS Lambda 应用程序。不需要其他代码或配置。你只需要将其打包并部署即可,接下来我们来看看如何做到这一点。

为了简化操作,我们提供了一个现成的示例项目,您可以在此访问。该项目已经准备好进行构建和部署。

你只需执行 ./mvnw clean package 即可生成 JAR 文件。所有必要的 Maven 插件已经配置好,用于生成适用于 AWS 的可部署 JAR 文件。(你可以在 JAR 布局说明 中阅读更多关于 JAR 布局的详细信息)。

然后你需要将 JAR 文件上传到 AWS(通过 AWS 控制台或 AWS CLI)。

当被问及 handler 时,你指定了 org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest,这是一个通用的请求处理器。

AWS 部署

这就是全部内容。保存并使用一些示例数据执行该函数,对于此函数,预期输入是一个字符串,函数将将其转换为大写并返回。

虽然 org.springframework.cloud.function.adapter.aws.FunctionInvoker 是一个通用的 AWS RequestHandler 实现,旨在完全隔离你与 AWS Lambda API 的具体细节,但在某些情况下,你可能希望指定要使用的特定 AWS RequestHandler。下一节将解释如何实现这一点。

AWS 请求处理程序

虽然 AWS Lambda 允许你实现各种 RequestHandlers,但在使用 Spring Cloud Function 时,你不需要实现任何 RequestHandler,而是使用提供的 org.springframework.cloud.function.adapter.aws.FunctionInvoker,它是 AWS 的 RequestStreamHandler 的实现。用户在部署函数时,只需在 AWS 控制台上将其指定为 handler 即可,无需做其他任何事情。它将处理大多数情况,包括 Kinesis、流处理等。

如果你的应用程序有多个类型为 Function 等的 @Bean,那么你可以通过配置 spring.cloud.function.definition 属性或环境变量来选择要使用的函数。这些函数是从 Spring Cloud 的 FunctionCatalog 中提取的。如果你没有指定 spring.cloud.function.definition,框架将尝试按照搜索顺序找到一个默认函数,首先搜索 Function,然后是 Consumer,最后是 Supplier

类型转换

Spring Cloud Function 将尝试透明地处理原始输入流与函数声明类型之间的类型转换。

例如,如果你的函数签名是 Function<Foo, Bar>,我们将尝试将传入的流事件转换为 Foo 的实例。

在事件类型未知或无法确定的情况下(例如,Function<?, ?>),我们将尝试将传入的流事件转换为通用的 Map

原始输入

有时候你可能希望访问原始输入。在这种情况下,你只需要声明你的函数签名以接受 InputStream。例如,Function<InputStream, ?>。在这种情况下,我们不会尝试任何转换,而是直接将原始输入传递给函数。

AWS 函数路由

Spring Cloud Function 的核心特性之一是 路由 —— 能够通过一个特殊函数根据用户提供的路由指令将请求委托给其他函数。

在 AWS Lambda 环境中,此功能提供了额外的优势,因为它允许你将单个函数(路由函数)绑定为 AWS Lambda,从而为 API Gateway 提供一个单一的 HTTP 端点。因此,最终你只需管理一个函数和一个端点,同时受益于可以作为应用程序一部分的多个函数。

更多详情可在提供的示例中找到,但仍有一些一般性内容值得提及。

当你的应用程序中有多个函数时,路由功能将默认启用,因为 org.springframework.cloud.function.adapter.aws.FunctionInvoker 无法确定要将哪个函数绑定为 AWS Lambda,因此它默认使用 RoutingFunction。这意味着你只需要提供路由指令即可,你可以通过多种机制来实现(更多详情请参见示例)。

另外,请注意,由于 AWS 不允许在环境变量名称中使用点 . 和/或连字符 -,你可以利用启动支持,简单地将点替换为下划线,并将连字符替换为驼峰命名法。例如,spring.cloud.function.definition 变为 spring_cloud_function_definition,而 spring.cloud.function.routing-expression 变为 spring_cloud_function_routingExpression

自定义运行时

你也可以利用 AWS Lambda 自定义运行时 这一 AWS Lambda 功能,而 Spring Cloud Function 提供了所有必要的组件,使其变得简单易用。

从代码的角度来看,应用程序应该与任何其他 Spring Cloud Function 应用程序没有区别。你唯一需要做的是在 zip/jar 的根目录下提供一个运行 Spring Boot 应用程序的 bootstrap 脚本,并在 AWS 中创建函数时选择“自定义运行时”。以下是一个 bootstrap 文件的示例:

#!/bin/sh

cd ${LAMBDA_TASK_ROOT:-.}

java -Dspring.main.web-application-type=none -Dspring.jmx.enabled=false \
-noverify -XX:TieredStopAtLevel=1 -Xss256K -XX:MaxMetaspaceSize=128M \
-Djava.security.egd=file:/dev/./urandom \
-cp .:`echo lib/*.jar | tr ' ' :` com.example.LambdaApplication
text

com.example.LambdaApplication 代表你的应用程序,其中包含函数 bean。

在 AWS 中将处理程序名称设置为您的函数名称。您也可以在这里使用函数组合(例如,uppercase|reverse)。差不多就是这样。一旦您将 zip/jar 上传到 AWS,您的函数将在自定义运行时中运行。我们提供了一个示例项目,您还可以在其中查看如何配置您的 POM 以正确生成 zip 文件。

功能式 bean 定义风格同样适用于自定义运行时,并且比 @Bean 风格更快。自定义运行时甚至可以比 Java lambda 的功能式 bean 实现启动得更快——这主要取决于你在运行时需要加载的类的数量。Spring 在这里并没有做太多工作,因此你可以通过在函数中仅使用基本类型来减少冷启动时间,例如,不在自定义的 @PostConstruct 初始化器中执行任何操作。

AWS 自定义运行时的函数路由

在使用自定义运行时时,函数路由的工作方式相同。你只需要像使用函数名称作为处理程序一样,将 functionRouter 指定为 AWS 处理程序即可。

部署容器镜像

Custom Runtime 还负责处理容器镜像的部署。当以类似于此处描述的方式部署容器镜像时,务必记住设置一个环境变量 DEFAULT_HANDLER,并将其值设置为函数的名称。

例如,对于下面显示的函数 bean,DEFAULT_HANDLER 的值将是 readMessageFromSQS

@Bean
public Consumer<Message<SQSMessageEvent>> readMessageFromSQS() {
return incomingMessage -> {..}
}
java

此外,重要的是要记住确保 spring_cloud_function_web_export_enabled 也设置为 false。默认情况下它是关闭的。

关于 JAR 布局的注意事项

在 Lambda 运行时,你不需要 Spring Cloud Function Web 或 Stream 适配器,因此在创建发送到 AWS 的 JAR 文件之前,你可能需要排除这些依赖。Lambda 应用程序需要进行 shading 处理,而 Spring Boot 独立应用程序则不需要,因此你可以使用两个独立的 JAR 文件运行相同的应用程序(如示例所示)。示例应用程序会创建两个 JAR 文件,其中一个带有 aws 分类器,用于在 Lambda 中部署,另一个则用于其他场景。

这是一个包含 spring-cloud-function-web 的可执行(精简)JAR 文件。Spring Cloud Function 会尝试从 JAR 文件的清单中为你定位一个“主类”,使用 Start-Class 属性(如果你使用 Spring Boot 的 starter parent,这个属性将由 Spring Boot 工具自动添加)。如果你的清单中没有 Start-Class,你可以在将函数部署到 AWS 时使用环境变量或系统属性 MAIN_CLASS

如果您没有使用功能性的 bean 定义,而是依赖于 Spring Boot 的自动配置,并且不依赖 spring-boot-starter-parent,那么必须在 maven-shade-plugin 的执行过程中配置额外的转换器。

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.4</version>
</dependency>
</dependencies>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>aws</shadedClassifierName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlers</resource>
</transformer>
<transformer implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">
<resource>META-INF/spring.factories</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemas</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.components</resource>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
xml

构建文件设置

为了在 AWS Lambda 上运行 Spring Cloud Function 应用程序,你可以利用云平台提供商提供的 Maven 或 Gradle 插件。

Maven

为了使用 Maven 的适配器插件,请将插件依赖项添加到您的 pom.xml 文件中:

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-adapter-aws</artifactId>
</dependency>
</dependencies>
xml

正如 JAR 布局注意事项 中提到的,你需要一个 shaded jar 才能将其上传到 AWS Lambda。你可以使用 Maven Shade Plugin 来实现这一点。上面的 设置 示例可以参考。

你可以使用 Spring Boot Maven 插件来生成 thin jar

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot.experimental</groupId>
<artifactId>spring-boot-thin-layout</artifactId>
<version>${wrapper.version}</version>
</dependency>
</dependencies>
</plugin>
xml

你可以在这里找到完整的 pom.xml 文件示例,用于将 Spring Cloud Function 应用程序部署到 AWS Lambda 使用 Maven:此处

Gradle

为了使用 Gradle 的适配器插件,请将依赖项添加到你的 build.gradle 文件中:

dependencies {
compile("org.springframework.cloud:spring-cloud-function-adapter-aws:${version}")
}
groovy

正如在 关于 JAR 布局的注意事项 中指出的那样,你需要一个 shaded jar 才能将其上传到 AWS Lambda。为此,你可以使用 Gradle Shadow Plugin

你可以使用 Spring Boot Gradle 插件和 Spring Boot Thin Gradle 插件来生成 thin jar

以下是一个完整的 Gradle 文件

plugins {
id 'java'
id 'org.springframework.boot' version '3.2.0-M2'
id 'io.spring.dependency-management' version '1.1.3'
id 'com.github.johnrengelman.shadow' version '8.1.1'
id 'maven-publish'
id 'org.springframework.boot.experimental.thin-launcher' version "1.0.31.RELEASE"
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
sourceCompatibility = '17'
}

repositories {
mavenCentral()
mavenLocal()
maven { url 'https://repo.spring.io/milestone' }
}

ext {
set('springCloudVersion', "2023.0.0-M1")
}

assemble.dependsOn = [thinJar, shadowJar]

publishing {
publications {
maven(MavenPublication) {
from components.java
versionMapping {
usage('java-api') {
fromResolutionOf('runtimeClasspath')
}
usage('java-runtime') {
fromResolutionResult()
}
}
}
}
}

shadowJar.mustRunAfter thinJar

import com.github.jengelman.gradle.plugins.shadow.transformers.*

shadowJar {
archiveClassifier = 'aws'
manifest {
inheritFrom(project.tasks.thinJar.manifest)
}
// Required for Spring
mergeServiceFiles()
append 'META-INF/spring.handlers'
append 'META-INF/spring.schemas'
append 'META-INF/spring.tooling'
append 'META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports'
append 'META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports'
transform(PropertiesFileTransformer) {
paths = ['META-INF/spring.factories']
mergeStrategy = "append"
}
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.cloud:spring-cloud-function-adapter-aws'
implementation 'org.springframework.cloud:spring-cloud-function-context'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}

tasks.named('test') {
useJUnitPlatform()
}
groovy

您可以在此处找到用于将 Spring Cloud Function 应用程序部署到 AWS Lambda 的完整示例 build.gradle 文件:这里