距離 Java 開發者玩轉 Serverless,到底還有多遠?

頭圖.png

做者 | 方劍(洛夜)  Spring Cloud Alibaba 開源項目負責人/創始人之一
來源|阿里巴巴雲原生公衆號html

導讀:本文摘自 Spring Cloud Alibaba 開源項目創始團隊成員方劍撰寫的《深刻理解 Spring Cloud 與實戰》一書,主要講述了 Java 微服務框架 Spring Boot/Cloud 這個事實標準下如何應對 FaaS 場景。java

Serverless & FaaS

2019 年,O'Reilly 對 1500 名 IT 專業人員的調查中,有 40% 的受訪者在採用 Serverless 架構的組織中工做。2020 年 DataDog 調查顯示,如今有超過 50% 的 AWS 用戶正在使用 Serverless 架構的 AWS Lambda。spring

Serverless 正在成爲主流,因而就誕生了下面這幅圖,從單體應用的管理到微服務應用的管理再到函數的管理。編程

1.png

Serverless 到目前爲止尚未一個精準定義。Martin Fowler 在我的博客上有一篇《Serverless Architectures》文章,其對 Serverless 的的定義分紅了 BaaS 或 FaaS。後端

2.png

Baas 是全稱是 Backend-as-a-Service,後端即服務,FaaS 的全稱是 Function-as-a-Service,函數即服務。數組

今天咱們來聊聊 FaaS。這是維基百科對 FaaS 的定義:架構

函數即服務(FaaS)是一類雲計算服務,它提供了一個平臺,使客戶能夠開發,運行和管理應用程序功能,而無需構建和維護一般與開發和啓動應用程序相關的基礎架構。遵循此模型構建應用程序是實現 Serverless 架構的一種方法,一般在構建微服務應用程序時使用。app

對於 Python、JavaScript 這種天生支持 Lambda 的開發語言,和 FaaS 簡直是完美結合。Serverless Framework 的調研報告也很好地說明了這一點。NodeJS、Python 是 FaaS 使用率前二的語言。框架

3.png

咱們知道,由於 JVM 佔用的內存比較大,因此 Java 應用的啓動會有點慢,不太適合 FaaS 這個場景,這也是 Java 在使用率上偏低的緣由。less

另外,對 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: 一次消息消費/生產/轉換是一次函數調用。

4.png

這裏再多介紹統一雲廠商的 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 開源項目負責人/創始人之一。《深刻理解 Spring Cloud 與實戰》做者,Apache RocketMQ Committer,Alibaba Nacos Committer。曾在我的博客上編寫過《SpringMVC 源碼分析系列》、《SpringBoot 源碼分析系列》文章,目前,關注微服務、雲原生、Kubernetes。

相關文章
相關標籤/搜索