做者 | 方劍(洛夜) Spring Cloud Alibaba 開源項目負責人/創始人之一
來源|阿里巴巴雲原生公衆號html
導讀:本文摘自 Spring Cloud Alibaba 開源項目創始團隊成員方劍撰寫的《深刻理解 Spring Cloud 與實戰》一書,主要講述了 Java 微服務框架 Spring Boot/Cloud 這個事實標準下如何應對 FaaS 場景。java
2019 年,O'Reilly 對 1500 名 IT 專業人員的調查中,有 40% 的受訪者在採用 Serverless 架構的組織中工做。2020 年 DataDog 調查顯示,如今有超過 50% 的 AWS 用戶正在使用 Serverless 架構的 AWS Lambda。spring
Serverless 正在成爲主流,因而就誕生了下面這幅圖,從單體應用的管理到微服務應用的管理再到函數的管理。編程
Serverless 到目前爲止尚未一個精準定義。Martin Fowler 在我的博客上有一篇《Serverless Architectures》文章,其對 Serverless 的的定義分紅了 BaaS 或 FaaS。後端
Baas 是全稱是 Backend-as-a-Service,後端即服務,FaaS 的全稱是 Function-as-a-Service,函數即服務。數組
今天咱們來聊聊 FaaS。這是維基百科對 FaaS 的定義:架構
函數即服務(FaaS)是一類雲計算服務,它提供了一個平臺,使客戶能夠開發,運行和管理應用程序功能,而無需構建和維護一般與開發和啓動應用程序相關的基礎架構。遵循此模型構建應用程序是實現 Serverless 架構的一種方法,一般在構建微服務應用程序時使用。app
對於 Python、JavaScript 這種天生支持 Lambda 的開發語言,和 FaaS 簡直是完美結合。Serverless Framework 的調研報告也很好地說明了這一點。NodeJS、Python 是 FaaS 使用率前二的語言。框架
咱們知道,由於 JVM 佔用的內存比較大,因此 Java 應用的啓動會有點慢,不太適合 FaaS 這個場景,這也是 Java 在使用率上偏低的緣由。less
另外,對 Java 開發者來講 Spring Boot/Cloud 已經成爲了事實標準,依賴注入是 Spring Framework 的核心,Spring Boot/Cloud 這個事實標準應對 FaaS 這個場景,會碰撞出怎麼樣的火花呢?這就是今天咱們要聊的 Spring Cloud Function。
在對 Spring Cloud Function 介紹以前,咱們先來看 Java 裏的核心函數定義。
JDK 1.8 推出了新特性 Lambda 表達式,java.util.function 包下面提供了不少的函數。這 3 個函數尤其重要:
@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);
@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()); });
@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());
Java Function 的編程模型很是簡單,本質上就是這 3 個核心函數:
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 函數。
這裏再多介紹統一雲廠商的 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 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 開源項目負責人/創始人之一。《深刻理解 Spring Cloud 與實戰》做者,Apache RocketMQ Committer,Alibaba Nacos Committer。曾在我的博客上編寫過《SpringMVC 源碼分析系列》、《SpringBoot 源碼分析系列》文章,目前,關注微服務、雲原生、Kubernetes。