|
该版本仍在开发中,尚未被视为稳定。对于最新稳定版本,请使用 spring-cloud-function 5.0.0! |
AWS Lambda
AWS适配器会将Spring Cloud Function应用转换成可以在AWS Lambda中运行的表单。
一般来说,在AWS Lambda上运行Spring应用程序有两种方式:
-
通过 Spring Cloud Function 使用 AWS Lambda 适配器,实现如下所述的功能方法。这非常适合单一责任 API 以及基于事件和消息的系统,比如处理来自 Amazon SQS 或 Amazon MQ 队列的消息、Apache Kafka 流,或在 Amazon S3 中响应文件上传。
-
通过无服务器 Java 容器项目在 AWS Lambda 上运行 Spring Boot Web 应用。这非常适合将现有 Spring 应用迁移到 AWS Lambda,或者如果你构建拥有多个 API 端点的复杂 API,并希望保持熟悉的使用体验
休息控制器方法。这种方法在 Spring Boot Web 的无服务器 Java 容器中有更详细的描述。
以下指南期望你对AWS和AWS Lambda有基本了解,并重点介绍Spring带来的额外价值。关于如何开始使用 AWS Lambda 的细节不在本文档的讨论范围内。如果你想了解更多,可以浏览基础的AWS Lambda概念或完整的Java on AWS概述。
开始
Spring Cloud Function 框架的一个目标是提供必要的基础设施元素,使一个简单的功能应用能够兼容特定环境(如 AWS Lambda)。
在 Spring 的语境中,一个简单的函数式应用程序包含 的 beans提供商,功能或消费者.
让我们来看这个例子:
@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();
}
}
你可以看到一个完整的 Spring Boot 应用程序,里面定义了一个函数豆。表面上看,这只是另一个 Spring Boot 应用。然而,当将 Spring Cloud Function AWS 适配器添加到项目中时,它将成为一个完全有效的 AWS Lambda 应用程序:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-adapter-aws</artifactId>
</dependency>
</dependencies>
不需要其他代码或配置。我们提供了一个可供构建和部署的示例项目。你可以在官方的 Spring Cloud 函数示例仓库中访问它。
你只需要执行MVN 清洁封装生成JAR文件。所有必要的 Maven 插件都已经设置好,可以生成
一个合适的AWS可部署JAR文件。(你可以在JAR布局笔记中阅读更多关于JAR布局的详细信息。)
AWS Lambda 函数处理器
与传统Web应用通过在特定HTTP端口(80, 443)上通过监听器暴露功能不同,AWS Lambda函数是在预定义的入口点调用的,称为Lambda函数处理程序。
部署
构建应用后,您可以通过 AWS 控制台、AWS 命令行接口(CLI)或基础设施即代码(IaC)工具(如 AWS 服务器无应用模型 SAM)、AWS Cloud Development Kit(AWS CDK)、AWS CloudFormation 或 Terraform 手动部署 JAR 文件。
用 AWS 控制台创建 Hello World Lambda 函数
-
选择创建函数。
-
从零开始选择作者。
-
对于函数名称,输入
MySpringLambda函数. -
运行时选择 Java 21。
-
选择创建函数。
上传代码并测试函数:
-
比如上传之前创建的JAR文件
目标/function-sample-aws-0.0.1-SNAPSHOT-aws.jar. -
提供入口处理方法
org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest. -
进入“测试”标签,点击“测试”按钮。函数应返回提供的大写JSON负载。
如需使用基础设施即代码(IaC)工具实现自动化部署,请参阅官方AWS文档。
AWS 请求处理程序
如入门部分所述,AWS Lambda 函数在预定义的入口点调用,称为 Lambda 函数处理器。最简单的形式可以是 Java 方法引用。在上面的例子中,那是com.my.package.FunctionConfiguration::大写.该配置用于告知AWS Lambda在JAR中调用哪个Java方法。
当调用 Lambda 函数时,它会向该处理方法传递额外的请求有效载荷和上下文对象。请求有效载荷取决于触发该函数的AWS服务(Amazon API Gateway、Amazon S3、Amazon SQS、Apache Kafka等)。上下文对象提供了关于 Lambda 函数、调用和环境的额外信息,例如唯一的请求 ID(另见官方文档中的 Java 上下文)。
AWS 提供预定义的处理程序接口(称为请求处理员或请求流处理程序)通过 aws-lambda-java-events 和 aws-lambda-java-core 库处理有效载荷和上下文对象。
Spring Cloud 函数已经实现了这些接口,并提供了org.springframework.cloud.function.adapter.aws.FunctionInvoker完全抽象你的函数代码
基于AWS Lambda的具体情况。这样你就可以根据你运行的函数平台切换入口点。
不过,对于某些用例,你需要深度集成 AWS 环境。例如,当你的功能被亚马逊S3文件上传触发时,你可能想访问特定的亚马逊S3属性。或者,如果你想在处理亚马逊SQS队列中的商品时返回部分批次响应。那你仍然可以利用仿制药org.springframework.cloud.function.adapter.aws.FunctionInvoker但你会在函数代码中处理专用的AWS对象:
@Bean
public Function<S3Event, String> processS3Event() {}
@Bean
public Function<SQSEvent, SQSBatchResponse> processSQSEvent() {}
AWS 功能路由
Spring Cloud Function 的核心功能之一是路由。该功能允许您拥有一个特殊的 Java 方法(作为 Lambda 函数处理程序)来委派给其他内部方法。你已经见过这种做法,仿制药FunctionInvoker自动将请求路由到你的大写功能在入门部分。
默认情况下,如果你的应用有多个@Bean类型功能等等,它们是从春云中提取出来的功能目录框架会尝试按照搜索顺序寻找默认值,先搜索功能然后消费者最后提供商.这些默认路由功能是因为需要的FunctionInvoker无法确定要绑定哪个函数,因此内部默认为路由函数.建议通过多种机制提供额外的路由指令(详见示例)。
合适的路由机制取决于你将 Spring Cloud Function 项目作为单一或多个 Lambda 函数部署的偏好。
单一功能与多功能
比如,如果你在同一个 Spring Cloud Function 项目中实现多个 Java 方法,比如大写和小写你要么部署两个带有静态路由信息的 Lambda 函数,要么提供动态路由方法,决定运行时调用哪个方法。让我们来看看这两种方法。
-
如果每个函数对扩展、配置或权限要求不同,部署两个独立的AWS Lambda函数是合理的。例如,如果你创建两个 Java 方法。
readObjectFromAmazonS3和writeToAmazonDynamoDB在同一个 Spring Cloud 函数项目中,你可能想创建两个独立的 Lambda 函数。这是因为它们需要不同的权限才能与 S3 或 DynamoDB 通信,或者它们的加载模式和内存配置差异很大。一般来说,这种方法也推荐用于基于消息的应用,比如从流或队列读取数据,因为你根据 Lambda 事件源映射有专用配置。 -
当多个 Java 方法共享相同的权限集或提供一个连贯的业务功能时,单一 Lambda 函数是一种有效的方法。例如,基于CRUD的Spring Cloud Function项目,
createPet,更新宠物,阅读宠物和deletePet这些方法都与同一个DynamoDB表通信,并且使用模式相似。使用单一 Lambda 函数将提升部署的简便性、内聚性和代码的重用性(宠物实体).此外,它还能减少连续调用之间的冷启动,因为阅读宠物其次写宠物很可能会进入已经运行的 Lambda 执行环境。然而,当你构建更复杂的API,或者你想利用@RestController你也可以考虑一下Spring Boot Web的Serverless Java容器选项。
如果你更倾向于第一种方法,也可以创建两个独立的 Spring Cloud Function 项目并分别部署。如果不同团队负责维护和部署这些功能,这会非常有益。但在这种情况下,你需要处理它们之间共享诸如辅助方法或实体类等跨领域关注点的问题。一般来说,我们建议在功能性项目中应用与传统基于网页的应用相同的软件模块化原则。如需了解更多选择正确方法的信息,可以参考《无服务器微服务的设计方法比较》。
决定做出后,您可以利用以下路由机制。
多Lambda函数的路由
如果你决定将单个 Spring Cloud 函数项目(JAR)部署到多个 Lambda 函数上,你需要提示具体调用哪个方法,比如大写或小写.你可以用AWS Lambda环境变量来提供路由指令。
注意,AWS 不允许使用 Dot.和/或环境变量名称中的连字符。你可以利用Spring Boot支持,只需用带划线的点和用驼背的字母替换。比如说-spring.cloud.function.definition成为spring_cloud_function_definition和Spring.cloud.function.routing-expression成为spring_cloud_function_routingExpression.
因此,单个 Spring Cloud 项目的配置,部署两个方法到不同的 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();
}
@Bean
public Function<String, String> lowercase() {
return value -> value.toLowerCase();
}
}
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
MyUpperCaseLambda:
Type: AWS::Serverless::Function
Properties:
Handler: org.springframework.cloud.function.adapter.aws.FunctionInvoker
Runtime: java21
MemorySize: 512
CodeUri: target/function-sample-aws-0.0.1-SNAPSHOT-aws.jar
Environment:
Variables:
spring_cloud_function_definition: uppercase
MyLowerCaseLambda:
Type: AWS::Serverless::Function
Properties:
Handler: org.springframework.cloud.function.adapter.aws.FunctionInvoker
Runtime: java21
MemorySize: 512
CodeUri: target/function-sample-aws-0.0.1-SNAPSHOT-aws.jar
Environment:
Variables:
spring_cloud_function_definition: lowercase
你可能会问——为什么不直接用 Lambda 函数处理器,把 entry 方法指向大写和小写?在 Spring Cloud 函数项目中,建议使用内置的FunctionInvoker如 AWS 请求处理程序中所述。因此,我们通过环境变量提供路由定义。
性能考量
无服务器功能的核心特性是能够扩展到零并应对突发流量激增。为了处理请求,AWS Lambda 启动了新的执行环境。这些环境需要初始化,代码需要下载,然后启动一个JVM+你的应用程序。这也被称为冷启动。为了缩短冷启动时间,你可以依赖以下机制来优化性能。
-
利用AWS Lambda SnapStart,从预先初始化的快照启动你的Lambda函数。
-
通过AWS Lambda功率调优调整内存配置,找出性能与成本之间的最佳权衡。
-
遵循AWS SDK最佳实践,比如在处理代码之外定义SDK客户端,或利用更高级的启动技术。
-
实现额外的 Spring 机制以减少 Spring 启动和初始化时间,如功能性豆注册。
更多信息请参阅官方指南。
GraalVM 原生映像
Spring Cloud Function 为运行在 AWS Lambda 上的函数提供了 GraalVM 原生映像支持。由于 GraalVM 原生镜像无法在传统的 Java 虚拟机(JVM)上运行,你必须将原生的 Spring Cloud 函数部署到 AWS Lambda 自定义运行时。最显著的区别是你不再提供 JAR 文件,而是提供原生映像和带有起始指令的引导文件,捆绑在一个 zip 包中:
lambda-custom-runtime.zip
|-- bootstrap
|-- function-sample-aws-native
Bootstrap 文件:
#!/bin/sh
cd ${LAMBDA_TASK_ROOT:-.}
./function-sample-aws-native
自定义运行时
Lambda 专注于提供稳定的长期支持(LTS)Java 运行时版本。官方的 Lambda 运行时基于作系统、编程语言和软件库的组合构建,这些库需进行维护和安全更新。例如,Java 的 Lambda 运行时支持 LTS 版本,如 Java 17 Corretto 和 Java 21 Corretto。你可以在这里找到完整的名单。对于非LTS版本如Java 22、Java 23或Java 24,没有提供运行时。
为了使用其他语言版本、JVM或GraalVM原生镜像,Lambda允许你创建自定义运行时。自定义运行时允许你提供和配置自己的运行时来运行他们的应用代码。Spring Cloud Function 提供了所有必要的组件,让这一切变得简单。
从代码角度看,该应用不应与其他 Spring Cloud Function 应用有不同。
你唯一需要做的就是提供启动在运行 Spring Boot 应用的 ZIP/JAR 根节点中的脚本。
在 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
这com.example.LambdaApplication代表包含函数豆的应用。
在AWS中将处理程序名设置为函数名称。你也可以在这里使用函数复合(例如,大写|反写).
一旦你把ZIP/JAR文件上传到AWS,函数就会在自定义运行时运行。
我们还提供了一个示例项目,你还可以了解如何配置你的POM以正确生成ZIP文件。
函数式 bean 定义风格同样适用于自定义运行时,且
比@Bean风格。自定义运行时的启动速度甚至比功能性豆实现快得多
Java lambda 的 Lambda 主要取决于运行时需要加载多少类。
Spring在这里作用不大,所以你可以通过函数中只使用原类型来减少冷启动时间,比如,
而且不做任何定制工作@PostConstruct初始化器。
AWS函数路由与自定义运行时
使用自定义运行时函数时,路由的工作原理相同。你只需要具体说明函数路由就像你用函数名称作为handler一样,作为AWS Handler。
部署 Lambda 作为容器镜像的功能
与基于JAR或ZIP的部署不同,你也可以通过镜像注册表将Lambda函数作为容器镜像部署。更多详情请参阅官方AWS Lambda文档。
在部署容器镜像时,类似于此处描述的方式,这一点非常重要
记得设置环境变量DEFAULT_HANDLER并附上函数名称。
例如,对于函数豆,如下所示DEFAULT_HANDLER值为readMessageFromSQS.
@Bean
public Consumer<Message<SQSMessageEvent>> readMessageFromSQS() {
return incomingMessage -> {..}
}
此外,务必确保spring_cloud_function_web_export_enabled也设置为false.是的true默认。
JAR布局说明
在 Lambda 运行时你不需要 Spring Cloud Function Web 或 Stream 适配器,所以你可能会
在创建发送给AWS的JAR之前,需要排除这些。Lambda应用必须是
有着色,但Spring Boot的独立应用没有,所以你可以用两个版本运行同一个应用
分开罐子(根据
Samples)。示例应用创建了两个jar文件,其中一个包含AWS用于在 Lambda 中部署的分类器,以及一个可执行的(薄)jar,包含Spring-cloud-function-web在运行时。Spring Cloud 函数会尝试从JAR文件中帮你找到一个“主类”
显现,使用起始级属性(该属性将由Spring靴添加
如果你用起始父节点,就是工具)。如果没有起始级在你的清单里你可以
使用环境变量或系统属性MAIN_CLASS当你将该函数部署到 AWS 时。
如果你没有使用功能豆定义,而是依赖 Spring Boot 的自动配置,
并且不依赖于Spring靴启动父,
然后,在 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>3.4.2</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>
构建文件设置
为了在AWS Lambda上运行Spring Cloud Function应用,你可以利用Maven或Gradle 插件。
梅文
要使用 Maven 的适配器插件,请将插件依赖添加到你的pom.xml文件:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-adapter-aws</artifactId>
</dependency>
</dependencies>
正如JAR布局笔记中指出的,上传时需要一个带阴影的罐子 转给AWS Lambda。你可以用Maven Shade插件来实现这个功能。 设置示例可见上方。
你可以用 Spring Boot Maven 插件生成薄罐子。
<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>
你可以找到完整的样本pom.xml用于部署 Spring Cloud 函数的文件
这里是用 Maven 向 AWS Lambda 应用的应用。
格拉德勒
为了使用 Gradle 的适配器插件,请将依赖添加到你的build.gradle文件:
dependencies {
compile("org.springframework.cloud:spring-cloud-function-adapter-aws:${version}")
}
正如JAR布局笔记中指出的,上传时需要一个带阴影的罐子 转给AWS Lambda。你可以用Gradle Shadow插件来实现这个功能:
你可以使用 Spring Boot Gradle 插件和 Spring Boot Thin Gradle 插件来生成 那个薄罐子。
以下是完整的gradle文件
plugins {
id 'java'
id 'org.springframework.boot' version '3.4.2'
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', "2024.0.0")
}
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()
}
你可以找到完整的样本build.gradle用于部署 Spring Cloud 函数的文件
这里有应用程序可以下载到 AWS Lambda with Gradle。
Spring Boot Web 的无服务器 Java 容器
你可以使用 aws-serverless-java-container 库在 AWS Lambda 中运行 Spring Boot 3 应用。这非常适合将现有 Spring 应用迁移到 AWS Lambda,或者如果你构建拥有多个 API 端点的复杂 API,并希望保持熟悉的使用体验休息控制器方法。以下部分将对该过程进行高层次概述。更多信息请参阅官方示例代码。
-
将无服务器 Java 容器库导入到你现有的 Spring Boot 3 网页应用中
<dependency> <groupId>com.amazonaws.serverless</groupId> <artifactId>aws-serverless-java-container-springboot3</artifactId> <version>2.1.2</version> </dependency> -
使用内置的Lambda函数处理器作为入口点
com.amazonaws.serverless.proxy.spring.SpringDelegatingLambdaContainerHandler -
配置一个环境变量,名为
MAIN_CLASS让通用处理器知道在哪里找到你的原始应用主类。通常那是用@SpringBootApplication注释的类。
MAIN_CLAS = com.my.package.MySpringBootApplication
下面你可以看到一个部署配置示例:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
MySpringBootLambdaFunction:
Type: AWS::Serverless::Function
Properties:
Handler: com.amazonaws.serverless.proxy.spring.SpringDelegatingLambdaContainerHandler
Runtime: java21
MemorySize: 1024
CodeUri: target/lambda-spring-boot-app-0.0.1-SNAPSHOT.jar #Must be a shaded Jar
Environment:
Variables:
MAIN_CLASS: com.amazonaws.serverless.sample.springboot3.Application #Class annotated with @SpringBootApplication
请在这里找到包括 GraalVM native-image 在内的所有示例。