Reactive简介

本文深入探讨了Reactive编程的概念,包括其异步非阻塞的特性,以及如何解决传统编程模型中的性能瓶颈。文章介绍了Reactor框架,作为Spring WebFlux的实现,展示了其在处理数据流和事件时的优势。通过比较不同的编程模型,如观察者模式、迭代器模式,解释了Reactive编程如何提供更好的并发模型和可读性。同时,文章还讨论了Reactive Stream规范,背压机制,以及Reactor框架中的Mono和Flux核心API的使用。
摘要由CSDN通过智能技术生成

理解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,比如数据库请求或网络调用),资源就会被浪费,因为线程(可能有很多线程)现在处于空闲状态,等待数据。

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

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

理解阻塞的弊端

阻塞场景 - 数据顺序加载

image.png

 

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 模式即串行执行 。

 

理解并行的复杂

并行场景-并行数据加载

image.png

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>,异步进程计算一个T值,但是Future对象包装了对它的访问。该值不是立即可用的,可以轮询该对象,直到该值可用为止。例如,运行Callable <T>任务的ExecutorService使用Future对象。

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值