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 不允許環境變數名稱中使用點 . 和/或連字號 -,因此您可以受益於 Boot 支援,只需將點替換為底線,將連字號替換為駝峰式命名。因此,例如 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 的根目錄中提供一個 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 應用程式必須是 shaded 的,但 Spring Boot 獨立應用程式則不需要,因此您可以使用 2 個單獨的 jar 執行相同的應用程式(如範例所示)。範例應用程式建立 2 個 jar 檔案,一個帶有 aws 分類器用於在 Lambda 中部署,另一個 可執行(thin)jar,其中包含執行階段的 spring-cloud-function-web。Spring Cloud Function 將嘗試從 JAR 檔案資訊清單中為您找到「主類別」,使用 Start-Class 屬性(如果您使用 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>

建置檔案設定

為了在 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 佈局注意事項中所述,您需要一個 shaded jar 才能將其上傳到 AWS Lambda。您可以使用 Maven Shade Plugin 來完成此操作。有關設定的範例,請參閱上方。

您可以使用 Spring Boot Maven Plugin 來產生 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>

您可以在此處找到使用 Maven 將 Spring Cloud Function 應用程式部署到 AWS Lambda 的完整範例 pom.xml 檔案。

Gradle

為了將轉接器外掛程式用於 Gradle,請將依賴項新增至您的 build.gradle 檔案

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

JAR 佈局注意事項中所述,您需要一個 shaded jar 才能將其上傳到 AWS Lambda。您可以使用 Gradle Shadow Plugin 來完成此操作

您可以使用 Spring Boot Gradle Plugin 和 Spring Boot Thin Gradle Plugin 來產生 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 Lambda 的完整範例 build.gradle 檔案。