AWS Lambda

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

本文档不包含有关如何开始使用 AWS Lambda 的详细信息,因此我们假设用户已熟悉 AWS 和 AWS Lambda,并希望了解 Spring 提供的额外价值。

入门

Spring Cloud Function 框架的目标之一是提供必要的基础设施元素,使一个简单的函数应用能够以特定方式在特定环境中进行交互。简单的函数应用(在 Spring 的上下文中)是一个包含 Supplier、Function 或 Consumer 类型的 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();
	}
}

它展示了一个完整的 Spring Boot 应用,其中定义了一个函数 Bean。有趣的是,从表面上看,这只是一个普通的 Boot 应用,但在 AWS 适配器的上下文中,它也是一个完全有效的 AWS Lambda 应用。不需要其他代码或配置。您只需将其打包并部署,因此让我们看看如何做到这一点。

为了简化操作,我们提供了一个可构建和部署的示例项目,您可以从 这里 访问它。

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

然后,您必须将 JAR 文件(通过 AWS 控制台或 AWS CLI)上传到 AWS。

当询问处理程序时,您指定 org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest,这是一个通用的请求处理程序。

AWS deploy

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

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

AWS 请求处理程序

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

如果您的应用有多个 @Bean 类型为 Function 等,则可以通过配置 spring.cloud.function.definition 属性或环境变量来选择要使用的 Bean。这些函数是从 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 Boot 的支持,只需将点替换为下划线,并将连字符替换为驼峰命名法即可。例如,spring.cloud.function.definition 变为 spring_cloud_function_definitionspring.cloud.function.routing-expression 变为 spring_cloud_function_routingExpression

自定义运行时

您还可以利用 AWS Lambda 的AWS Lambda 自定义运行时功能,Spring Cloud Function 提供了所有必要的组件,使其易于使用。

从代码的角度来看,应用程序应该与任何其他 Spring Cloud Function 应用程序没有区别。您唯一需要做的是在您的 zip/jar 文件的根目录中提供一个bootstrap脚本,该脚本运行 Spring Boot 应用程序,并在 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表示包含函数 Bean 的应用程序。

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

函数 Bean 定义样式也适用于自定义运行时,并且比@Bean样式更快。即使是 Java Lambda 的函数 Bean 实现,自定义运行时也可以启动得更快——这主要取决于您需要在运行时加载的类数量。Spring 在这里不做太多事情,因此您可以通过仅在函数中使用基本类型(例如)并且不在自定义@PostConstruct初始化程序中执行任何工作来减少冷启动时间。

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

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

部署容器镜像

自定义运行时还负责处理容器镜像部署。当以类似于此处所述的方式部署容器镜像时,务必记住使用函数名称设置并设置环境变量DEFAULT_HANDLER

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

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

此外,务必记住确保spring_cloud_function_web_export_enabled也设置为false。这是默认设置。

关于 JAR 布局的说明

您在 Lambda 运行时不需要 Spring Cloud Function Web 或 Stream 适配器,因此您可能需要在创建发送到 AWS 的 JAR 文件之前排除它们。Lambda 应用程序必须进行阴影处理,但 Spring Boot 独立应用程序不需要,因此您可以使用 2 个单独的 JAR 文件运行相同的应用程序(根据示例)。示例应用程序创建 2 个 JAR 文件,一个带有用于在 Lambda 中部署的aws分类器,另一个可执行(精简)JAR 文件,其中包含运行时的spring-cloud-function-web。Spring Cloud Function 将尝试从 JAR 文件清单中为您找到“主类”,使用Start-Class属性(如果使用启动程序父级,则 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>

构建文件设置

为了在 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>

关于 JAR 布局的说明中所述,您需要一个阴影 JAR 文件才能将其上传到 AWS Lambda。您可以使用Maven Shade 插件来实现。上面可以找到设置示例。

您可以使用 Spring Boot Maven 插件生成精简 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>

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

Gradle

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

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

关于 JAR 布局的说明中所述,您需要一个阴影 JAR 文件才能将其上传到 AWS Lambda。您可以使用Gradle Shadow 插件来实现

您可以使用 Spring Boot Gradle 插件和 Spring Boot Thin Gradle 插件生成精简 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()
}

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