做者 | 方剑(洛夜) 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。