作者 | 方劍(洛夜) Spring Cloud Alibaba 開源項目負責人/創始人之一
本文摘自 Spring Cloud Alibaba 開源項目創始團隊成員方劍撰寫的《深入理解 Spring Cloud 與實戰》一書,主要講述了 Java 微服務框架 Spring Boot/Cloud 這個事實標準下如何應對 FaaS 場景。
Serverless & FaaS
2019 年,O'Reilly 對 1500 名 IT 專業人員的調查中,有 40% 的受訪者在採用 Serverless 架構的組織中工作。2020 年DataDog 調查顯示,現在有超過 50% 的 AWS 用戶正在使用 Serverless 架構的 AWS Lambda。
Serverless 正在成爲主流,於是就誕生了下面這幅圖,從單體應用的管理到微服務應用的管理再到函數的管理。
Serverless 到目前爲止還沒有一個精準定義。Martin Fowler 在個人博客上有一篇《Serverless Architectures》文章,其對 Serverless 的的定義分成了 BaaS 或 FaaS 。
Baas 是全稱是 Backend-as-a-Service,後端即服務,FaaS 的全稱是 Function-as-a-Service,函數即服務。
今天我們來聊聊 FaaS。這是維基百科對 FaaS 的定義:
函數即服務(FaaS)是一類雲計算服務,它提供了一個平臺,使客戶可以開發,運行和管理應用程序功能,而無需構建和維護通常與開發和啓動應用程序相關的基礎架構。遵循此模型構建應用程序是實現 Serverless 架構的一種方法,通常在構建微服務應用程序時使用。
對於 Python、JavaScript 這種天生支持 Lambda 的開發語言,和 FaaS 簡直是完美結合。Serverless Framework 的調研報告也很好地說明了這一點。NodeJS、Python 是 FaaS 使用率前二的語言。
我們知道,因爲 JVM 佔用的內存比較大,所以 Java 應用的啓動會有點慢,不太適合 FaaS 這個場景,這也是 Java 在使用率上偏低的原因。
另外,對 Java 開發者來說 Spring Boot/Cloud 已經成爲了事實標準,依賴注入是 Spring Framework 的核心,Spring Boot/Cloud 這個事實標準應對 FaaS 這個場景,會碰撞出怎麼樣的火花呢?這就是今天我們要聊的 Spring Cloud Function。
Java Function
在對 Spring Cloud Function 介紹之前,我們先來看 Java 裏的核心函數定義。
JDK 1.8 推出了新特性 Lambda 表達式,java.util.function 包下面提供了很多的函數。這 3 個函數尤爲重要:
1. java.util.function.Function: 需要一個參數,得到另一個結果。
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
比如通過 Stream API 裏的 map 方法可以通過 Function 把字符串從小寫變成大寫:
Stream.of("a", "b", "c").map(String::toUpperCase);
這裏的 map 方法需要一個 Function 參數:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
2. java.util.function.Consumer: 需要一個參數進行操作,無返回值。
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
比如通過 Stream API 裏的 forEach 方法遍歷每個元素,做對應的業務邏輯處理:
RestTemplate restTemplate = new RestTemplate();
Stream.of("200", "201", "202").forEach(code -> {
ResponseEntity<String> responseEntity =
restTemplate.getForEntity("http://httpbin.org/status/" + code, String.class);
System.out.println(responseEntity.getStatusCode());
});
3. java.util.function.Supplier: 得到一個結果,無輸入參數。
@FunctionalInterface
public interface Supplier<T> {
T get();
}
比如自定義 Supplier 可以返回隨機數:
Random random = new Random();
Supplier supplier100 = () -> random.nextInt(100);
Supplier supplier1000 = () -> random.nextInt(1000);
System.out.println(supplier100.get());
System.out.println(supplier1000.get());
Spring Cloud Function
Java Function 的編程模型非常簡單,本質上就是這 3 個核心函數:
- Supplier<O>
- Function<I, O>
- Consumer<I>
Spring Cloud Function 是 Spring 生態跟 Serverless(FaaS) 相關的一個項目。它出現的目的是增強 Java Function,主要體現在這幾點:
統一雲廠商的 FaaS 編程模型: Spring Cloud Function 的口號是 "Write Once, Run Anywhere"。我們寫的 Spring Cloud Function 代碼可以運行在本地、各個雲廠商(AWS Lambda, GCP Cloud Functions, Azure Functions)。
自動類型轉換: 理解過 Spring MVC 或者 Spring Cloud Stream 的同學肯定對 HttpMessageConverter 或者 MessageConverter 模型,這個轉換器的作用是將 HTTP BODY(或者 Message Payload)、HTTP Query Parameter、HTTP HEADER(或者 Message Header)自動轉換成對應的 POJO。有了這個特性後,我們就無需關注函數的入參和返回值,用 String 參數就可以獲取原始的入參信息,用 User 這個 POJO 參數就可以將原始的入參參數自動轉換成 User 對象。
函數組合: 可以讓多個函數之間進行組合操作。
函數管理: 新增 FunctionCatalog、FunctionRegistry 接口用於 Function 的管理。管理 ApplicationContext 內的 Function,動態註冊 Function 等操作。
Reactive 支持: Spring Cloud Function 新增比如 FluxFunction、FluxSupplier、FunctionConsumer 這種 Reactive 函數。
自動跟 Spring 生態內部原有的組件進行深度集成:
Spring Web/Spring WebFlux: 一次 HTTP 請求是一次函數調用。
Spring Cloud Task: 一次任務執行是一次函數調用。
Spring Cloud Stream: 一次消息消費/生產/轉換是一次函數調用。
這裏再多介紹統一雲廠商的 FaaS 編程模型,讓大家對 Spring Cloud Function 更有體感。
AWS Lambda 是第一個是提供 FaaS 服務的雲廠商,RequestStreamHandler 是 AWS 提供的針對 Java 開發者的接口,需要實現這個接口:
public class HandlerStream implements RequestStreamHandler {
@Override
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException
{
...
Azure Functions 針對 Java 開發者提供了 @HttpTrigger 註解:
public class Function {
public String echo(@HttpTrigger(name = "req",
methods = {HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS)
String req, ExecutionContext context) {
...
}
}
從這兩段代碼可以看出,不同的雲廠商要編寫不同的代碼。如果要變換雲廠商,這個過程會很痛苦。
另外,無論是 AWS、Azure 或者 GCP 提供的接口或註解,他們沒有任何 Spring 上下文相關的初始化邏輯。如果我們是一個 Spring Boot/Cloud 應用遷移到 FaaS 平臺,需要添加 Spring 上下文初始化邏輯等改動量。
Spring Cloud Function 的出現就是爲了解決這些問題。
Spring Cloud Function 的使用
Spring Cloud Function & Spring Web:
@SpringBootApplication
public class SpringCloudFunctionWebApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudFunctionWebApplication.class, args);
}
@Bean
public Function<String, String> upperCase() {
return s -> s.toUpperCase();
}
@Bean
public Function<User, String> user() {
return user -> user.toString();
}
}
訪問對應的 Endpoint:
$ curl -XPOST -H "Content-Type: text/plain" localhost:8080/upperCase -d hello
HELLO
$ curl -XPOST -H "Content-Type: text/plain" localhost:8080/user -d '{"name":"hello SCF"}'
User{name\u003d\u0027hello SCF\u0027}
Spring Cloud Function & Spring Cloud Stream:
@SpringBootApplication
public class SpringCloudFunctionStreamApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudFunctionStreamApplication.class, args);
}
@Bean
public Function<String, String> uppercase() {
return x -> x.toUpperCase();
}
@Bean
public Function<String, String> prefix() {
return x -> "prefix-" + x;
}
}
加上 function 相關的配置(針對 input-topic 上的每個消息,payload 轉換大寫後再加上 prefix- 前綴,再寫到 output-topic 上):
spring.cloud.stream.bindings.input.destination=input-topic
spring.cloud.stream.bindings.input.group=scf-group
spring.cloud.stream.bindings.output.destination=output-topic
spring.cloud.stream.function.definition=uppercase|prefix
Spring Cloud Function & Spring Cloud Task:
@SpringBootApplication
public class SpringCloudFunctionTaskApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudFunctionTaskApplication.class, args);
}
@Bean
public Supplier<List<String>> supplier() {
return () -> Arrays.asList("200", "201", "202");
}
@Bean
public Function<List<String>, List<String>> function() {
return (list) ->
list.stream().map( item -> "prefix-" + item).collect(Collectors.toList());
}
@Bean
public Consumer<List<String>> consumer() {
return (list) -> {
list.stream().forEach(System.out::println);
};
}
}
加上 function 相關的配置(Supplier 模擬任務的輸入源,Function 模擬對任務輸入源的處理,Consumer 模擬處理對 Function 處理輸入源後的數據):
spring.cloud.function.task.function=function
spring.cloud.function.task.supplier=supplier
spring.cloud.function.task.consumer=consumer
(完)
作者簡介
方劍,花名洛夜,Spring Cloud Alibaba 開源項目負責人/創始人之一。Apache RocketMQ Committer,Alibaba Nacos Committer。目前就職於阿里巴巴集團。
曾在個人博客上編寫過《Spring MVC源碼分析系列》、《Spring Boot源碼分析系列》文章。目前,關注微服務、雲原生、Kubernetes。
圖書推薦
▊《深入理解Spring Cloud與實戰》
方劍 編著
- Spring Cloud Alibaba創始人傾力打造
- 理論與實踐相結合,核心知識點輔以案例講解
這是一本深入剖析 Spring Cloud 全家桶的書籍,主要介紹Spring Cloud各個核心組件的設計原理,以及目前流行的Spring Cloud Alibaba和 Netflix組件,並且剖析Spring Cloud對流處理、批處理,以及目前業界流行的Serverless的支持。在介紹各部分內容時,本書將理論與實踐相結合,對每個核心知識點都給出了具體的案例應用,以幫助讀者掌握核心組件的設計理念。