初识Spring WebFlux

官网地址:https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html

什么是WebFlux

​ Spring WebFlux 是spring5提出的一种新的开发web 的技术栈,它是完全无阻塞的,支持 Reactive Streams背压,可以运行在Netty、 Servlet 3.1+ 容器等服务器上,可以支持非常高的并发量。听了这个解释之后,有一种听君一席话,如听一席话的感觉,不要着急,要想学废WebFlux不是一个概念就能理解的,等学完了整篇文章,再回来看这句话,就会通畅多了。。。【手动狗头🐶】

下面是官网提供的对比图,SpringMVC和WebFlux不同点主要在于:

  1. webFlux是一种非阻塞的开发模式,也就是一个线程可以处理更多请求,而传统的MVC是一种同步的阻塞开发模式,一个请求对应容器里面的一个线程
  2. 运行环境的不同,SpringMVC是基于Servlet API,所以必须要运行组servlet容器上面,而WebFlux是基于响应式流(Reactive Streams),可以运行在 Servlet 3.1+ 容器(支持异步servlet的容器),或者运行在netty上面(Spring默认容器)
  3. 数据库:目前关系型数据库都是不支持这种响应式的,比如:Mysql,Oracle这种基于JDBC的数据库,这里为什么不支持关系型数据库是因为JDBC会阻塞线程
  4. 所以WebFlux 最好的应用场景是系统之间调用

优势

最主要的优势就是提高吞吐量,也就是提高并发量。当我们业务系统的并发量逐渐增大时,我们就要想办法提高系统的并发量,提高并发量的方式主要有两种:一种叫水平扩展,一种叫垂直扩展,简单举例来说,水平扩展就是加人,垂直扩展就是加班

  1. 水平扩展:增加节点,也就是加机器,增加成本,加钱就能解决
  2. 垂直扩展:线程还是那么多线程,但是要处理更加多的请求,也就是WebFlux的典型应用

如何学习WebFlux

  1. 先从JDK8的 lambda表达式和Stream开始学习 ,学习函数式编程的思想
  2. 学习JDK9的Reactive Streams,了解背压和背压的实现
  3. 前面两个学完之后,我们就很容易掌握WebFlux的基石 ,也就是Reactor框架,从而掌握WebFlux的技术重点
  4. 而gateWay就是WebFlux最成功的实现

学习建议

  1. 我们在学习一个知识点的时候,一定要把这个知识点的前置知识及原理学明白,不要让我们的技术出现技术断层,往往遇到解决不了的问题都是因为技术断层导致的
  2. 有一定自学和动手能力:要向应用于实际开发工作中,一定要自己写代码勤加练习
  3. 中高级程序员/架构师:不适合初级程序员,因为很多概念比较抽象
  4. 提高自己技术功底,提高设计能力的同学:学习WebFlux设计模式以及高性能原因,借鉴它的设计思想和设计理念,提升我们在开发中的程序设计能力

Spring WebFlux前置知识

lambda

​ Lambda 表达式(lambda expression)是一个匿名函数

Jdk8自带的函数式接口:

注:上图思维导图分享链接 → 点我打开

Stream流

Stream是一个高级的迭代器,它不是一个数据结构,也不是一个集合,不会存放数据,它关注的是怎么样把数据高效处理,可以理解为把制定数据在流水线中处理,在流水线的开始输入数据,在流水线的尾端得到结果,也就是流水线式处理思想

注: 上图思维导图分享 → 点我打开

函数式编程

函数式编程,是一种相对于命令式编程的一种编程范式,他不是一种具体的技术,而是一种方法论,是一种关于如何搭建应用程序的方法论,这个概念有点抽象,不太好理解,其实网上也没有一个清晰的概念,我的理解就是我们能熟练使用Stream流Api和Lambda表达式,有流相关的一种思想,就可以说我们会用函数式编程了。下面对比一下函数式编程和命令式编程的主要差异

  1. 命令式编程里面我们关注的是怎么样做
  2. 函数式编程里面我们关注的是做什么,我们只需要关注实现什么样的功能,而不需要关注实现的细节
  3. Python代码中基本上每一行代码都可以写成函数式编程,这依赖于Python的语法结构和强大的第三方库函数的支持,这也奠定了Python在敏捷开发快速迭代中的至高地位。java程序员如何快速学习上手Python?→ 点我有彩蛋

响应式编程

​ 响应式编程也有人叫反应式编程, Reactive Programming(反应式编程),是一种高性能应用的编程方式。其最早是由微软提出并引入到 .NET 平台中,随后 ES6 也引入了类似的技术。在 Java 平台上,较早采用反应式编程技术的是 Netflix 公司开源的 RxJava 框架。现在大家比较熟知的Hystrix 就是以RxJava 为基础开发的。是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似”=B1+C1”的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。

​ 最近几年,随着 Node.js、Golang等新技术、新语言的出现,Java在服务器端开发语言老大的地位受到了不小的挑战。虽然,Java 的市场份额依旧很大,短时间内也不会改变,但 Java 社区对于挑战也并没有无动于衷。相反,Java 社区积极应对这些挑战,不断提高自身应对高并发服务器端开发场景的能力。为了应对高并发的服务器端开发,在2009年的时候,微软提出了一个更优雅地实现异步编程的方式 —— Reactive Programming,中文称反应式编程。随后,其它技术也迅速地跟上了脚步,像 ES6 通过 Promise引入了类似的异步编程方式。Java 社区也没有落后很多,Netflix 和 TypeSafe 公司提供了 RxJava和Akka Stream技术,让 Java平台也有了能够实现响应式编程的框架。

Reactive Streams

​ Reactive Streams是JDK9引入的一套标准,这套标准是有大名鼎鼎的Doug Lea 引入进来的,秉承着苟蛋出品,必是精品的学习原则,Reactive Streams 今后将成为所有java中高级程序员的必修课程,再此我大胆预测,不出五年Reactive Streams将占领各大面试题榜首,成为如今多线程一样的热门面试题目,中国人不骗中国人,我李苟蛋的这个flag今天就在这立下了,5年之后它不火,你来打我🐶。铺垫结束,下面来介绍一下Reactive Streams的基本原理,它是一套基于发布订阅模式的数据处理的规范,在JDK里面真正的叫法应该叫flow API(java.util.concurrent.Flow),它跟之前的Stream流编程没有太大关系,最起码在代码层面没有任何耦合。它引入的背压的概念,什么是背压呢?说白了就是一个交互,也就是一个反馈,是订阅者和发布者之间的一个交互,订阅者可以告诉发布者我需要多少数据,处理完了再去发布者拿数据,数据没处理完,就不要给我数据,这样就可以起到一个调节数据流量的作用。可以避免发布者产生过多数据导致数据大量积压,也避免了把订阅者压垮。

下图为JDK9 Reactive Streams 中有四大接口

示例代码如下:

代码示例订阅者中的 this.subscription.request(1); 就是响应式的关键,订阅者在onNext方法接收发布者发布的数据后,进行业务处理,处理完成之后调用this.subscription.request() 方法向发布者再请求一条数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class FlowDemo01 {

public static void main(String[] args) throws InterruptedException {
// 1、定义发布者,发布者的数据类型是Integer,使用jdk自带的 SubmissionPublisher ,它实现了Publisher接口
SubmissionPublisher<Integer> publisher = new SubmissionPublisher<Integer>();
// 2、定义订阅者(主要的逻辑都是在订阅者这里)
Subscriber<Integer> subscriber = new Subscriber<Integer>() {

private Flow.Subscription subscription;

// 建立订阅关系的时候调用,入参就是订阅关系
@Override
public void onSubscribe(Flow.Subscription subscription) {
// 保存订阅关系,需要用它来给发布者响应
this.subscription = subscription;
// 告诉发布者,需要请求一个数据
this.subscription.request(1);
}

// 接收到一个数据,当有数据到了的时候,这个方法会被触发
@Override
public void onNext(Integer item) {
// 业务处理部分
System.out.println("接收到数据,处理数据:" + item);

// 处理完了告诉发布者给我发送下一条数据 (这个就是响应式的关键)
this.subscription.request(1);
// 已经达到目标,告诉发布者不要再发数据给我
// this.subscription.cancel();
}

@Override
public void onError(Throwable throwable) {
// onNext 处理出错,在这里会接收到异常
throwable.printStackTrace();
// 出错之后告诉发布者不接收数据了
this.subscription.cancel();
}

@Override
public void onComplete() {
// 数据全部处理完成(发布者关闭的时候触发,即:publisher.close();)
System.out.println("处理完成!");

}
};

// 3、发布者和订阅者建立订阅关系
publisher.subscribe(subscriber);

// 4、生产数据,并发布,忽略数据生产过程
int data = 666;
publisher.submit(data);
// 5、结束后关闭发布者,正式环境应放到finally或者使用try-resouce确保关闭
publisher.close();

// 主线程延迟停止,避免数据没有消费就退出
Thread.currentThread().join(1000);

}
}

这里思考一个问题,我们说响应式流里面最关键的就是一个反馈,订阅者可以通过这个反馈来调节生产者发送数据的速度,它究竟是怎么让我们发布者生产数据的速度慢下来的呢?

  • 关键点在于发布者这里面的publisher.submit(i); 方法实际上是个阻塞方法,发布者生产的数据会放到Subscription的缓冲器中,缓冲区的默认大小是32,最大缓冲区大小是256。
  • 当发布者生产的数据把Subscription的缓冲池放满了,那么发布者生产数据这件事就会停下来,等待订阅者处理完数据之后缓冲池有位置了再继续生产下一条数据

Reactor

前言

​ 其实,在更早之前,像 Mina 和 Netty 这样的 NIO 框架其实也能搞定高并发的服务器端开发任务,但这样的技术相对来说只是少数高级开发人员手中的工具。对于更多的普通开发者来说,难度显得大了些,所以不容易普及。到了2017年,虽然已经有不少公司在实践响应式编程。但整体来说,应用范围依旧不大。原因在于缺少简单易用的技术将响应式编程推广普及,并同诸如 MVC 框架、HTTP 客户端、数据库技术等整合。终于,在2017年9月28日,解决上面问题的利器浮出水面 —— Spring 5 正式发布。Spring 5 其最大的意义就是能将响应式编程技术的普及向前推进一大步。而作为在背后支持 Spring 5 响应式编程的框架 Reactor,也相应的发布了 3.1.0 版本。

在 Java 平台上,Netflix(开发了 RxJava)、TypeSafe(开发了 Scala、Akka)、Pivatol(开发了 Spring、Reactor)共同制定了一个被称为 Reactive Streams 项目(规范),用于制定反应式编程相关的规范以及接口。其主要的接口有这三个:

  • Publisher
  • Subscriber
  • Subcription

反应式编程其实并不神秘,通过与我们熟悉的迭代器模式对比便可了解其基本思想:

event(事件) Iterable (pull)迭代器 Subscriber (push)订阅者
retrieve data:检索数据 T next() onNext(T)
discover error throws Exception onError(Exception)
complete !hasNext() onCompleted()

​ 表格的中的 Subscriber 那一列便代表反应式编程的 API 使用方式。可见,它就是常见的观察者模式的一种延伸。如果将迭代器看作是拉模式,那观测者模式便是推模式。被订阅者(Publisher)主动的推送数据给订阅者(Subscriber),触发 onNext 方法。异常和完成时触发另外两个方法。如果 Publisher 发布消息太快了,超过了 Subscriber 的处理速度,那怎么办。这就是 Backpressure(背压) 的由来,Reactive Programming 框架需要提供机制,使得 Subscriber 能够控制消费消息的速度。

Reactor 的主要模块

​ Reactor 框架主要有两个主要的模块:reactor-core 和 reactor-ipc。前者主要负责 Reactive Programming 相关的核心 API 的实现,后者负责高性能网络通信的实现,目前是基于 Netty 实现的。

Reactor 的主要类

  1. Reactor 有两个核心类,MonoFlux,这两个类实现接口 Publisher,提供丰富操作符。Flux 代表 N 个元素的发布者;Mono 代表 0 或者 1 个元素的发布者。
  2. Flux 和 Mono 都是数据流的发布者,使用 Flux 和 Mono 都可以发出三种数据信号: 元素值,错误信号,完成信号,错误信号和完成信号都代表终止信号,终止信号用于告诉 订阅者数据流结束了,错误信号终止数据流同时把错误信息传递给订阅者。
  3. 说白了Reactor我们就可以理解为: Reactor = jdk8 stream + jdk9 reactive stream

SpringWebflux

​ 上面介绍了响应式编程的一些概念,以及 reactive stream 和 Reactor,现在对三者关系总结一下,其实很简单,Reactive Streams 是规范,Reactor 实现了 Reactive Streams。WebFlux 以 Reactor 为基础,实现 Web 领域的响应式编程框架

Webflux对比SpringMvc

  1. SpringMVC :同步阻塞的方式,基于 SpringMVC+Servlet+Tomcat
  2. SpringWebflux :异步非阻塞 方式,基于 SpringWebflux+Reactor+Netty
  3. SpringWebflux 执行过程和 SpringMVC非常相似,可以对比学习
  4. SpringWebflux 核心控制器 DispatchHandler,实现接口WebHandler

Webflux开发方式

webFlux提供两种开发模式:

  1. 第一种就是我们熟悉的以前老的SpringMvc的开发模式,使用@Controller,@RequestMapping之类注解
  2. 第二种Router Functions,是一种比较类似于函数式编程风格,简洁灵活

方式一:SpringMvc的开发模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* WebFlux开发模式1:@Controller@RequestMapping
*/
@Slf4j
@RestController
public class TestController01 {

@GetMapping("/1")
private String get1() {
log.info("get1 开始");
String date = createData();
log.info("get1 结束");
return date + "-> success";
}

@GetMapping("/2")
private Mono<String> get2() {
log.info("get2 开始");
Mono<String> stringMono = Mono.fromSupplier(this::createData).map(s -> s + "-> flux success");
log.info("get2 结束");
return stringMono;
}

/**
* 设置:produces = "text/event-stream" 告诉浏览器后台数据以流方式返回
* http是一问一答的形式返回数据,他是如何做到多次返回的呢? H5: SSE(Server-Send Events)
*/
@GetMapping(value = "/3", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
private Flux<String> get3() {
log.info("get3 开始");
Flux<String> stringFlux = Flux.fromStream(IntStream.range(1, 5).mapToObj(this::sleepOneSecond));
log.info("get3 结束");
return stringFlux;
}


private String sleepOneSecond(Integer i) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "李苟蛋flux3:" + i;
}

private String createData() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "李苟蛋";
}
}

方式二:Router Functions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 配置请求路由
*/
@Configuration
public class AllRouters {

@Bean
RouterFunction<ServerResponse> userRouter(TestHandler01 handler01) {
return RouterFunctions.nest(RequestPredicates.path("/user"), // 相当于类上面的@RequestMapping
RouterFunctions.route(RequestPredicates.GET("/1"), handler01::getAllStudent));
}
}

/**
* WebFlux开发模式2:Router Functions
* 1、开发HandlerFunction(输入ServerRequest 返回ServerResponse)
* 2、RouterFunction(请求URL和HandlerFunction对应起来)
* 3、HttpHandler -> Server 处理
*/
@Slf4j
@Component
public class TestHandler01 {

public Mono<ServerResponse> getAllStudent(ServerRequest request) {
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).
body(Flux.just(new Student("小明", 10, Gender.MALE, Grade.ONE)), Student.class);
}
}

Webflux成功应用之GateWay

  • Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5,Spring Boot 2和 Project Reactor等技术。
  • Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能, 例如:熔断、限流、重试等
  • SpringCloud Gateway 是 Spring Cloud 的一个全新项目,基于 Spring 5.0+Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
  • SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。

注:上图思维导图的链接地址→ 点我打开 springcloud技术栈

其他应用Fizz GateWay

  1. 官网:https://www.fizzgate.com/
  2. 码云:https://gitee.com/fizzgate/fizz-gateway

总结

函数式编程未来可期

​ 我们思考一个问题,为什么Python可以很容易做到函数式编程呢?其核心在于不需要指定变量类型而可以直接调用方法做数据处理,那我们的java可不可以呢?其实正在往这方向发展,当我们所有处理数据的接口都以流对象为参数的时候,岂不就可以做到这种函数式编程的链式调用了吗?我们从WebFlux的核心处理类 DispatcherHandler 中就可以感受得到,我相信不久将来,这种代码风格将会席卷整个中国互联网行业,而我们的代码可读性也将会更上一层楼,所以小伙伴们抓紧入坑吧。。。ps:吹牛,我是专业的【手动狗头】

尾声

  1. 要想把Webflux玩得精通还需要大量的学习和实践,建议自己可以用Webflux做一些小的需求,来增加使用熟练度
  2. 从门之后,建议看一些成熟的开源框架的源码,比如说GateWay,学习大佬们是怎么使用的,本文只能做到抛砖引玉的作用
  3. 最后,谢谢大家

本文代码分享