WebFlux入门

3,257 阅读6分钟

这是我参与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】响应式的、异步非阻塞

img

响应式编程

响应式编程(Reactive Programming) 是一种基于数据流(data stream)变化传递(propagation of change)声明式(declarative) 的编程范式。

就定义而言,难免有点晦涩和抽象,以下对一些名词进行详细说明。

变化传递

就平时我们习惯的编程模式——命令式编程而言,对于式子c=a+b而言,c的值是由式子计算时刻,ab的和,后续ab的变化,并不会导致c值的变化。

而对于响应式编程来说,同样对于式子c=a+b而言,后续ab的变化,会导致c值的变化。

这就是所谓的变化传递

数据流

这里的数据流并不是指JDK8中的流编程,而是指JDK9中的响应式流(Reactive Streams)

所谓响应式流(Reactive Streams) ,是指通过定义一组实体,接口和互操作方法,给出了实现异步非阻塞背压的标准。第三方遵循这个标准来实现具体的解决方案,常见的有Reactor,RxJava,Akka Streams,Ratpack等。

其主要的接口有这三个:

  • Publisher
  • Subscriber
  • Subcription

其中,发布者(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的返回类型有两种:MonoFlux

FluxMono 都实现了Reactor的Publisher接口,属于发布者,对消费者提供订阅接口,当有事件发生的时候,Flux或者Mono会通过回调消费者的相应的方法来通知消费者相应的事件。这就是所谓的响应式编程模型。

其中,Mono 是一个发出0-1个元素的Publisher<T>,可以被onComplete信号或者onError信号所终止。

FluxMono类似,是一个发出0-N个元素组成的异步序列的Publisher<T>,可以被onComplete信号或者onError信号所终止。在响应流规范中存在三种给下游消费者调用的方法 onNext, onComplete, 和onError。下面这张图表示了Flux的抽象模型:

img

请求响应参数

在WebFlux中,请求和响应不再是WebMVC中的ServletRequestServletResponse,而是ServerRequestServerResponse。后者是在响应式编程中使用的接口,它们提供了对非阻塞和背压特性的支持,以及Http消息体与响应式类型Mono和Flux的转换方法。

使用场景

综上所述,Spring WebFlux 是一个异步非阻塞式的 Web 框架,响应式和非阻塞并不是总能让应用跑的更快,况且将代码构建为非阻塞的执行方式本身还会带来少量的成本。但是在类似于WEB应用这样的高并发、少计算且I/O密集的应用中,响应式和非阻塞往往能够发挥出价值。尤其是微服务应用中,网络I/O比较多的情况下,效果会更加惊人。所以,它适合应用在 IO 密集型的服务中,目前最常见的应用场景就是网关

需要注意的是控制层一旦使用 Spring WebFlux,它下面的安全认证层、数据访问层都必须使用 Reactive API。其次,Spring Data Reactive Repositories 目前只支持 MongoDBRedisCouchbase 等几种不支持事务管理的 NOSQL。技术选型时需要权衡这些弊端和风险。