Spring Webflux是一个无阻塞web框架,它适合处理大量并发连接,可以更有效率的利用多核cpu,这篇博客主要讲述WebFlux是什么和MVC有什么区别.
为什么WebFlux是无阻塞框架
WebFlux采用的是NIO模型,而java采用的IO模型
NIO
从下图来看,在请求过来时首先经过Selector,Selector会不断的轮询注册在上面的所有channel,并找到普配的channel并把任务交给他.每当完成就返回给Selector,这时一个现成就可以同时处理多个任务,其中nio的read/write是非阻塞的
IO
由于Java IO是阻塞的,所以当面对多个流的读写时需要多个线程处理。例如在网络IO中,Server端使用一个线程监听一个端口,一旦某个连接被accept,创建新的线程来处理新建立的连接。其中 read/write 是阻塞的。
总结
IO中高并发的场景,经常会使用ThreadPool去处理任务,但这种情况其实也是阻塞io.因为当thread pool中没有线程时,其他任务只能等线程空闲.但NIO不存在这个问题.所以webFlux是非阻塞的,它更适合高并发的场景.
Reactive Stream
Reactive Stream诞生于JDK9.reactive stream就是一个异步stream处理的标准,它的特点就是非阻塞的back pressure。reactive stream只是一个标准,它定义了实现非阻塞的back pressure的最小区间的接口,方法和协议。在JDK给的实现叫FlowAPI
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zYlmLBll-1599730454759)(https://tech.taiji.com.cn/ui/api/upload/a373be93840c4a8fac5c3d90030b454c_-_1599704667.jpg)]
- Subscription 接口定义了连接发布者和订阅者的方法,主要的作用是为发布者和订阅者建立关系
- Publisher 接口定义了发布者的方法;
- Subscriber 接口定义了订阅者的方法;
- Processor<T,R> 接口定义了处理器;
订阅者 Subscriber
Subscriber 订阅 Publisher 的数据流,并接受回调。 如果 Subscriber 没有发出请求,就不会受到数据。对于给定订阅(Subscription),调用 Subscriber 的方法是严格按顺序的。方法如下所示:
Subscriber<Integer> subscriber = new Subscriber<Integer>() {
private Subscription subscription;
//建立订阅关系
@Override
public void onSubscribe(Subscription subscription) {
this.subscription = subscription;
//已经准备好索要数据
this.subscription.request(1);
}
//处理接收数据
@Override
public void onNext(Integer integer) {
//处理数据
System.out.println("接收到数据 "+ integer);
//处理完成继续要数据
this.subscription.request(1);
}
@Override
public void onError(Throwable throwable) {
//数据处理异常
this.subscription.cancel();
}
@Override
public void onComplete() {
//数据全部处理完成
this.subscription.cancel();
}
};
订阅Subscription
Subscription 用于连接 Publisher 和 Subscriber。Subscriber 只有在请求时才会收到项目,并可以通过 Subscription 取消订阅。Subscription 主要有两个方法:
- request:订阅者调用此方法请求数据;
- cancel:订阅者调用这个方法来取消订阅,解除订阅者与发布者之间的关系。
发布者 Publisher
Publisher 将数据流发布给注册的 Subscriber。它通常使用 Executor 异步发布项目给订阅者。 Publisher 需要确保每个订阅的 Subscriber 方法严格按顺序调用。subscribe 方法用于订阅者订阅发布者。
处理器 Processor
Processor 位于 Publisher 和 Subscriber 之间,用于做数据转换。可以有多个 Processor 同时使用,组成一个处理链,链中最后一个处理器的处理结果发送给 Subscriber。JDK 没有提供任何具体的处理器。处理器同时是订阅者和发布者,接口的定义也是继承了两者,作为订阅者接收数据,然后进行处理,处理完后作为发布者,再发布出去。
背压 back pressure
在传统的消息推送机制中主要有两种方法 pull push,pull 面临的问题是当你去拉取数据,数据可能没有准备好.而push的问题是,不知道消费者到地能不能处理push过去的数据,如果数据量过大会造成消费者崩溃.所谓的背压,就是Subscriber告诉Publisher我要多少数据你给我发过来,也就是下图所示Request N item的过程.
WebFlux
从下图来看,webFlux支持函数式便程,Netty以及Event loop
- WebFlux是Spring推出响应式编程的一部分(web端)
- 响应式编程是异步非阻塞的(是一种基于数据流(data stream)和变化传递(propagation of change)的声明式(declarative)的编程范式)
Event loop
Event loop 是一种事件轮询机制,主要是为了解决单线程情况下啊,事件阻塞的问题.JavaScript用的就是Event loop,举个例子,前端进行渲染时,往往可能需要请求多个数据,同时才做Dom对象,如果是阻塞的,那所有的任务都只能排好队一个个执行,这会导致效率极其低下,但event loop 就是分发任务,不断的轮询,拿到结果,多个任务页面同时进行.个人觉得就是多路IO.
上面说到的Selector她的主要作用就是Event loop 监控多个channel.
Reactor
WebFlux使用的响应式流并不是用JDK9平台的,而是一个叫做Reactor响应式流库。所以,入门WebFlux其实更多是了解怎么使用Reactor的API,下面我们来看看~
Reactor是一个响应式流,它也有对应的发布者(Publisher
),Reactor的发布者用两个类来表示:
- Mono(返回0或1个元素)
- Flux(返回0-n个元素)
Mono 一次性返回所有数据
Flux 可以实现间隔一段时间返回数据
下面通过代码来认识一下webflux
@GetMapping(value = "/2" )
public Mono<String> test2() throws InterruptedException {
log.info("start");
TimeUnit.SECONDS.sleep(5);
Mono<String> stringMono = Mono.fromSupplier(() -> createStr());
log.info("end");
return stringMono;
}
从调用者(浏览器)的角度而言,是感知不到有什么变化的,因为都是得等待5s才返回数据。但是,从服务端的日志我们可以看出,WebFlux是直接返回Mono对象的 但是mvc的项目会阻塞5秒
事件推送
正常的请求都是一问一答的模式,但是事件推送(SSE -> Server Send Event)可以做到连续返回数据.下面代码的效果就是连续的返回数据.
示例代码
@GetMapping(value = "/3",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> test3() {
log.info("start");
Flux<String> stringMono = Flux.fromStream(IntStream.range(1,5).mapToObj(i ->{
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "flux" + i;
}));
log.info("end");
return stringMono;
}
Spring Data Reavtive Repositories
目前支持的响应式数据库有 Mongo,Cassandra,Redis,Couchbase,R2dbc ,目前r2dbc支持以下数据库
R2DBC
接下来讲述,WebFlux 如何集成R2DBC Mysql
依赖配置
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--r2dbc mysql 库-->
<dependency>
<groupId>dev.miku</groupId>
<artifactId>r2dbc-mysql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
数据库配置
spring:
r2dbc:
url: r2dbc:mysql://127.0.0.1:3306/webflux?characterEncoding=utf-8&useSSL=false
username: root
password: root
application:
name: webflux
接着就可以正常像mvc一样开发了
示例项目
webflux+curd+r2dbc完整示例
https://gitee.com/chenzhehome/webflux-r2dbc-curd.git
总结
当一个网络请求到容器时,容器会为每一个请求分配线程去处理,线程会调用对应的servlet处理,当使用同步servlet时,业务逻辑的用了多久,servlet线程就要等待多久,而异步servlet是不需要等待的.他可以同时处理多个.只不过当业务逻辑完成时通过发布订阅的关系返回给他.
mvc和wedflux没有好坏之分,只不过webflux更适合处理高并发的场景,但webflux的异常排查,暂时没有太好的办法,如果不是高并发场景个人觉得还是mvc好一点.