这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战
前些日子在学习Spring cloud gateway,由于Spring cloud gateway基于WebFlux,自己对WebFlux不太懂,特此记录学习。
WebFlux是什么
Spring WebFlux 是 Spring Framework 5.0中引入的新的响应式web框架,其实现基于Reactor 框架。与Spring MVC不同,它不需要Servlet API,是异步非阻塞的,并且通过Reactor项目实现了Reactive Streams规范。
同步和异步: 同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发IO 操作并等待或者轮询的去查看IO 操作是否就绪,而异步是指用户进程触发IO 操作以后便开始做自己的事情,而当IO 操作已经完成的时候会得到IO 完成的通知。
同步异步是针对调用者来说的,调用者发起一个请求后,一直干等被调用者的反馈就是同步,不必等去做别的事就是异步。
阻塞和非阻塞: 阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作方法的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入方法会立即返回一个状态值。
阻塞非阻塞是针对被调用者来说的,被调用者收到一个请求后,做完请求任务后才给出反馈就是阻塞,收到请求直接给出反馈再去做任务就是非阻塞。
与传统的Spring MVC相比,Spring WebFlux有以下的不同:
【spring-webmvc + Servlet + Tomcat】命令式的、同步阻塞的
【spring-webflux + Reactor + Netty】响应式的、异步非阻塞的
响应式编程
响应式编程(Reactive Programming) 是一种基于数据流(data stream) 和变化传递(propagation of change) 的声明式(declarative) 的编程范式。
就定义而言,难免有点晦涩和抽象,以下对一些名词进行详细说明。
变化传递
就平时我们习惯的编程模式——命令式编程而言,对于式子c=a+b而言,c的值是由式子计算时刻,a与b的和,后续a与b的变化,并不会导致c值的变化。
而对于响应式编程来说,同样对于式子c=a+b而言,后续a与b的变化,会导致c值的变化。
这就是所谓的变化传递。
数据流
这里的数据流并不是指JDK8中的流编程,而是指JDK9中的响应式流(Reactive Streams) 。
所谓响应式流(Reactive Streams) ,是指通过定义一组实体,接口和互操作方法,给出了实现异步非阻塞背压的标准。第三方遵循这个标准来实现具体的解决方案,常见的有Reactor,RxJava,Akka Streams,Ratpack等。
其主要的接口有这三个:
PublisherSubscriberSubcription
其中,发布者(Publisher)主动地推送数据给订阅者(Subscriber),触发 onNext 方法。异常和完成时触发onError(Exception)和onCompleted()方法。
如果 Publisher 发布消息太快了,超过了 Subscriber 的处理速度,此时,Reactive Programming 框架提供了一个机制Subcription,使得 Subscriber 能够控制消费消息的速度,从而避免生产者生产数据多了,就把消费者给压垮的情况发生。这就是背压 (Backpressure) 的由来。也就是说,系统会在它的请求buffer被充满时,将其推送会给发送者,让发送者稍后再试,或者使用其他接收器,这就能确保发送者和接收者之间的管道不会被充满,这样才有机会获得一个响应式系统。
使用示例
WebFlux可使用注解方式进行使用,使用方式与Spring MVC差不多。
首先,需要将spring-boot-starter-web依赖更改成spring-boot-starter-webflux
pom.xml
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-web</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
接着就可以在代码中编写相应代码了。
示例如下
@RequestMapping("/demo")
@RestController
public class DemoController {
@RequestMapping(value = "/hello")
public Mono<String> foobar() throws InterruptedException {
Thread.sleep(1000);
return Mono.just("Hello!");
}
/**
* Flux : 返回0-n个元素
* 注:需要指定MediaType
* @return
*/
@GetMapping(value = "/test/{num}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
private Flux<String> flux(@PathVariable String num) {
Flux<String> result = Flux
.fromStream(IntStream.range(1, Integer.valueOf(num)).mapToObj(i -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
return "flux data--" + i;
}));
return result;
}
}
其中,与Spring MVC不同的主要有两个地方:返回类型与请求响应参数。
返回类型
WebFlux的返回类型有两种:Mono和Flux。
Flux和Mono 都实现了Reactor的Publisher接口,属于发布者,对消费者提供订阅接口,当有事件发生的时候,Flux或者Mono会通过回调消费者的相应的方法来通知消费者相应的事件。这就是所谓的响应式编程模型。
其中,Mono 是一个发出0-1个元素的Publisher<T>,可以被onComplete信号或者onError信号所终止。
而Flux 与Mono类似,是一个发出0-N个元素组成的异步序列的Publisher<T>,可以被onComplete信号或者onError信号所终止。在响应流规范中存在三种给下游消费者调用的方法 onNext, onComplete, 和onError。下面这张图表示了Flux的抽象模型:
请求响应参数
在WebFlux中,请求和响应不再是WebMVC中的ServletRequest和ServletResponse,而是ServerRequest和ServerResponse。后者是在响应式编程中使用的接口,它们提供了对非阻塞和背压特性的支持,以及Http消息体与响应式类型Mono和Flux的转换方法。
使用场景
综上所述,Spring WebFlux 是一个异步非阻塞式的 Web 框架,响应式和非阻塞并不是总能让应用跑的更快,况且将代码构建为非阻塞的执行方式本身还会带来少量的成本。但是在类似于WEB应用这样的高并发、少计算且I/O密集的应用中,响应式和非阻塞往往能够发挥出价值。尤其是微服务应用中,网络I/O比较多的情况下,效果会更加惊人。所以,它适合应用在 IO 密集型的服务中,目前最常见的应用场景就是网关。
需要注意的是控制层一旦使用 Spring WebFlux,它下面的安全认证层、数据访问层都必须使用 Reactive API。其次,Spring Data Reactive Repositories 目前只支持 MongoDB、Redis 和 Couchbase 等几种不支持事务管理的 NOSQL。技术选型时需要权衡这些弊端和风险。