導讀:本文摘自 Spring Cloud Alibaba 開源專案創始團隊成員方劍撰寫的《深入理解 Spring Cloud 與實戰》一書,主要講述了 Java 微服務框架 Spring Boot/Cloud 這個事實標準下如何應對 FaaS 場景。
Serverless & FaaS2019 年,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: 需要一個引數,得到另一個結果.@FunctionalInterfacepublic 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: 需要一個引數進行操作,無返回值。
@FunctionalInterfacepublic 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: 得到一個結果,無輸入引數。
@FunctionalInterfacepublic 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 FunctionJava Function 的程式設計模型非常簡單,本質上就是這 3 個核心函式:
SupplierFunctionConsumerSpring 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:
@SpringBootApplicationpublic 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 helloHELLO$ 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:
@SpringBootApplicationpublic 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-topicspring.cloud.stream.bindings.input.group=scf-groupspring.cloud.stream.bindings.output.destination=output-topicspring.cloud.stream.function.definition=uppercase|prefix
Spring Cloud Function & Spring Cloud Task:
@SpringBootApplicationpublic 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=functionspring.cloud.function.task.supplier=supplierspring.cloud.function.task.consumer=consumer
《深入理解 Spring Cloud 與實戰》一書正式開始預售啦,這是一本深入剖析 Spring Cloud 全家桶的書籍,涉及以下內容:
Spring Boot 核心特性Spring Cloud 服務註冊/服務發現原理剖析雙註冊雙訂閱模型完成 Eureka 遷移至 Nacos 的案例負載均衡:Spring Cloud LoadBalancer 和 Netflix RibbonDubbo Spring Cloud:Spring Cloud 與 Apache Dubbo 的融合Spring Cloud 灰度釋出案例Spring 體系配置,動態重新整理載入原理剖析Spring Cloud Circuit Breaker 抽象以及 Sentinel、Hystrix、Resilience4j 熔斷器對比Spring 體系訊息程式設計模型剖析Spring Cloud Data Flow 完成批處理和流處理任務Spring Cloud Gateway 閘道器剖析Spring 與 Serverless 的融合點選瞭解詳情,更有機會贏取免費圖書!
作者簡介方劍 Spring Cloud Alibaba 開源專案負責人/創始人之一。《深入理解 Spring Cloud 與實戰》作者,Apache RocketMQ Committer,Alibaba Nacos Committer。曾在個人部落格上編寫過《SpringMVC 原始碼分析系列》、《SpringBoot 原始碼分析系列》文章,目前,關注微服務、雲原生、Kubernetes。