关注「Java视界」公众号,获取更多技术干货

响应式编程将成主流?啥是响应式编程?

一、响应式编程解决什么问题?

Spring framework 5 最大的变化就是引入了响应式编程Reactive Programming)。

Spring 为啥要引入这个特性?

八个字:“逆水行舟,不进则废”!

在2009 年,微软提出了响应式编程,旨在应对高并发服务器端开发场景。随后各语言很快跟进,都拥有了属于自己的响应式编程实现,比如Go、Node 等新语言。Java 作为服务器端开发语言老大的地位受到了不小的挑战,后有追兵,不得不进化啊。

所以,响应式编程的初心,就是去满足高并发下的服务器端开发任务

二、Java的响应式编程原理

Java的响应式编程是借助MinaNetty 这样的NIO 框架。

说到NIO,可以参考:什么是阻塞和非阻塞?什么是同步和异步?什么是BIO、NIO、AIO?

鉴于以上的NIO知识,简单地说:

当我们调用socket.read()、socket.write()这类阻塞函数的时候,这类函数不能立即返回,也无法中断,需要等待socket可读或者可写,才会返回,因此一个线程只能处理一个请求。在这等待的过程中,cpu并不干活,(即阻塞住了),那么cpu的资源就没有很好地利用起来。因此对于这种情况,我们使用多线程来提高cpu资源的利用率:在等待的这段时间,就可以切换到别的线程去处理事件,直到socket可读或可写了,通过中断信号通知cpu,再切换回来继续处理数据。例如线程A正在等待socket可读,而线程B已经就绪了,那么就可以先切换到线程B去处理。虽然上下文切换也会花一些时间,但是远比阻塞在线程A这里空等要好。当然计算机内部实际的情况比这复杂得多。

而NIO的读写函数可以立刻返回,这就给了我们不开线程利用CPU的最好机会:如果一个连接不能读写(socket.read()返回0或者socket.write()返回0),我们可以把这件事记下来。因此只需要一个线程不断地轮询这些事件,一旦有就绪的时间,处理即可。不需要多线程。

即,阻塞型IO

  • 需要多线程,即需要很大的线程池。
  • 每个请求都要有一个单独的线程去处理。

非阻塞型IO

  • 只需要数量非常少的线程。
  • 固定的几个工作线程去处理事件。

因此,基于NIO的响应式编程就是,当你做一个带有一定延迟的才能够返回的io操作时,不会阻塞,而是立刻返回一个流,并且订阅这个流,当这个流上产生了返回数据,可以立刻得到通知并调用回调函数处理数据。

但原生的Netty技术只掌握在少数高级开发人员手中,因为它们难度较大,并不适合大部分普通开发者。不过Spring 5 给我们做了很好的封装,借助基本的几个API就能实现响应式编程。Java 领域的响应式编程库中,最有名的算是 Reactor 了。Reactor 也是 Spring 5 中反应式编程的基础,Webflux 依赖 Reactor 而构建。

Reactor 是一个基于 JVM 之上的异步应用基础库。为 Java 、Groovy 和其他 JVM 语言提供了构建基于事件和数据驱动应用的抽象库。Reactor 性能相当高,在最新的硬件平台上,使用无堵塞分发器每秒钟可处理 1500 万事件。

三、响应式编程模型在这里插入图片描述

首先需要理解响应式编程的基本模型,如上。

Flux 和 Mono

Reactor 中的发布者(Publisher)由FluxMono两个类定义,它们都提供了丰富的操作符(operator)。一个Flux对象代表一个包含N个元素的响应式序列,元素可以是普通对象、数据库查询的结果、http响应体,甚至是异常。Mono 表示的是包含 0 或者 1 个元素的异步序列。该序列中同样可以包含与 Flux 相同的三种类型的消息通知。Flux 和 Mono 之间可以进行转换。对一个 Flux 序列进行计数操作,得到的结果是一个 Mono对象。把两个 Mono 序列合并在一起,得到的是一个 Flux 对象。上图就是一个Flux类型的数据流,Flux往流上发送了3个元素,Subscriber通过订阅这个流来接收通知。

因此,响应式编程是基于发布订阅模式的,或者是基于事件的。(这里涉及到背压的概念,背压是指订阅者能和发布者交互,可以调节发布者发布数据的速率,解决把订阅者压垮的问题。订阅者一般有request和cancel 两个方法,用于通知发布者需要数据和通知发布者不再接受数据。)

四、Spring中的响应式编程代码示例

依赖:

<dependencies>
    <dependency>
        <groupId>io.projectreactor</groupId>
        <artifactId>reactor-core</artifactId>
        <version>3.2.3.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>io.projectreactor</groupId>
        <artifactId>reactor-test</artifactId>
        <version>3.2.3.RELEASE</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Flux

FluxMono作为“数据流”的发布者,都可以发出三种“数据信号”:元素值错误信号完成信号。当消息通知产生时,订阅者中对应的方法 onNext(), onComplete()onError()会被调用。

//创建一个流,并直接往流上发布一个值为value数据
Flux.just(value);

//通过list创建一个流,往流上依次发布list中的数据
Flux.fromIterable(list);

//创建一个流,并向流上从i开始连续发布n个数据,数据类型为Integer
Flux.range(i, n);

//创建一个流,并定时向流上发布一个数据,数据从0开始递增,数据类型为Long
Flux.interval(Duration.ofSeconds(n));

调用 just 或者其他方法只是声明数据流,数据流并没有发出,只有进行订阅之后才会触发数据流,不订阅什么都不会发生的。

Subscriber

subscriber是一个订阅者,只有非常简单的4个接口,主要是对应发布者的三种数据信号:

public interface Subscriber<T> {
    void onSubscribe(Subscription var1);

    //收到下一个元素值信号时的行为
    void onNext(T var1);

    //收到错误信号时的行为
    void onError(Throwable var1);

    //收到终止信号时的行为
    void onComplete();
}

Subscriber必须要订阅一个Flux才能够接收通知:

flux.subscribe(
    value -> handleData(value),
    error -> handleError(error),
    () -> handleComplete()
);

上面这个例子通过lambda表达式,定义了Subscriber分别在收到消息,收到错误,和消息流结束时的行为,当Subscriber接收到一个新数据,就会异步地执行handleData方法处理数据。

五、WebFlux 是什么?

在这里插入图片描述
WebFlux 模块的名称是 spring-webflux,名称中的 Flux 来源于 Reactor 中的类 FluxSpring webflux 有一个全新的非堵塞的函数式 Reactive Web 框架,可以用来构建异步的、非堵塞的、事件驱动的服务,在伸缩性方面表现非常好。

如图所示,WebFlux 模块从上到下依次是 Router FunctionsWebFluxReactive Streams 三个新组件。

  • Router Functions 对标准的 @Controller,@RequestMapping 等的 Spring MVC 注解,提供一套 函数式风格的 API,用于创建 Router、Handler 和Filter。
  • WebFlux 核心组件,协调上下游各个组件提供 响应式编程 支持。
  • Reactive Streams 一种支持 背压 (Backpressure) 的 异步数据流处理标准,主流实现有 RxJava 和Reactor,Spring WebFlux 集成的是 Reactor。

默认情况下,Spring Boot 2 使用 Netty WebFlux,因为 Netty 在异步非阻塞空间中被广泛使用,Netty 是高性能的 NIO 框架,异步非阻塞的框架,异步非阻塞连接可以节省更多的资源,提供更高的响应度。值得注意的是:支持 reactive 编程的数据库只有 MongoDB, redis, Cassandra, Couchbase。

一般来说,Spring MVC 用于同步处理,Spring Webflux 用于异步处理
SpringMVC 采用命令式编程:一行一行执行。Webflux 采用异步响应式编程,因此使用在gateway网关服务场景。

Spring Boot Webflux 有两种编程模型实现,一种类似 Spring MVC 注解方式,另一种是基于 Reactor 的响应式方式。

代码演示:

@RestController
public class HelloController {

    @GetMapping("/hello")
    public Mono<String> hello() {
        return Mono.just("Welcome to reactive world ~");
    }
}

通过上面的示例可以发现,开发模式和之前 Spring Mvc 的模式差别不是很大,只是在方法的返回值上有所区别。just() 方法可以指定序列中包含的全部元素。响应式编程的返回值必须是 Flux 或者 Mono ,前面说过两者之间可以相互转换。

@RunWith(SpringRunner.class)
@WebFluxTest(controllers = HelloController.class)
public class HelloTests {
    @Autowired
    WebTestClient client;

    @Test
    public void getHello() {
        client.get().uri("/hello").exchange().expectStatus().isOk();
    }
}

SpringWebflux 执行过程和 SpringMVC 相似的:SpringMVC 核心控制器 DispatchServletSpringWebflux 核心控制器 DispatchHandler

Webflux 特点:

  1. 非阻塞式:在有限资源下,提高系统吞吐量和伸缩性,以 Reactor 为基础实现响应式编程。
  2. 函数式编程:Spring5 框架基于 java8,Webflux 使用 Java8 函数式编程方式实现路由请求。

六、WebClient 是什么?

WebClient是从Spring WebFlux 5.0版本开始提供的一个非阻塞的基于响应式编程的进行Http请求的客户端工具。它的响应式编程的基于Reactor的。WebClient中提供了标准Http请求方式对应的getpostputdelete等方法,可以用来发起相应的请求。

举个完整的例子:

@Data
@AllArgsConstructor
public class User {
 
    private String name;
    private String gender;
    private Integer age;
}
//用户操作接口
public interface UserService {
 
    //根据id查用户。mono返回0-1
    Mono<User> getUserById(int id);
 
    //查询所有用户,flux返回多个
    Flux<User> getAllUser();
 
    //添加用户。mono返回0-1
    Mono<Void> saveUserInfo(Mono<Void> user);
}
public class UserHandler {
 
    private final UserService userService;
 
    public UserHandler(UserService userService) {
        this.userService = userService;
    }
 
    //根据 id 查询
    public Mono<ServerResponse> getUserById(ServerRequest request) {
        //获取 id 值
        int userId = Integer.valueOf(request.pathVariable("id"));
 
        //空值处理
        Mono<ServerResponse> notFound = ServerResponse.notFound().build();
 
        //调用 service 方法得到数据
        Mono<User> userMono = this.userService.getUserById(userId);
        //把 userMono 进行转换返回
        //使用 Reactor 操作符 flatMap(对象变成流)
        return userMono.flatMap(person ->
            ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
                    .body(fromObject(person))
                    .switchIfEmpty(notFound));
    }
 
    //查询所有
    public Mono<ServerResponse> getAllUsers() {
        //调用 service 得到结果
        Flux<User> users = this.userService.getAllUser();
        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users,User.class);
    }
    
    //添加
    public Mono<ServerResponse> saveUser(ServerRequest request) {
        //得到 user 对象
        Mono<User> userMono = request.bodyToMono(User.class);
        return ServerResponse.ok().build(this.userService.saveUserInfo(userMono));
    } 
}
public class Server {
    //1 创建 Router 路由
    public RouterFunction<ServerResponse> routingFunction(){
        //创建 handler 对象
        UserService userService = new UserServiceImpl();
        UserHandler handler = new UserHandler(userService);
        //设置路由
        return RouterFunctions.route(
                GET("/users/{id}").and(accept(APPLICATION_JSON)),handler::getUserById)
                .andRoute(GET("/users").and(accept(APPLICATION_JSON)),handler::getAllUsers);
    }
}

    //2 创建服务器完成适配
    public void createReactorServer() {
        //路由和 handler 适配
        RouterFunction<ServerResponse> route = routingFunction();
        HttpHandler httpHandler = toHttpHandler(route);
        ReactorHttpHandlerAdapter adapter = new
                ReactorHttpHandlerAdapter(httpHandler);
        //创建服务器
        HttpServer httpServer = HttpServer.create();
        httpServer.handle(adapter).bindNow();
    }
public class WebClientTest {
    public static void main(String[] args) {
        WebClient webClient = WebClient.create("http://127.0.0.1:50066");
     
        String id = "1";
        User u = webClient.get().uri("/users/{id}",id)
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(User.class)
                .block();
        System.out.println(u.getName());//lucy
       
        //查询所有
        Flux<User> results = webClient.get().uri("/users")
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToFlux(User.class);
        results.map(stu -> stu.getName())
               .buffer().doOnNext(System.out::println).blockFirst();
        //[lucy, mary, jack]
    }
}
posted @ 2022-06-25 14:01  沙滩de流沙  阅读(670)  评论(0编辑  收藏  举报

关注「Java视界」公众号,获取更多技术干货