反应式编程

615 阅读12分钟

前言

本文主要介绍反应式编程(Reactive Programming,又称为响应式编程。本文统称为反应式编程),比如Java中的反应式编程,Spring 5 WebFlux的反应式编程。

反应式编程的目的是为了方便创建异步编程。异步的概念就是请求的结果并不会立即返回,而是会通过回调或者推的方式来实现。而反应式编程就是采取了推的方式。而反应式编程为了方便这个"推"的效果,采取了数据流的设计,也就是所有的代码都是在定义数据的流向,当你的代码完成后,数据的流向也就确定了。那么当数据产生后,就会按照制定的流程流向订阅者。

1、反应式编程基础

1.1、举例

反应式编程来源于数据流和变化的传播,意味着由底层的执行模型负责通过数据流来自动传播变化。比如求值一个简单的表达式 c=a+b,当 a 或者 b 的值发生变化时,传统的编程范式需要对 a+b 进行重新计算来得到 c 的值。如果使用反应式编程,当 a 或者 b 的值发生变化时,c 的值会自动更新。

1.2、规范

  • 反应式流必须是无阻塞的
  • 反应式流必须是一个数据流
  • 必须可以异步执行
  • 必须可以处理负压(参考下面的说明)

2、RxJava

反应式编程最早由 .NET 平台上的 Reactive Extensions (Rx) 库来实现。后来迁移到 Java 平台之后就产生了著名的 RxJava 库,并产生了很多其他编程语言上的对应实现。在这些实现的基础上产生了后来的反应式流(Reactive Streams)规范。该规范定义了反应式流的相关接口,并将集成到 Java 9 中。

CompletableFuture提供了响应式编程的实现方式,具体使用可以参考作者的多线程编程

注意:RxJava适合复杂业务的实现,比如To B的业务等,可以提供代码的易读性等

3、Reactor

前面提到的 RxJava 库是 JVM 上反应式编程的先驱,也是反应式流规范的基础。RxJava 2 在 RxJava 的基础上做了很多的更新。不过 RxJava 库也有其不足的地方。RxJava 产生于反应式流规范之前,虽然可以和反应式流的接口进行转换,但是由于底层实现的原因,使用起来并不是很直观。RxJava 2 在设计和实现时考虑到了与规范的整合,不过为了保持与 RxJava 的兼容性,很多地方在使用时也并不直观。Reactor 则是完全基于反应式流规范设计和实现的库,没有 RxJava 那样的历史包袱,在使用上更加的直观易懂。Reactor 也是 Spring 5 中反应式编程的基础。学习和掌握 Reactor 可以更好地理解 Spring 5 中的相关概念。

3.1、基本数据类型

Reactor为了能够产生上面所说的反应式流(数据流),提供了两种基本的数据类型来实现,分别是Flux和Mono。

3.1.1、Flux

Flux是可以产生0到N个元素的异步数据流。Flux可以弹出数据进行操作,处理的结果也会组成一个新的数据流。最终被正常或者异常终止。

图片.png

3.1.2、Mono

Mono是可以产生0到1个元素的异步数据流。和Flux数据流一样,Mono数据流中的数据可以被处理,也会正常或异常终止。

图片.png

3.1.3、二者区别

我们是可以只使用Flux,但是很多操作我们知道只返回一个或者没有,而且反应式编程也为Mono提供了很多独有的用法。所以我们使用Mono是为了方便我们的编程。

3、WebFlux

Spring Framework中包含的原始Web框架Spring MVC,它是专门为Servlet API和Servlet容器构建的,还包含响应式堆栈Web框架Spring WebFlux(在5.0版本中添加),它是完全非阻塞的,支持Reactive Streams背压(背压是指订阅者能和发布者交互,可以调节发布者发布数据的速率,解决把订阅者压垮的问题),并在Netty、Undertow和Servlet 3.1+容器等服务器上运行。

image.png

3.1、优点

  1. 少量的线程支持更多的处理;
  2. 函数式编程。对于允许异步逻辑的,声明式组合的非阻塞应用程序,和延续式API(由CompletableFuture和ReactiveX推广)是一个福音。 注意:WebFlux 并不能使接口的响应时间缩短,它仅仅能够提升吞吐量和伸缩性。

3.2、应用场景

它特别适合应用在IO密集型的服务中,IO密集型包括:磁盘IO密集型, 网络IO密集型,微服务网关就属于网络IO密集型,使用异步非阻塞式编程模型,能够显著地提升网关对下游服务转发的吞吐量。

3.3、MVC和WebFlux

3.3.1、技术场景使用图:

Webflux的核心库Reactor API,它与MVC区别:

  1. Publisher使用的是Mono/Flux;
  2. 同时支持注解和函数式编程两种模式:
    • 函数式使用RouterFunction (路由函数)和HandlerFunction(处理函数)开发接口函数式Demo
    • 注解式类似于Spring MVC使用注解开发接口,注解式Demo

3.3.2、如何选择

WebFlux 不是 Spring MVC 的替代方案!虽然 WebFlux 也可以被运行在 Servlet 容器上(需是 Servlet 3.1+ 以上的容器),但是 WebFlux 主要还是应用在异步非阻塞编程模型,而 Spring MVC 是同步阻塞的,如果你目前在 Spring MVC 框架中大量使用非同步方案,那么,WebFlux 才是你想要的,否则,使用 Spring MVC 才是你的首选。

在微服务架构中,Spring MVC 和 WebFlux 可以混合使用,比如对于那些 IO 密集型服务(如网关),我们就可以使用 WebFlux 来实现。

4、WebFlux请求处理流程

4.1、注解式

先看看Spring MVC的请求处理流程: 图片.png Spring WebFlux的请求处理流程: 图片.png 我们可以看到,处理流程基本一样,只是处理对象不同: 图片.png 其中最主要的不同处理对象有:

  • 处理核心
    • WebFlux--DispatcherHandler
    • SpringMvc--DispatcherServlet
  • 返回值处理器
    • WebFlux--HandlerResultHandler
    • SpringMvc--HandlerMethodReturnValueHandler
  • 内容协商配置器
    • WebFlux--RequestedContentTypeResolverBuilder
    • SpringMvc--ContentNegotiationConfigurer

4.1.1、核心控制器DispatcherHandler

核心控制器DispatcherHandler,等同于阻塞方式的DispatcherServlet,DispatcherHandler实现ApplicationContextAware,那么必然会调用setApplicationContext方法。setApplicationContext方法源码如下:

public class DispatcherHandler implements WebHandler, ApplicationContextAware {
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        initStrategies(applicationContext);
    }
}

initStrategies方法是初始化,获取HandlerMapping,HandlerAdapter,HandlerResultHandler的所有实例,源码如下:

protected void initStrategies(ApplicationContext context) {
    //获取HandlerMapping及其子类型的bean
    //HandlerMapping根据请求request获取handler执行链
    Map<String, HandlerMapping> mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
            context, HandlerMapping.class, true, false);
 
    ArrayList<HandlerMapping> mappings = new ArrayList<>(mappingBeans.values());
    //排序
    AnnotationAwareOrderComparator.sort(mappings);
    this.handlerMappings = Collections.unmodifiableList(mappings);
 
    //获取HandlerAdapter及其子类型的bean
    Map<String, HandlerAdapter> adapterBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
            context, HandlerAdapter.class, true, false);
 
    this.handlerAdapters = new ArrayList<>(adapterBeans.values());
    //排序
    AnnotationAwareOrderComparator.sort(this.handlerAdapters);
 
    //获取HandlerResultHandler及其子类型的bean
    Map<String, HandlerResultHandler> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
            context, HandlerResultHandler.class, true, false);
 
    this.resultHandlers = new ArrayList<>(beans.values());
    AnnotationAwareOrderComparator.sort(this.resultHandlers);
}

DispatcherHandler的总体流程:

  1. 通过 HandlerMapping(和DispathcherServlet中的HandlerMapping不同)获取到HandlerAdapter放到ServerWebExchange的属性中;
  2. 获取到HandlerAdapter后触发handle方法,得到HandlerResult;
  3. 通过HandlerResult,触发handleResult,针对不同的返回类找到不同的HandlerResultHandler如视图渲染ViewResolutionResultHandler、ServerResponseResultHandler、ResponseBodyResultHandler、ResponseEntityResultHandler不同容器有不同的实现,如Reactor,Jetty,Tomcat等。

4.1.2、HandlerMapping

webflux中引入了一个新的HandlerMapping,即RouterFunctionMapping,它实现了InitializingBean,因此在其实例化的时候,会调用afterPropertiesSet方法。源码如下:

public class RouterFunctionMapping extends AbstractHandlerMapping implements InitializingBean {
 
   @Nullable
   private RouterFunction<?> routerFunction;
    //省略部分代码
    
    //afterPropertiesSet()方法 是组件初始化后回调 必须实现InitializingBean接口
    //
   @Override
   public void afterPropertiesSet() throws Exception {
      if (CollectionUtils.isEmpty(this.messageReaders)) {
         ServerCodecConfigurer codecConfigurer = ServerCodecConfigurer.create();
         this.messageReaders = codecConfigurer.getReaders();
      }
 
      //初始化routerFunction
      if (this.routerFunction == null) {
         initRouterFunctions();
      }
   }
 
   /**
    * Initialized the router functions by detecting them in the application context.
    * 从应用上下文中查找他们并初始化路由方法
    */
   protected void initRouterFunctions() {
      if (logger.isDebugEnabled()) {
         logger.debug("Looking for router functions in application context: " +
               getApplicationContext());
      }
 
      //查找合并所有路由方法的bean
      List<RouterFunction<?>> routerFunctions = routerFunctions();
      if (!CollectionUtils.isEmpty(routerFunctions) && logger.isInfoEnabled()) {
         routerFunctions.forEach(routerFunction -> logger.info("Mapped " + routerFunction));
      }
       
      //将一个请求中含有多个路由请求方法合并成一个方法
      this.routerFunction = routerFunctions.stream()
            .reduce(RouterFunction::andOther)
            .orElse(null);
   }
 
    //查找并合并所有路由方法
   private List<RouterFunction<?>> routerFunctions() {
       //声明 SortedRouterFunctionsContainer bean
      SortedRouterFunctionsContainer container = new SortedRouterFunctionsContainer();
       //自动注入到上下文中 
      obtainApplicationContext().getAutowireCapableBeanFactory().autowireBean(container);
      //返回路由
      return CollectionUtils.isEmpty(container.routerFunctions) ? Collections.emptyList() :
            container.routerFunctions;
   }
    //省略部分代码
   private static class SortedRouterFunctionsContainer {
 
      @Nullable
      private List<RouterFunction<?>> routerFunctions;
 
       //由上面的方法 自动注入bean时实现依赖查找,查找所有的 RouterFunction beans
       //并注入到 List<RouterFunction<?>> 中。这样就会得到所有实现路由方法的集合
      @Autowired(required = false)
      public void setRouterFunctions(List<RouterFunction<?>> routerFunctions) {
         this.routerFunctions = routerFunctions;
      }
   }
 
}

4.1.3、HandlerAdapter

webflux中引入了一个新的HandlerAdapter,即HandlerFunctionAdapter。

webflux中引入了一个新的HandlerResultHandler,即ServerResponseResultHandler,它实现了InitializingBean,因此在其实例化的时候,会调用afterPropertiesSet方法。

流式处理请求handler(),源码如下:

@Override
public Mono<Void> handle(ServerWebExchange exchange) {
    //handlerMappings在initStrategies()方法中已经构造好了
    if (this.handlerMappings == null) {
        return createNotFoundError();
    }
    //构造Flux,数据源为handlerMappings集合
    return Flux.fromIterable(this.handlerMappings)
            //获取Mono<Handler>对象,通过concatMap保证顺序和handlerMappings顺序一致
            //严格保证顺序是因为在一个系统中可能存在一个Url有多个能够处理的HandlerMapping的情况
            .concatMap(mapping -> mapping.getHandler(exchange))
            .next()
            //如果next()娶不到值则抛出错误
            .switchIfEmpty(createNotFoundError())
            //触发HandlerApter的handle方法
            .flatMap(handler -> invokeHandler(exchange, handler))
            //触发HandlerResultHandler 的handleResult方法
            .flatMap(result -> handleResult(exchange, result));
}

触发HandlerApter的handle方法

private Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
    return getResultHandler(result).handleResult(exchange, result)
            .onErrorResume(ex -> result.applyExceptionHandler(ex).flatMap(exceptionResult ->
                    getResultHandler(exceptionResult).handleResult(exchange, exceptionResult)));
}
 
private HandlerResultHandler getResultHandler(HandlerResult handlerResult) {
    if (this.resultHandlers != null) {
        for (HandlerResultHandler resultHandler : this.resultHandlers) {
            if (resultHandler.supports(handlerResult)) {
                return resultHandler;
            }
        }
    }
    throw new IllegalStateException("No HandlerResultHandler for " + handlerResult.getReturnValue());
}

4.2、函数式

流程图: 图片.png 通过上图,我们可以看到,这个处理跟之前的注解驱动请求大有不同,但是请求的流程是万变不离其宗,只是组件有所变化。接下来我们就跟着流程图一步一步的来解读WebFlux函数端点式请求的源码。

4.2.1、装配阶段

由上图我们可以看到 RouterFunctionMapping是由WebFluxConfigurationSupport创建的,接下来看一下RouterFunctions是怎么合并RouterFunction的并且如何关联到RouterFunctionMapping的。RouterFunctionMapping 的源码,在4.1.2已经介绍了。

4.2.2、请求阶段

请求阶段的核心代码就是 org.springframework.web.reactive.DispatcherHandler#handle方法,我们来看一下源码。

@Override
public Mono<Void> handle(ServerWebExchange exchange) {
   if (logger.isDebugEnabled()) {
      ServerHttpRequest request = exchange.getRequest();
      logger.debug("Processing " + request.getMethodValue() + " request for [" + request.getURI() + "]");
   }
   if (this.handlerMappings == null) {
      return Mono.error(HANDLER_NOT_FOUND_EXCEPTION);
   }
   // 1.HTTP请求进来后执行的流程
   return Flux.fromIterable(this.handlerMappings)  //2 遍历handlerMappings定位RouterFunctionMapping
         .concatMap(mapping -> mapping.getHandler(exchange))   // 3.获取HandlerFunction
         .next()
         .switchIfEmpty(Mono.error(HANDLER_NOT_FOUND_EXCEPTION))  
         .flatMap(handler -> invokeHandler(exchange, handler))   //4.执行
         .flatMap(result -> handleResult(exchange, result));  //5. 处理结果
}

上面的代码已经把大部分的流程说明清楚了,那么我们来看一下lambda表达式中每个内部方法的具体实现。

首先我们来看一下步骤3的具体实现 org.springframework.web.reactive.handler.AbstractHandlerMapping#getHandler

@Override
public Mono<Object> getHandler(ServerWebExchange exchange) {
    //调用 getHandlerInternal 方法来确定HandlerFunction
   return getHandlerInternal(exchange).map(handler -> {
      if (CorsUtils.isCorsRequest(exchange.getRequest())) {
         CorsConfiguration configA = this.globalCorsConfigSource.getCorsConfiguration(exchange);
         CorsConfiguration configB = getCorsConfiguration(handler, exchange);
         CorsConfiguration config = (configA != null ? configA.combine(configB) : configB);
         if (!getCorsProcessor().process(config, exchange) ||
               CorsUtils.isPreFlightRequest(exchange.getRequest())) {
            return REQUEST_HANDLED_HANDLER;
         }
      }
      return handler;
   });
}

上面一大段代码其实主要来获取handler的方法是 getHandlerInternal(exchange) 剩下的部分是跨域处理的逻辑。我们看一下这个方法。

@Override
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
   if (this.routerFunction != null) {
      ServerRequest request = ServerRequest.create(exchange, this.messageReaders);
      exchange.getAttributes().put(RouterFunctions.REQUEST_ATTRIBUTE, request);
      return this.routerFunction.route(request);  //通过路由获取到对应处理的HandlerFunction 也就是执行方法
   }
   else {
      return Mono.empty();
   }
}

获取到对应的HandlerFunction后我们就来执行第四步,调用HandlerFunction

private Mono<HandlerResult> invokeHandler(ServerWebExchange exchange, Object handler) {
   if (this.handlerAdapters != null) {
      for (HandlerAdapter handlerAdapter : this.handlerAdapters) {
         if (handlerAdapter.supports(handler)) {  //判断HandlerAdapters中是否支持之前获取到的handler
            return handlerAdapter.handle(exchange, handler);  //执行handler 对应下面handle的方法
         }
      }
   }
   return Mono.error(new IllegalStateException("No HandlerAdapter: " + handler));
}

org.springframework.web.reactive.function.server.support.HandlerFunctionAdapter#handle方法,这个类中的方法就是处理函数式端点请求的Adapter具体实现

@Override
public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
   HandlerFunction<?> handlerFunction = (HandlerFunction<?>) handler;
   ServerRequest request = exchange.getRequiredAttribute(RouterFunctions.REQUEST_ATTRIBUTE);
   return handlerFunction.handle(request)   //由lambda模式 (返回值-参数)  无需准确的方法签名
         .map(response -> new HandlerResult(handlerFunction, response, HANDLER_FUNCTION_RETURN_TYPE));   //返回HandlerResult对象 
}

这里的lambda模式比较难理解,主要是看HandlerFunction这个函数式接口

@FunctionalInterface
public interface HandlerFunction<T extends ServerResponse> {
 
   /**
    * Handle the given request.
    * @param request the request to handle
    * @return the response
    */
   Mono<T> handle(ServerRequest request);
 
}

我们只需要满足入参是ServerRequest类型 返回值是Mono<T> 就可以执行。

调用完具体方法之后,我们就可以进行返回值解析序列化了。这里就是步骤五处理结果。

private Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
    //获取对应的返回结果处理器并处理          
   return getResultHandler(result).handleResult(exchange, result)   
       //如果出现错误或者异常 则选择对应的异常结果处理器进行处理
         .onErrorResume(ex -> result.applyExceptionHandler(ex).flatMap(exceptionResult ->                   getResultHandler(exceptionResult).handleResult(exchange, exceptionResult)));
}

我们再来看一下getResultHandler代码

private HandlerResultHandler getResultHandler(HandlerResult handlerResult) {
   if (this.resultHandlers != null) {
      for (HandlerResultHandler resultHandler : this.resultHandlers) {
         if (resultHandler.supports(handlerResult)) {
            return resultHandler;
         }
      }
   }
   throw new IllegalStateException("No HandlerResultHandler for " + handlerResult.getReturnValue());
}

在这里我们看一下resultHandlers中都含有哪些返回值处理器

img

我们通过截图可以看出返回值解析器跟流程图一一对应。

在匹配到对应的返回值解析器之后进行返回值的封装和返回,这里要注意DataBuffer是NIO的写处理,最后写回到浏览器客户端。

5、WebClient

Spring WebFlux包括WebClient对HTTP请求的响应式,非阻塞式。WebFlux客户端和服务器依靠相同的非阻塞编解码器对请求和响应内容进行编码和解码。

WebClient 内部委托给HTTP客户端库。默认情况下,WebClient 使用Reactor Netty,内置了对Jetty反应式HttpClient的支持,其他的则可以通过插入ClientHttpConnector。

5.1、使用注意事项

5.1.1、连接池配置

  1. maxConnections:最大连接数,默认最大连接数为处理器数量*2(但最小值为16),最大只能设置为200,超过这个数值设置无效。
  2. pendingAcquireMaxCount:等待队列大小,默认是最大连接数的2倍,等待队列pendingAcquireMaxCount调大,同时处理的任务数等于最大连接数,未被处理的任务在队列中等待处理。
  3. pendingAcquireTimeout:任务等待超时时间,当队列中的任务等待超过pendingAcquireTimeout还获取不到连接,就会抛出异常。

5.1.2、默认IO线程执行回调

WebClient默认内部使用Netty实现http客户端调用,这里IO线程其实是netty的IO线程,而netty客户端的IO线程内是不建议做耗时操作的,因为IO线程是用来轮巡注册到,select上的channel的数据的,如果阻塞了,那么其他channel的读写请求就会得不到及时处理。所以如果consumer内逻辑比较耗时,建议从IO线程切换到其他线程来做。可以使用publishOn把IO线程切换到自定义线程池进行处理:

resp.publishOn(Schedulers.elastic())//切换到Schedulers.elastic()对应的线程池进行处理
        .onErrorMap(throwable -> {
            System.out.println("onErrorMap:" + throwable.getLocalizedMessage());
            return throwable;
        }).subscribe(s -> System.out.println("result:" + Thread.currentThread().getName() + " " + s));

5.1.3、超时相关的配置

与服务端建立链接超时

HttpClient httpClient = HttpClient.create()
        .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
 
 
WebClient webClient = WebClient.builder()
        .clientConnector(new ReactorClientHttpConnector(httpClient))
        .build();

从服务端读取数据超时

HttpClient httpClient = HttpClient.create()
        .doOnConnected(conn -> conn
                .addHandlerLast(new ReadTimeoutHandler(10)));

写数据到服务端超时

HttpClient httpClient = HttpClient.create()
        .doOnConnected(conn -> conn
                .addHandlerLast(new WriteTimeoutHandler(10)));

从链接池获取链接超时 需要自己重写ReactorResourceFactory

private Supplier<ConnectionProvider> connectionProviderSupplier = () -> {
   return ConnectionProvider.fixed("webflux", 500,45000);//设置超时时间为45s
};

其它

  1. 官方文档
  2. 源码