Reactive简介

理解Reactive

相关技术

  • 反应堆模式(Reactor)

    同步非阻塞,多工模式,一个事情可以分为几个步骤,每个步骤相应去做,同步串行先做A,后做B

  • Proactor模式

异步非阻塞,多工模式,A,B,C同时去做,异步去做。

  • 观察者模式(Observer)

     事件通知和监听的模式,也是一种推模式,由服务端推送到客户端。

  • 迭代器模式(Iterator)

     拉模式,服务端准备好数据,由客户端通过循环去获取。

  • Java并发模型

     WebFlux的底层核心技术是Reactive,Reactive就是关于同步、异步、多工、或者设计模式的综合体。

 

关于Reactive的一些讲法

  • Reactive是异步非阻塞编程
  • Reactive能够提升程序性能
  • Reactive能解决传统编程模型遇到的困境

 

Reactive实现框架

  • RxJava:Reactive Extensions Java

     在Reactive基础上做了一些扩展,不是WebFlux的底层实现

  • Reactor:Spring WebFlux Reactive类库,标准的Spring家族的实现
  • Flow API:Java9 Flow API实现,标准的实现了Reactive Stream,有一些并发的扩展

 

传统编程模型中的某些困境

Reactor认为阻塞可能是浪费的

https://projectreactor.io/docs/core/release/reference/#_blocking_can_be_wasteful

现代应用程序有着大量并发用户,而且,即使现代硬件的能力不断提高,现代软件的性能仍然是一个关键问题。

从广义上讲,有两种方法可以提高程序的性能:

并行化以使用更多线程和更多硬件资源;

在使用现有资源方面寻求更高的效率;

通常,Java开发人员使用阻塞代码编写程序。在出现性能瓶颈之前,这种做法是可行的。然后是时候引入额外的线程,运行类似的阻塞代码了。但是这种资源利用率的扩展会很快引入争用和并发问题。

更糟糕的是,阻塞会浪费资源。如果仔细观察,只要程序涉及一些延迟(特别是I/O,比如数据库请求或网络调用),资源就会被浪费,因为线程(可能有很多线程)现在处于空闲状态,等待数据。

所以并行化方法并非银弹。

  • 阻塞导致性能瓶颈和浪费资源
  • 增加线程可能会引起资源竞争(锁带来的性能问题,并行变串行)和并发问题(数据同步问题,读写线程,可见性问题)
  • 并行的方式不是银弹(不能解决所有问题,线程数和切换成本)

理解阻塞的弊端

阻塞场景 - 数据顺序加载

Reactive简介_第1张图片

 

public class DataLoader {
    
    // 模板方法
    public final void load() {
        long startTime = System.currentTimeMillis();
        doLoad();
        long costTime = System.currentTimeMillis() - startTime;
        System.out.println("load()总耗时:" + costTime + "毫秒");
    }

    protected void doLoad() {
        loadConfigurations();
        loadUsers();
        loadOrders();
    }

    protected final void loadConfigurations() {
        loadMock("loadConfigurations()", 1);
    }

    protected final void loadUsers() {
        loadMock("loadUsers", 2);
    }

    protected final void loadOrders() {
        loadMock("loadOrders()", 3);
    }

    private void loadMock(String source, int seconds) {
        try {
            long startTime = System.currentTimeMillis();
            long milliseconds = TimeUnit.SECONDS.toMillis(seconds);
            Thread.sleep(milliseconds);
            long costTime = System.currentTimeMillis() - startTime;
            System.out.printf("[线程: %s] %s 耗时: %d 毫秒\n",
                    Thread.currentThread().getName(), source, costTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new DataLoader().load();
    }
}

运行结果:

[线程: main] loadConfigurations() 耗时: 1001 毫秒

[线程: main] loadUsers 耗时: 2001 毫秒

[线程: main] loadOrders() 耗时: 3000 毫秒

load()总耗时:6035毫秒

 

结论:

由于加载过程串行执行的关系,导致消耗实现线性累加。Blocking 模式即串行执行 。

 

理解并行的复杂

并行场景-并行数据加载

Reactive简介_第2张图片

public class ParallelDataLoader extends DataLoader {
    protected void doLoad() {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        //CompletionService是一个接口,ExecutorCompletionService为其实现类
        //ExecutorCompletionService在构造函数中会创建一个BlockingQueue
        // (使用的基于链表的无界队列LinkedBlockingQueue),
        // 该BlockingQueue的作用是保存Executor执行的结果。
        // 当计算完成时,调用FutureTask的done方法。
        // 当提交一个任务到ExecutorCompletionService时,
        // 首先将任务包装成QueueingFuture,它是FutureTask的一个子类,
        // 然后改写FutureTask的done方法,之后把Executor执行的计算结果放入BlockingQueue中。
        CompletionService completionService = new ExecutorCompletionService(executorService);
        completionService.submit(super::loadConfigurations, null);
        completionService.submit(super::loadUsers, null);
        completionService.submit(super::loadOrders, null);

        int count = 0;
        while (count < 3) {
            if (completionService.poll() != null) {
                count++;
            }
        }
        executorService.shutdown();
    }

    public static void main(String[] args) {
        new ParallelDataLoader().load();
    }
}

运行结果:

[线程: pool-1-thread-1] loadConfigurations() 耗时: 1004 毫秒

[线程: pool-1-thread-2] loadUsers 耗时: 2003 毫秒

[线程: pool-1-thread-3] loadOrders() 耗时: 3003 毫秒

load()总耗时:3122毫秒

 

结论:

明显地,程序改造为并行加载后,性能和资源利用率得到提升,消耗时间取最大者。

 

Reactor认为异步不一定能够救赎

https://projectreactor.io/docs/core/release/reference/#_asynchronicity_to_the_rescue

寻求更高的效率,可以解决资源浪费的问题,通过编写异步、非阻塞代码,可以让执行切换到另一个使用相同底层资源的活动任务,然后在异步处理完成后返回到当前进程。

但是如何在JVM上生成异步代码? Java提供了两种异步编程模型:

Callbacks:异步方法没有返回值,但是带有一个额外的回调参数(lambda或匿名类),当结果可用时将被调用。一个常见的例子是Swing的EventListener层次结构。

Futures:异步方法立即返回一个Future,异步进程计算一个T值,但是Future对象包装了对它的访问。该值不是立即可用的,可以轮询该对象,直到该值可用为止。例如,运行Callable 任务的ExecutorService使用Future对象。

但是两种方法都有局限性。

回调很难组合在一起,会导致难以阅读和维护的代码(称为“回调地狱”)。

Future比回调要好一些,尽管CompletableFuture在Java 8中带来了改进,但它们在组合方面仍然表现不佳。此外,Future还有其他问题:

  • 通过调用get()方法,很容易导致对Future对象的另一种阻塞情况。
  • 不支持惰性计算。
  • 缺乏对多值和高级错误处理的支持。

 

观点归纳:

  • Callbacks 是解决非阻塞的方案,然而他们之间很难组合,并且快速地将代码引导至 "Callback Hell" 的不归路
  • Futures 相对于 Callbacks 好一点,不过还是无法组合,不过 CompletableFuture 能够提升这方面的不足

 

理解"Callback Hell"

public class JavaGUI {
    public static void main(String[] args) {
        JFrame jFrame = new JFrame("GUI 示例");
        jFrame.setBounds(500, 300, 400, 300);
        LayoutManager layoutManager = new BorderLayout(400, 300);
        jFrame.setLayout(layoutManager);
        jFrame.addMouseListener(
                // callback 1
                new MouseAdapter() {
                    @Override
                    public void mouseClicked(MouseEvent e) {
                        System.out.printf("[线程 : %s] 鼠标点击,坐标(X : %d, Y : %d)\n",
                                currentThreadName(), e.getX(), e.getY());
                    }
                });
        jFrame.addWindowListener(
                // callback 2
                new WindowAdapter() {
                    @Override
                    public void windowClosing(WindowEvent e) {
                        System.out.printf("[线程 : %s] 清除 jFrame... \n", currentThreadName());
                        jFrame.dispose(); // 清除 jFrame
                    }

                    @Override
                    public void windowClosed(WindowEvent e) {
                        System.out.printf("[线程 : %s] 退出程序... \n", currentThreadName());
                        System.exit(0); // 退出程序
                    }
                });
        System.out.println("当前线程:" + currentThreadName());
        jFrame.setVisible(true);
    }

    private static String currentThreadName() {
        // 当前线程名称
        return Thread.currentThread().getName();
    }
}

运行结果:

当前线程:main

[线程 : AWT-EventQueue-0] 鼠标点击,坐标(X : 180, Y : 109)

[线程 : AWT-EventQueue-0] 清除 jFrame...

[线程 : AWT-EventQueue-0] 退出程序...

 

结论:

Java GUI 以及事件/监听模式基本采用匿名内置类实现,即回调实现。从本例可以得出,鼠标的点击确实没有被其他线程给阻塞。不过当监听的维度增多时,Callback 实现也随之增多。同时,事件/监听者模式的并发模型可为同步或异步(线程模型)。

回顾

  • Spring 事件/监听器(同步/异步):
    • 事件: ApplicationEvent
    • 事件监听器: ApplicationListener
    • 事件广播器: ApplicationEventMulticaster
    • 事件发布器: ApplicationEventPublisher
  • Servlet 事件/监听器
    • 同步
      • 事件: ServletContextEvent
      • 事件监听器: ServletContextListener
    • 异步
      • 事件: AsyncEvent
      • 事件监听器: AsyncListener

 

理解Future阻塞问题

如果 DataLoader 的 loadOrders() 方法依赖于 loadUsers() 的结果,而 loadUsers() 又依赖于 loadConfigurations() ,调整实现:

public class FutureBlockingDataLoader extends DataLoader {
    protected void doLoad() {
        // 创建线程池 
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        runCompletely(executorService.submit(super::loadConfigurations));
        runCompletely(executorService.submit(super::loadUsers));
        runCompletely(executorService.submit(super::loadOrders));
        executorService.shutdown();
    }

    private void runCompletely(Future future) {
        try {
            future.get();
        } catch (Exception e) {
        }
    }

    public static void main(String[] args) {
        new FutureBlockingDataLoader().load();
    }
}

运行结果:

[线程: pool-1-thread-1] loadConfigurations() 耗时: 1003 毫秒

[线程: pool-1-thread-2] loadUsers 耗时: 2001 毫秒

[线程: pool-1-thread-3] loadOrders() 耗时: 3002 毫秒

load()总耗时:6105毫秒

 

结论:

Future#get() 方法不得不等待任务执行完成,换言之,如果多个任务提交后,返回的多个 Future 逐一调用 get() 方法时,将会依次 blocking,任务的执行从并行变为串行。

 

理解Future链式问题

由于Future无法实现异步执行结果链式处理,尽管FutureBlockingDataLoader能够解决方法数据依赖以及顺序执行的问题,不过它将并行执行带回了阻塞(串行)执行,所以它不是一个理想实现。不过CompletableFuture可以帮助提升Future的限制。

public class ChainDataLoader extends DataLoader {
    @Override
    protected void doLoad() {
        CompletableFuture
                .runAsync(super::loadConfigurations)
                .thenRun(super::loadUsers).thenRun(super::loadOrders)
                // 完成时回调
                .whenComplete((result, throwable) -> {
                    System.out.println("[线程:]"+Thread.currentThread().getName() + "] 加载完成");
                })
                .join(); // 等待完成,也可以使用get,但是需要做异常处理
    }

    public static void main(String[] args) {
        new ChainDataLoader().load();
    }
}

运行结果:

[线程: ForkJoinPool.commonPool-worker-1] loadConfigurations() 耗时: 1001 毫秒

[线程: ForkJoinPool.commonPool-worker-1] loadUsers 耗时: 2002 毫秒

[线程: ForkJoinPool.commonPool-worker-1] loadOrders() 耗时: 3001 毫秒

[线程:]ForkJoinPool.commonPool-worker-1] 加载完成

load()总耗时:6088毫秒

 

图解:

Reactive简介_第3张图片

结论:

  • 如果阻塞导致性能瓶颈和资源浪费的话,Reactive 也能解决这个问题?
  • CompletableFuture 属于异步操作,如果强制等待结束的话,又回到了阻塞编程的方式,那么Reactive 也会面临同样的问题吗?
  • CompletableFuture 让我们理解到非阻塞不一定提升性能,那么 Reactive 也会这样吗?

 

Reactive Streams JVM认为异步系统和资源消费需要特殊处理

https://github.com/reactive-streams/reactive-streams-jvm

在异步系统中,处理数据流,特别是容量未预先确定的实时数据,需要特别注意。最突出的问题是,需要仔细控制资源消耗,以使过快生产的数据源不会大大超过流的消费能力。为了在协作的网络主机或单机中的多个CPU核上并行使用计算资源,需要使用异步。

 

观点归纳:

  • 流式数据容量难以预判
  • 异步编程复杂
  • 数据源和消费端之间资源消费难以平衡

 

Reactive Programming定义

The Reactive Manifesto

https://www.reactivemanifesto.org    

Reactive Systems are: Responsive, Resilient, Elastic and Message Driven.

 

关键字:

  • 响应的(Responsive)
  • 适应性强的(Resilient)
  • 弹性的(Elastic)
  • 消息驱动的(Message Driven)

 

侧重点:

  • 面向 Reactive 系统
  • Reactive 系统原则

 

维基百科

https://en.wikipedia.org/wiki/Reactive_programming

反应式编程是一种声明式编程范例,涉及数据流和变化的传播。有了这个范例,就可以轻松地表达静态(例如,数组)或动态(例如,事件发射器)数据流,还可以传达相关执行模型中存在的推断依赖关系,这有助于自动传播更改后的数据流。

 

关键字:

  • 数据流(data streams)
  • 传播变化(propagation of change)

 

侧重点:

  • 数据结构
    • 数组(arrays)
    • 事件发射器(event emitters)
  • 数据变化

 

技术连接:

  • 数据流:java8 Stream
  • 传播变化:java Obserable/Observer
  • 事件:java EventObject/EventListener

 

Spring Framework

https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-why-reactive

术语“反应性”是指围绕响应变化而构建的编程模型,如网络组件对I/O事件做出响应,UI控制器对鼠标事件做出响应等。从这个意义上说,非阻塞是反应性的,因为我们现在处于操作完成或数据可用时对通知做出反应的模式,而不是被阻塞的模式。

 

关键字:

  • 变化响应(reacting to change)
  • 非阻塞(non-blocking)

 

侧重点:

响应通知

  • 操作完成
  • 数据可用

 

技术连接:

  • 非阻塞:Servlet3.1 ReadListener/WriteListener
  • 响应通知:Servlet3.0 AsyncListener

 

ReactiveX

http://reactivex.io/intro.html

ReactiveX是一个使用可观察序列组合异步和基于事件的程序的库。它扩展了observer模式以支持数据/事件序列,并添加了允许以声明方式将序列组合在一起的操作符,同时抽象出对诸如低层次的线程、同步、线程安全、并发数据结构和非阻塞I/O等问题的关注。

 

关键字:

  • 观察者模式(Observer pattern )
  • 数据/事件序列(Sequences of data and/or events )
  • 序列操作符(Opeators)
  • 屏蔽并发细节(abstracting away...)

 

侧重点:

  • 设计模式
  • 数据结构
  • 数据操作
  • 并发模型

 

技术连接:

  • 观察者模式:Java Observable / Observer
  • 数据/事件序列:Java 8 Stream
  • 数据操作:Java 8 Stream
  • 屏蔽并发细节(abstracting away...): Exectuor 、 Future 、 Runnable

 

Reactor

http://projectreactor.io/docs/core/release/reference/#intro-reactive

反应式编程范型通常在面向对象语言中作为Observer设计模式的扩展出现,可以将reactive stream模式与熟悉的迭代器设计模式进行比较,因为在所有这些库中,Iterable-Iterator具有二元性。一个主要的区别是,迭代器是基于pull的,而reactive stream是基于push的。

 

关键字:

  • 观察者模式(Observer pattern )
  • 响应流模式(Reactive streams pattern )
  • 迭代器模式(Iterator pattern)
  • 拉模式(pull-based)
  • 推模式(push-based)

 

侧重点:

  • 设计模式
  • 数据获取方式

 

技术连接:

  • 观察者模式:Java Observable / Observer
  • 响应流模式:Java 8 Stream
  • 迭代器模式:Java Iterator

 

andrestaltz

https://gist.github.com/staltz/868e7e9bc2a7b8c1f754#what-is-reactive-programming

反应式编程是使用异步数据流进行编程。

在某种程度上,这并不是什么新鲜事。事件总线或典型的单击事件实际上是异步事件流,您可以在该事件流上观察并产生一些副作用。Reactive是一种过于理想化的概念。您可以创建任何数据流,而不仅仅是从单击和悬停事件。流是低开销且无处不在,任何东西都可以是流:变量、用户输入、属性、缓存、数据结构等。

 

关键字:

  • 异步(asynchronous)
  • 数据流(data streams)
  • 并非新鲜事物(not anything new)
  • 过于理想化(idea on steroids)

 

侧重点:

  • 并发模式
  • 数据结构
  • 技术本质

 

技术连接:

  • 异步:Java Future
  • 数据流:Java 8 Stream

 

Reactive Programming特性

Reactive编程模型

  • 语言模型:响应式编程+函数式编程(可选)

https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-programming-models

Spring WebFlux提供了两种编程模型的选择:

Annotated Controllers: 与Spring MVC一致,并基于spring-web模块中的相同注解。Spring MVC和WebFlux控制器都支持reactive (Reactor和RxJava)返回类型,因此,很难将它们区分开来,一个显著的区别是WebFlux也支持响应性的@RequestBody参数。

Functional Endpoints: 基于lambda的轻量级函数式编程模型,可以将其看作一个小型库或一组实用程序,应用程序可以使用它们来路由和处理请求。与带注解的控制器最大的不同是,应用程序从头到尾负责处理请求,而不是通过注解声明然后被回调。

  • 对立模型:命令式编程(Imperative programming)

https://en.wikipedia.org/wiki/Imperative_programming

在计算机科学中,命令式编程是一种使用语句改变程序状态的编程范式。就像自然语言中的命令式语气表达命令的方式一样,命令式程序由计算机执行的命令组成。命令式编程着重于描述程序是如何操作的。

 

结论:

  • Reactive Programming:同步或异步非阻塞执行,数据传播被动通知
  • Imperative Programming:同步阻塞执行,数据主动获取

 

数据结构

  • 流式(Streams)
  • 序列(Sequences)
  • 事件(Events)

https://gist.github.com/staltz/868e7e9bc2a7b8c1f754#what-is-reactive-programming

流是按时间顺序排列的一系列进行中的事件。

 

设计模式

  • 扩展模式:观察者(Observer),推模式
  • 对立模式:迭代器(Iterator),拉模式
  • 混合模式:反应堆(Reactor)、Proactor

 

模式对比:

http://reactivex.io/intro.html

An Observable is the asynchronous/push “dual” to the synchronous/pull Iterable

Reactive简介_第4张图片

结论:

Reactive Programming作为观察者模式(Observer)的延伸,在处理流式数据的过中,并非使用传统的命令编程方式(Imperative Programming)同步拉取数据,如迭代器模式(Iterator),而是采用同步或异步非阻塞的推拉相结合的方式,响应数据传播时的变化。

 

并发模型

非阻塞(Non-Blocking),前提条件

  • 同步(Synchronous)
  • 异步(Asynchronous)

 

屏蔽并发编程细节,如线程、同步、线程安全以及并发数据结构。

 

Reactive Programming使用场景

Reactive Streams JVM

https://github.com/reactive-streams/reactive-streams-jvm

Reactive Streams的主要目标是管理跨异步边界的流数据交换,将元素传递到另一个线程或线程池,同时确保不强制接收方缓冲任意数量的数据.

 

主要目的:

  • 管理流式数据交换(govern the exchange of stream data)
  • 异步边界(asynchronous boundary)

 

Spring Framework

https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-performance

Reactive和非阻塞性通常不会使应用程序运行得更快。在某些情况下,如果使用WebClient并行执行远程调用。非阻塞方式需要做更多的工作,可能会稍微增加所需的处理时间。

Reactive和非阻塞性的主要预期好处是能够以较少的固定数量的线程和较少的内存进行扩展。 这使应用程序在负载下更具弹性,因为它们以更可预测的方式扩展。

 

主要目的:

  • 通常并非让应用运行更快速
  • 利用较少的资源提升伸缩性

 

ReactiveX

http://reactivex.io/intro.html

ReactiveX Observable模型允许使用与数据项集合(如数组)同样简单的可组合操作来处理异步事件流。它将您从错综复杂的回调网络中解放出来,从而使代码更具可读性,更不容易出现错误。

 

  • 主要目的:
    更好可读性(more readable)
  • 减少 bugs(less prone to bugs)

 

核心技术:

  • 异步(asynchronous)
  • 同顺序(same sort)
  • 组合操作(composable operations)

 

Reactor

https://projectreactor.io/docs/core/release/reference/#_from_imperative_to_reactive_programming

  • 可组合性和可读性
  • 以丰富的运算符操纵数据流
  • 在订阅之前什么都不会发生
  • 背压或消费者向生产者发出生产速率过高信号的能力
  • 与并发无关的高层次但高价值的抽象

 

主要目的:

  • 结构性和可读性(Composability and readability)
  • 高层次并发抽象(High level abstraction)

 

核心技术:

  • 丰富的数据操作符( rich vocabulary of operators)
  • 背压(Backpressure)
  • 订阅式数据消费(Nothing happens until you subscribe)

 

Java 原生技术限制:

  • Stream 有限操作符
  • Stream 不支持背压
  • Stream 不支持订阅

 

总结Reactive Programming

Reactive Programming 作为观察者模式(Observer) 的延伸,不同于传统的命令编程方式( Imperative programming)同步拉取数据的方式,如迭代器模式(Iterator) 。而是采用数据发布者同步或异步地推 送到数据流(Data Streams)的方案。当该数据流(Data Steams)订阅者监听到传播变化时,立即作出响应动作。在实现层面上,Reactive Programming 可结合函数式编程简化面向对象语言语法的臃肿性, 屏蔽并发实现的复杂细节,提供数据流的有序操作,从而达到提升代码的可读性,以及减少 Bugs 出现的目的。同时,Reactive Programming 结合背压(Backpressure)的技术解决发布端生成数据的速率高于订阅端消费的问题。

 

Reactive Stream规范

https://github.com/reactive-streams/reactive-streams-jvm

Reactive Stream是JVM的面向流的库的标准和规范:

处理可能无限多的元素

按顺序

在组件之间异步传递元素

非阻塞背压

 

API组件

  • Publisher:数据发布者(上游)
  • Subscriber:数据订阅者(下游)
  • Subscription:订阅信号
  • Processor:Publisher和Subscriber混合体

 

Publisher

数据发布者,数据上游

 

接口:

public interface Publisher {
    public void subscribe(Subscriber s);
}

 

Subscriber

 

接口:

public interface Subscriber {
    public void onSubscribe(Subscription s);
    public void onNext(T t);
    public void onError(Throwable t);
    public void onComplete();
}

 

信号事件:

  • onSubscribe:当下游订阅时
  • onNext:当下游接收数据时
  • onComplete:当数据流(Data Streams)执行完成时
  • onError:当数据流(Data Stream)执行错误时

 

Subscription

订阅信号控制

 

接口:

public interface Subscription {
    public void request(long n);
    public void cancel();
}

 

信号操作:

  • request:请求上游元素的数量
  • cancel:请求停止发送数据并且清除资源

 

Processor

消息发布者和订阅者综合体

 

接口:

public interface Processor extends Subscriber, Publisher {
}

 

背压

https://en.wikipedia.org/wiki/Back_pressure

在信息技术领域,这个术语也被类比地用来描述在I/O开关后的数据积累,如果缓冲区是满的,不能接收任何更多的数据;传输设备停止发送数据包,直到缓冲区被清空并再次能够存储信息。

 

关键字:

  • I/O 切换(I/O switch )
  • 缓冲填满(the buffers are full )
  • 数据无法接受(incapable of receiving any more data)
  • 传输设备(transmitting device )
  • 停止发送数据包 (halts the sending of data packets )

 

http://projectreactor.io/docs/core/release/reference/#reactive.backpressure

Propagating signals upstream is also used to implement backpressure, which we described in the assembly line analogy as a feedback signal sent up the line when a workstation processes more slowly than an upstream workstation.

The real mechanism defined by the Reactive Streams specification is pretty close to the analogy: a subscriber can work in unbounded mode and let the source push all the data at its fastest achievable rate or it can use the request mechanism to signal the source that it is ready to process at most n elements.

 

关键字:

  • Propagating signals upstream(传播上游信号)
  • 无边界模式(unbounded mode)
  • 处理最大元素数量(process at most n elements)

 

总结背压

假设下游Subscriber工作在无边界大小的数据流水线时,当上游Publisher提供数据的速率快于下游Subscriber的消费数据速率时,下游Subscriber将通过传播信号(request)到上游Publisher,请求限制数据的数量( Demand )或通知上游停止数据生产。

 

Reactor框架运用

核心API

  • Mono:0-1的异步结果
  • Flux:0-N的异步序列
  • Scheduler:Reactor调度线程池

 

Mono

定义:0-1的异步结果

实现:Reactive Stream JVM API Publisher

类比:异步 Optional

 

类似模式

点对点模式

Reactive简介_第5张图片

 

图解

Reactive简介_第6张图片

 

Flux

定义:0-N的异步序列

实现:Reactive Streams JVM API Publisher

类比:异步Stream

 

类似模式

发布者订阅模式

 

图解

Reactive简介_第7张图片

 

Scheduler

定义:Reactor调度线程池

 

  • 当前线程: Schedulers.immediate()
    • 等价关系:Thread.currentThread()
  • 单复用线程: Schedulers.single()
    • 内部名称:"single"
    • 线程名称:"single"
    • 线程数量:单个
    • 线程idel时间:Long Live
    • 底层实现:ScheduledThreadPoolExecutor (core 1)
  • 弹性线程池: Schedulers.elastic()
    • 内部名称:"elastic"
    • 线程名称:"elastic-evictor-{num}"
    • 线程数量:无限制(unbounded)
    • 线程idel时间:60 秒
    • 底层实现:ScheduledThreadPoolExecutor
  • 并行线程池: Schedulers.parallel()
    • 内部名称:"parallel"
    • 线程名称:"parallel-{num}"
    • 线程数量:处理器数量
    • 线程idel时间:60 秒
    • 底层实现:ScheduledThreadPoolExecutor

 

实战

添加maven依赖


            io.projectreactor
            reactor-core
        

 

public class FluxDemo {
    public static void main(String[] args) {
        print("运行...");

        // 同步线程,在main线程中执行
        Flux.just("A", "B", "C")
                .subscribe(FluxDemo::print);

        // 线程切换
        Flux.just("E", "D", "F")
                .publishOn(Schedulers.elastic())
                .map(s -> "*" + s)
                .subscribe(
                        /* 数据消费 = onNext*/
                        FluxDemo::print,
                        /* 异常处理 = onError(Throwable),若抛异常,onComplete不会执行了 */
                        FluxDemo::print,
                        /* 完成回调 = onComplete()*/
                        () -> print("done."),
                        /* 背压操作 onSubscribe(Subscription)*/
                        subscription -> {
                            /*请求的元素数量*/
                            subscription.request(Integer.MAX_VALUE);
                            /*取消上游传输数据到下游*/
                            subscription.cancel();
                        }
                );

        print("main done.");
    }

    private static void print(Object object) {
        String threadName = Thread.currentThread().getName();
        System.out.println("[线程:" + threadName + "] " + object);
    }
}

运行结果:

[线程:main] 运行...

[线程:main] A

[线程:main] B

[线程:main] C

[线程:elastic-2] *E

[线程:elastic-2] *D

[线程:elastic-2] *F

[线程:elastic-2] done.

 

public class FluxDemo1 {
    public static void main(String[] args) {
        Flux flux = Flux.generate(() -> 0,
                (v, sink) -> {
                    sink.next("value: " + v);
                    if (v == 10) {
                        sink.complete();
                    }
                    return v + 1;
                });

        flux.subscribe(FluxDemo1::print);

        Flux.range(0, 10).handle((item, sink) -> {
            if (item % 2 == 0) {
                sink.next("Even: " + item);
            }
        }).subscribe(FluxDemo1::print);
    }

    private static void print(Object object) {
        String threadName = Thread.currentThread().getName();
        System.out.println("[线程:" + threadName + "] " + object);
    }
}

运行结果:

[线程:main] value: 0

[线程:main] value: 1

[线程:main] value: 2

[线程:main] value: 3

[线程:main] value: 4

[线程:main] value: 5

[线程:main] value: 6

[线程:main] value: 7

[线程:main] value: 8

[线程:main] value: 9

[线程:main] value: 10

[线程:main] Even: 0

[线程:main] Even: 2

[线程:main] Even: 4

[线程:main] Even: 6

[线程:main] Even: 8

你可能感兴趣的:(reactor)