JDK9响应式流学习笔记

响应式流学习总结

1.异步(Asynchronous)非阻塞(Non-Blocking)

异步和同步

针对调用者,调用者发送请求,如果等着对方回应之后才去做其他事情就是同步,如果发送请求之后不等着对方回应就去做其他事情就是异步。

阻塞和非阻塞

针对被调用者,被调用者受到请求之后,做完请求任务之后才给出反馈就是阻塞,受到请求之后马上给出反馈然后再去做事情就是非阻塞。

2.阻塞的性能问题解决

Web应用通常要面对高并发、海量数据的挑战,性能从来都是必须要考量的核心因素,阻塞便是性能杀手之一。

  • 并行化:使用更多的线程和硬件资源;

高并发环境下,多线程的切换会消耗CPU资源(高并发情况下,线程数会非常多,那么上下文切换对资源的消耗也会变得明显起来。况且在切换过程中,CPU并未执行任何业务上的或有意义的计算逻辑);

应对高并发环境的多线程开发相对比较难(需要掌握线程同步的原理与工具、ExecutorService、Fork/Join框架、并发集合和原子类等的使用),并且有些问题难以发现或重现(比如指令重排);

高并发环境下,更多的线程意味着更多的内存占用(JVM默认为每个线程分配1M的线程栈空间)。

  • 异步化:基于现有的资源来提高执行效率。

前端JavaScript代码运行在浏览器上的时候被限制为单线程的,所以JavaScript早早就拥有了非阻塞的能力,我们最常见的异步调用的例子就是Ajax,如基于Jquery的Ajax调用的代码:

$.ajax({
   type: "POST",
   url: "/url/path",
   data: "name=John&location=Boston",
   success: function(msg){
     alert( "Data Saved: " + msg );
   }
});

这里发送一个POST请求,使用success回调函数接收参数。

3.JDK9之前—观察者模式(Observer Mode)

观察者模式是一种对象行为模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。在观察者模式中,主体是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅并接收通知。
JDK8提供的观察者模式两个类 Observer 和 Observable。

public class ObserverDemo extends Observable {

  public static void main(String[] args) {
    ObserverDemo obAble = new ObserverDemo();
    obAble.addObserver((o, arg) -> System.err.println("观察者1 接收到参数"));
    obAble.addObserver((o, arg) -> System.err.println("观察者2 接收到参数"));
    obAble.setChanged();
    obAble.notifyObservers();
  }
}

4.响应式编程(Reactive programming)概念

响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。

5.什么是流?

流是由生产者生产并由一个或多个消费者消费的元素的序列。
生产者——消费者(source-sink)模型。
发布者——订阅者(publisher-subscriber)模型。

流处理机制中,pull模型和push模型是最常见的。
在push模型中,publisher将元素推送给subscriber。
在pull模型中,subscriber将元素推送给publisher。

  • 引发的问题

如果publisher和subscriber都以同样的速率工作,这些模式非常有效。
但是如果publisher和subscriber不按同样的速率工作,当publisher发布速度比subscriber处理速度快的时候,subscriber会承受巨大的资源压力(pressure)而有可能崩溃。
如果subscriber在请求publisher的元素并且元素不可用时,在同步请求中subscriber必须无限期地等待,直到有元素可用。
如果publisher同步地向subscriber发送元素,并且subscriber同步处理它们,则publisher必须阻塞直到数据处理完成。

6.响应式流(Reactive Stream)

响应式流从2013年开始,作为提供非阻塞背压的异步流处理标准的倡议。
它旨在解决处理元素流的问题——如何将元素流从发布者传递到订阅者,而不需要发布者阻塞,或订阅者有无限制的缓冲区或丢弃。它是以带非阻塞背压方式处理异步数据流的标准,提供一组最小化的接口,方法和协议来描述必要的操作和实体。具备“异步非阻塞”特性和“流量控制”能力的数据流,我们称之为响应式流(Reactive Stream)。

7.背压(backpressure)机制

为了解决以上问题,数据流的速度需要被控制,即流量控制(flow control),以防止快速的数据流不会压垮目标。
响应式流使用一种称为背压(backpressure)的机制,其中subscriber告诉publisher减慢速率并保持元素,直到subscriber准备好处理更多的item。 使用背压可确保更快的publisher不会压制较慢的subscriber。 使用背压可能要求publisher拥有无限制的缓冲区,如果它要一直生成和保存元素。 publisher可以实现有界缓冲区来保存有限数量的item,如果缓冲区已满,可以选择放弃它们。

8.JDK9响应式流API

JDK9中Reactive Stream的实现规范 通常被称为 Flow API ,通过java.util.concurrent.Flow 和java.util.concurrent.SubmissionPublisher 类来实现响应式流。主要接口声明在Flow类里,Flow 类中定义了四个嵌套的静态接口,用于建立流量控制的组件,发布者在其中生成一个或多个供订阅者使用的数据项:
Flow类结构图:

Publisher:数据发布者,将数据流发布给注册Subscriber。

@FunctionalInterface
public static interface Publisher<T> {
    public void subscribe(Subscriber<? super T> subscriber);
}

Subscriber:数据项订阅者,发布者调用订阅者的这个方法来异步传递订阅 ,

public static interface Subscriber<T> {
    //在 publisher.subscribe()方法调用后被执行。
    public void onSubscribe(Subscription subscription);
    //发布者调用这个方法传递数据给订阅者
    public void onNext(T item);
    //当 Publisher 或 Subscriber 遇到不可恢复的错误时调用此方法,之后不会再调用其他方法
    public void onError(Throwable throwable);
    //当数据已经发送完成,且没有错误导致订阅终止时,调用此方法,之后不再调用其他方法
    public void onComplete();
    }

Subscription:发布者与订阅者之间的关系纽带,订阅令牌 ,Subscriber只有在请求时才会收到项目,并可以通过 Subscription 取消订阅。

 public static interface Subscription {
     //订阅者调用此方法请求数据
    public void request(long n);
     //订阅者调用这个方法来取消订阅,解除订阅者与发布者之间的关系
    public void cancel();
}

Processor:数据处理器:Processor 位于 Publisher 和 Subscriber 之间,用于做数据转换。可以有多个 Processor 同时使用,组成一个处理链,链中最后一个处理器的处理结果发送给 Subscriber。JDK 没有提供任何具体的处理器。处理器同时是订阅者和发布者,接口的定义也是继承了两者 即作为订阅者也作为发布者 ,作为订阅者接收数据,然后进行处理,处理完后作为发布者,再发布出去。

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

defaultBufferSize:发布者或订阅者缓冲的默认值,该默认值可在没有其他约束的情况下使用。

static final int DEFAULT_BUFFER_SIZE = 256;
public static int defaultBufferSize() {
    return DEFAULT_BUFFER_SIZE;
}
  • 回压的缓存策略

订阅者处理完一个元素的时候通过request(1)跟发布者再请求一个元素。由于发布者的数据不能很快被订阅者处理掉,那么发布者会将未处理的数据元素缓存起来。

这种处理方式与消息队列有些相似之处,发布者需要维护一个队列用来缓存还没有被处理的元素。通常用于对数据准确性要求比较高的场景,比如发布者这儿是突然到来的数据高峰,都是要保存到数据库的,作为订阅者的数据持久层没有那么快的处理速度,那么发布者就需要将数据暂时缓存起来。

  • 回压的丢弃策略

发布者不需要缓存来不及处理的数据,而是直接丢弃,当订阅者请求数据的时候,会拿到发布者那里最近的一个数据元素。比如我们在做一个监控系统,后台的监控数据以每秒10个的速度产生,而前端界面只需要每秒钟更新一下监控数据即可,那作为发布者的后台就不用缓存数据了,因为这种时效性强的场景,用不到的数据直接丢掉即可。

猜你喜欢

转载自blog.csdn.net/DeweyLau/article/details/112349309