AWS Lambda

AWS 适配器采用 Spring Cloud Function 应用,并将其转换为可以在 AWS Lambda 中运行的形式。 如何开始使用 AWS Lambda 的详细信息不在本文档的范围内,因此预期用户已对 AWS 和 AWS Lambda 有些了解,并希望了解 spring 提供的附加价值。

Getting Started

Spring Cloud 函数框架的一个目标是提供必要的元素以使一个 简单的函数应用程序 在特定环境中以特定方式进行交互。一个简单的函数应用程序(在内容或 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。有趣的是,从表面上看这仅仅是另一个引导应用程序,但从 AWS 适配器的角度来看,它也是一个完全有效的 AWS Lambda 应用程序。不需要其他代码或配置。您需要做的就是打包并部署它,让我们看看我们如何做到这一点。

为了简化操作,我们提供了一个准备构建和部署的示例项目,您可以在 here 中访问它。

您只需执行 ./mvnw clean package 即可生成 JAR 文件。所有必需的 Maven 插件已设置好,以生成可部署到 AWS 的适当 JAR 文件。(您可以在 Notes on JAR Layout 中阅读更多有关 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 Request Handlers

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

如果您的应用程序包含多个 @Bean,其类型为 Function 等,则可以通过配置 spring.cloud.function.definition 属性或环境变量来选择一个 Function。这些函数从 Spring Cloud FunctionCatalog 中提取。如果您未指定 spring.cloud.function.definition,框架将尝试按照以下搜索顺序查找一个默认值:首先搜索 Function,然后搜索 Consumer,最后搜索 Supplier)。

Type Conversion

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

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

如果事件类型未知或无法确定(例如,Function<?, ?>),我们将尝试将传入流事件转换为泛型 Map

Raw Input

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

AWS Function Routing

Spring Cloud Function 的核心特性之一是 routing,它可以根据用户提供的路由指令将一个特殊函数委派给其他函数。

在 AWS Lambda 环境中,此功能提供了一项附加好处,因为它允许您将单个函数(路由函数)作为 AWS Lambda 和一个 API 网关的单一 HTTP 端点进行绑定。因此,在最后,您只需管理一个函数和一个端点,同时还可从可作为应用程序一部分的众多函数中受益。

更多详细信息可在提供的 sample 中找到,然而,有一些值得一提的一般性内容。

每当应用程序中存在多于一个函数时,路由功能都会默认启用,因为 org.springframework.cloud.function.adapter.aws.FunctionInvoker 无法确定哪个函数绑定为 AWS Lambda,因此将默认设置为 RoutingFunction。这意味着您只需提供路由指令,您可以使用以下几种机制执行此操作:https://docs.spring.io/spring-cloud-function/docs/{project-version}/reference/html/spring-cloud-function.html#_function_routing_and_filtering[(有关更多详情,请参见样本:https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-aws-routing)。

此外,请注意,由于 AWS 不允许在环境变量的名称中使用句点“.”和/或连字符“—”,因此您可以受益于启动支持,简单地将句点替换为下划线,并将连字符替换为驼峰式。因此,例如 spring.cloud.function.definition 变为 spring_cloud_function_definition,而 spring.cloud.function.routing-expression 变为 spring_cloud_function_routingExpression

Custom Runtime

您还可以受益于 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 中将处理程序名称设置为函数名称。您还可以在此处使用函数组合(例如,uppecrase|reverse)。这几乎就是全部了。一旦您将 zip/jar 上传到 AWS,函数将在自定义运行时中运行。我们提供了一个示例项目, 您还可以在其中看到如何正确配置 POM 以生成 zip 文件:https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-samples/function-sample-aws-custom-new

功能 bean 定义样式也适用于自定义运行时,并且比 @Bean 样式更快。自定义运行时甚至可以比 Java lambda 的功能 bean 实现启动得更快 - 它主要取决于您在运行时需要加载的类数量。Spring 在此处做得并不多,因此您可以通过仅在函数中使用基元类型(例如,不执行任何自定义 @PostConstruct 初始化程序的工作)来减少冷启动时间。

AWS Function Routing with Custom Runtime

使用 Custom Runtime 时,函数路由工作方式相同。您所需要做的就是将 functionRouter 指定为 AWS 处理程序,这种方式与您将函数名称用作处理程序的方式相同。

Deploying Container images

自定义运行时还负责处理容器映像部署。当以与 here中所述类似的方式部署容器映像时,请务必使用函数名称设置和环境变量 DEFAULT_HANDLER

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

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

此外,务必要记住确保 spring_cloud_function_web_export_enabled 也设置为 false。默认情况下,它就是 false

Notes on JAR Layout

您在 Lambda 中的运行时不需要 Spring Cloud Function Web 或 Stream 适配器,因此您可能需要在将 JAR 发送到 AWS 之前将它们排除在外。Lambda 应用程序必须进行 shaded,但是 Spring Boot 独立应用程序无需进行 shaded,因此您可以使用 2 个单独的 jar 运行相同的应用程序(根据示例)。示例应用程序创建 2 个 jar 文件,一个带有 aws 分类器,用于在 Lambda 中部署,一个带有 [id="thin-jar"][id="thin-jar"] 的可执行(精简)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>

Build file setup

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

正如在Notes on JAR Layout中指出的,你将需要一个经过着色的jar文件以便将其上传到AWS Lambda。你可以为此使用 Maven Shade Pluginsetup的示例可以在上方找到。

您可以使用 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>

你可以找到用于将 Spring Cloud Function 应用程序部署到 AWS Lambda(使用 Maven here)的完整示例 pom.xml 文件。

Gradle

要使用适用于 Gradle 的适配器插件,请将依赖项添加到您的 build.gradle 文件:

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

正如在Notes on JAR Layout中指出的,你将需要一个经过着色的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()
}

Gradle 部署 Spring Cloud Function 应用至 AWS Lamda 的完整示例 build.gradle 文件,请访问 here