我们学习WebFlux 前置知识

开发 架构
Backpressure 在国内被翻译成背压,这个翻译在网上被很多人吐槽,我觉得大家的吐槽是有道理的,背压单纯从字面上确实看不出来有什么意思。所以松哥这里直接用英文 Backpressure 吧。

[[402929]]

 1.Backpressure

Backpressure 在国内被翻译成背压,这个翻译在网上被很多人吐槽,我觉得大家的吐槽是有道理的,背压单纯从字面上确实看不出来有什么意思。所以松哥这里直接用英文 Backpressure 吧。

Backpressure 是一种现象:当数据流从上游生产者向下游消费者传输的过程中,上游生产速度大于下游消费速度,导致下游的 Buffer 溢出,这种现象就叫做 Backpressure。

换句话说,上游生产数据,生产完成后通过管道将数据传到下游,下游消费数据,当下游消费速度小于上游数据生产速度时,数据在管道中积压会对上游形成一个压力,这就是 Backpressure,从这个角度来说,Backpressure 翻译成反压、回压似乎更合理一些。

Backpressure 会出现在有 Buffer 上限的系统中,当出现 Buffer 溢出的时候,就会有 Backpressure,对于 Backpressure,它的应对措施只有一个:丢弃新事件。那么什么是 Buffer 溢出呢?例如我的服务器可以同时处理 2000 个用户请求,那么我就把请求上限设置为 2000,这个 2000 就是我的 Buffer,当超出 2000 的时候,就产生了 Backpressure。

2.Flow API

JDK9 中推出了 Flow API,用以支持 Reactive Programming,即响应式编程。

在响应式编程中,会有一个数据发布者 Publisher 和数据订阅者 Subscriber,Subscriber 接收 Publisher 发布的数据并进行消费,在 Subscriber 和 Publisher 之间还存在一个 Processor,类似于一个过滤器,可以对数据进行中间处理。

JDK9 中提供了 Flow API 用以支持响应式编程,另外 RxJava 和 Reactor 等框架也提供了相关的实现。

我们来看看 JDK9 中的 Flow 类:

非常简洁,基本上就是按照 Reactive Programming 的设计来的:

Publisher

Publisher 为数据发布者,这是一个函数式接口,里边只有一个方法,通过这个方法将数据发布出去,Publisher 的定义如下:

  1. @FunctionalInterface 
  2. public static interface Publisher<T> { 
  3.     public void subscribe(Subscriber<? super T> subscriber); 

Subscriber

Subscriber 为数据订阅者,这个里边有四个方法,如下:

  1. public static interface Subscriber<T> { 
  2.     public void onSubscribe(Subscription subscription); 
  3.     public void onNext(T item); 
  4.     public void onError(Throwable throwable); 
  5.     public void onComplete(); 
  • onSubscribe:这个是订阅成功的回调方法,用于初始化 Subscription,并且表明可以开始接收订阅数据了。
  • onNext:接收下一项订阅数据的回调方法。
  • onError:在 Publisher 或 Subcriber 遇到不可恢复的错误时调用此方法,之后 Subscription 不会再调用 Subscriber 其他的方法。
  • onComplete:当接收完所有订阅数据,并且发布者已经关闭后会回调这个方法。

Subscription

Subscription 为发布者和订阅者之间的订阅关系,用来控制消息的消费,这个里边有两个方法:

  1. public static interface Subscription { 
  2.     public void request(long n); 
  3.     public void cancel(); 
  • request:这个方法用来向数据发布者请求 n 个数据。
  • cancel:取消消息订阅,订阅者将不再接收数据。

Processor

Processor 是一个空接口,不过它同时继承了 Publisher 和 Subscriber,所以它既能发布数据也能订阅数据,因此我们可以通过 Processor 来完成一些数据转换的功能,先接收数据进行处理,处理完成后再将数据发布出去,这个也有点类似于我们 JavaEE 中的过滤器。

  1. public static interface Processor<T,R> extends Subscriber<T>, Publisher<R> { 

2.1 消息订阅初体验

我们通过如下一段代码体验一下消息的订阅与发布:

  1. public class FlowDemo { 
  2.     public static void main(String[] args) { 
  3.         SubmissionPublisher<String> publisher = new SubmissionPublisher<>(); 
  4.         Flow.Subscriber<String> subscriber = new Flow.Subscriber<String>() { 
  5.             private Flow.Subscription subscription; 
  6.             @Override 
  7.             public void onSubscribe(Flow.Subscription subscription) { 
  8.                 this.subscription = subscription; 
  9.                 //向数据发布者请求一个数据 
  10.                 this.subscription.request(1); 
  11.             } 
  12.             @Override 
  13.             public void onNext(String item) { 
  14.                 System.out.println("接收到 publisher 发来的消息了:" + item); 
  15.                 //接收完成后,可以继续接收或者不接收 
  16.                 //this.subscription.cancel(); 
  17.                 this.subscription.request(1); 
  18.             } 
  19.             @Override 
  20.             public void onError(Throwable throwable) { 
  21.                 //出现异常,就会来到这个方法,此时直接取消订阅即可 
  22.                 this.subscription.cancel(); 
  23.             } 
  24.             @Override 
  25.             public void onComplete() { 
  26.                 //发布者的所有数据都被接收,并且发布者已经关闭 
  27.                 System.out.println("数据接收完毕"); 
  28.             } 
  29.         }; 
  30.         //配置发布者和订阅者 
  31.         publisher.subscribe(subscriber); 
  32.         for (int i = 0; i < 5; i++) { 
  33.             //发送数据 
  34.             publisher.submit("hello:" + i); 
  35.         } 
  36.         //关闭发布者 
  37.         publisher.close(); 
  38.         new Scanner(System.in).next(); 
  39.     } 

松哥稍微解释一下上面这段代码:

  1. 首先创建一个 SubmissionPublisher 对象作为消息发布者。
  2. 接下来创建 Flow.Subscriber 对象作为消息订阅者,实现消息订阅者里边的四个方法,分别进行处理。
  3. 为 publisher 配置上 subscriber。
  4. 发送消息。
  5. 消息发送完成后关闭 publisher。
  6. 最后是让程序不要停止,观察消息订阅者打印情况。

2.2 模拟 Backpressure

Backpressure 问题在 Flow API 中得到了很好的解决。Subscriber 会将 Publisher 发布的数据缓存在 Subscription 中,其长度默认为256,相关源码如下:

  1. public final class Flow { 
  2.     static final int DEFAULT_BUFFER_SIZE = 256; 
  3.     public static int defaultBufferSize() { 
  4.         return DEFAULT_BUFFER_SIZE; 
  5.     } 
  6.     ... 

一旦超出这个数据量,publisher 就会降低数据发送速度。

我们对上面的案例进行修改,如下:

  1. public class FlowDemo { 
  2.     public static void main(String[] args) { 
  3.         SubmissionPublisher<String> publisher = new SubmissionPublisher<>(); 
  4.  
  5.         Flow.Subscriber<String> subscriber = new Flow.Subscriber<String>() { 
  6.             private Flow.Subscription subscription; 
  7.  
  8.             @Override 
  9.             public void onSubscribe(Flow.Subscription subscription) { 
  10.                 this.subscription = subscription; 
  11.                 //向数据发布者请求一个数据 
  12.                 this.subscription.request(1); 
  13.             } 
  14.  
  15.             @Override 
  16.             public void onNext(String item) { 
  17.                 System.out.println("接收到 publisher 发来的消息了:" + item); 
  18.                 //接收完成后,可以继续接收或者不接收 
  19.                 //this.subscription.cancel(); 
  20.                 try { 
  21.                     Thread.sleep(2000); 
  22.                 } catch (InterruptedException e) { 
  23.                     e.printStackTrace(); 
  24.                 } 
  25.                 this.subscription.request(1); 
  26.             } 
  27.  
  28.             @Override 
  29.             public void onError(Throwable throwable) { 
  30.                 //出现异常,就会来到这个方法,此时直接取消订阅即可 
  31.                 this.subscription.cancel(); 
  32.             } 
  33.  
  34.             @Override 
  35.             public void onComplete() { 
  36.                 //发布者的所有数据都被接收,并且发布者已经关闭 
  37.                 System.out.println("数据接收完毕"); 
  38.             } 
  39.         }; 
  40.         publisher.subscribe(subscriber); 
  41.         for (int i = 0; i < 500; i++) { 
  42.             System.out.println("i--------->" + i); 
  43.             publisher.submit("hello:" + i); 
  44.         } 
  45.         //关闭发布者 
  46.         publisher.close(); 
  47.         new Scanner(System.in).next(); 
  48.     } 

一共修改了三个地方:

  1. Subscriber#onNext 方法中,每次休息两秒再处理下一条数据。
  2. 发布数据时,一共发布 500 条数据。
  3. 打印数据发布的日志。

修改完成后,我们再次启动项目,观察控制台输出:

可以看到,生产者先是一股脑生产了 257 条数据(hello0 在一开始就被消费了,所以缓存中实际上是 256 条),消息则是一条一条的来,由于消费的速度比较慢,所以当缓存中的数据超过 256 条之后,接下来都是消费一条,再发送一条。

2.3 数据处理

Flow.Processor 可以像过滤器一样,对数据进行预处理,数据从 publisher 出来之后,先进入 Flow.Processor 中进行预处理,然后再进入 Subscriber。

修改后的代码如下:

  1. public class FlowDemo { 
  2.     public static void main(String[] args) { 
  3.  
  4.         class DataFilter extends SubmissionPublisher<String> implements Flow.Processor<String,String>{ 
  5.  
  6.             private Flow.Subscription subscription; 
  7.  
  8.             @Override 
  9.             public void onSubscribe(Flow.Subscription subscription) { 
  10.                 this.subscription = subscription; 
  11.                 this.subscription.request(1); 
  12.             } 
  13.  
  14.             @Override 
  15.             public void onNext(String item) { 
  16.                 this.submit("【这是一条被处理过的数据】" + item); 
  17.                 this.subscription.request(1); 
  18.             } 
  19.  
  20.             @Override 
  21.             public void onError(Throwable throwable) { 
  22.                 this.subscription.cancel(); 
  23.             } 
  24.  
  25.             @Override 
  26.             public void onComplete() { 
  27.                 this.close(); 
  28.             } 
  29.         } 
  30.  
  31.         SubmissionPublisher<String> publisher = new SubmissionPublisher<>(); 
  32.         DataFilter dataFilter = new DataFilter(); 
  33.         publisher.subscribe(dataFilter); 
  34.  
  35.         Flow.Subscriber<String> subscriber = new Flow.Subscriber<String>() { 
  36.             private Flow.Subscription subscription; 
  37.  
  38.             @Override 
  39.             public void onSubscribe(Flow.Subscription subscription) { 
  40.                 this.subscription = subscription; 
  41.                 //向数据发布者请求一个数据 
  42.                 this.subscription.request(1); 
  43.             } 
  44.  
  45.             @Override 
  46.             public void onNext(String item) { 
  47.                 System.out.println("接收到 publisher 发来的消息了:" + item); 
  48.                 //接收完成后,可以继续接收或者不接收 
  49.                 //this.subscription.cancel(); 
  50.                 try { 
  51.                     Thread.sleep(2000); 
  52.                 } catch (InterruptedException e) { 
  53.                     e.printStackTrace(); 
  54.                 } 
  55.                 this.subscription.request(1); 
  56.             } 
  57.  
  58.             @Override 
  59.             public void onError(Throwable throwable) { 
  60.                 //出现异常,就会来到这个方法,此时直接取消订阅即可 
  61.                 this.subscription.cancel(); 
  62.             } 
  63.  
  64.             @Override 
  65.             public void onComplete() { 
  66.                 //发布者的所有数据都被接收,并且发布者已经关闭 
  67.                 System.out.println("数据接收完毕"); 
  68.             } 
  69.         }; 
  70.         dataFilter.subscribe(subscriber); 
  71.         for (int i = 0; i < 500; i++) { 
  72.             System.out.println("发送消息 i--------->" + i); 
  73.             publisher.submit("hello:" + i); 
  74.         } 
  75.         //关闭发布者 
  76.         publisher.close(); 
  77.         new Scanner(System.in).next(); 
  78.     } 

简单起见,我这里创建了一个局部内部类 DataFilter,DataFilter 继承自 SubmissionPublisher 并实现了 Flow.Processor 接口,由于 DataFilter 继承自 SubmissionPublisher,所以它也兼具 SubmissionPublisher 的功能。

在 DataFilter 中完成消息的处理并重新发送出去。接下来定义 publisher,让 dataFilter 作为其订阅者,再定义新的订阅者,作为 dataFilter 的订阅者。

最终运行效果如下:

3.小结

好啦,这就是今天和大家介绍的 Java9 中的 Reactive Stream,那么至此,我们的 WebFlux 前置知识差不多告一段落了,下篇文章开始,正式开整 WebFlux。

本文转载自微信公众号「江南一点雨」,可以通过以下二维码关注。转载本文请联系江南一点雨公众号。

 

责任编辑:武晓燕 来源: 江南一点雨
相关推荐

2021-05-19 10:37:16

WebFlux 前置工具

2019-08-02 11:53:50

Android开发学习

2022-12-09 07:57:15

2021-12-13 22:47:31

人工智能机器人学习

2018-09-10 05:14:38

物联网工程物联网IOT

2017-03-07 15:43:28

编程语言函数数据结构

2024-05-22 08:03:39

2022-09-22 08:19:26

WebFlux函数式编程

2022-07-04 09:15:10

Spring请求处理流程

2023-02-09 08:01:12

核心组件非阻塞

2021-06-02 10:39:59

ServletWebFluxSpringMVC

2018-04-12 13:53:19

2018-08-05 06:48:34

2022-11-04 08:39:46

SpringWebFlux

2022-05-17 11:05:16

机器学习人工智能

2010-03-09 18:34:29

Python日志

2011-06-16 20:05:41

SEO

2023-10-31 08:21:18

WebFlux基本用法JPA

2023-09-04 11:52:53

SpringMVC性能

2010-04-30 00:38:34

UNIX引号
点赞
收藏

51CTO技术栈公众号