GrowingIO Reactor速成指南

响应式编程(二)Mono<T>、Flux<T> 方法大全 阅读详情

简介

Reactor响应式编程(Reactive Programming):

Reactor is a fully non-blocking reactive programming foundation for the JVM, with efficient demand management (in the form of managing “backpressure”). It integrates directly with the Java 8 functional APIs, notably CompletableFuture, Stream, and Duration. It offers composable asynchronous sequence APIs — Flux (for [N] elements) and Mono (for [0|1] elements) — and extensively implements the Reactive Streams specification.

翻译一下就是:Reactor是JVM的一个完全无阻塞的响应式编程基础,具有高效的需求管理(以管理“背压”的形式)。它直接与Java 8功能api集成,尤其是CompletableFuture、Stream和Duration。它提供了可组合的异步序列api——Flux(用于[N]元素)和Mono(用于[0|1]元素)——并且广泛地实现了反应流规范。

背景

我们服务端的项目大多采用了Spring WebFlux,reactor是 Spring WebFlux 的首选反应库,WebFlux 需要 Reactor 作为核心依赖项。Reactor 存在一定的学习成本,在开发中我们遇到了些bug,相当一部分是因为我们不够了解 reactor ,踩了很多坑。所以在本文档中我们主要针对的是一些学习过程容易让新人感到迷茫的知识点(map、flatMap、异步、并发),期望能让新人更好上手 Spring WebFlux。

Mono

Mono<T>是特殊的Publisher<T>,它通过onNext信号最多发出一个项目, 然后以一个onComplete信号(成功Mono,有或没有值)终止,或者只发出一个onError信号。某些Operator(尤其是那些将Mono与其他Publisher结合在一起的Operator)可以把Mono切换到Flux。 例如,Mono.concatWith(Publisher)返回Flux,而Mono.then(Mono)返回另一个Mono。

Flux

Flux<T>是标准的Publisher<T>,表示它是可以发送0到N个元素的异步序列,可选的终止操作有onComplete或onError。 与Reactive Streams规范一样,这三种信号转换为对下游的onNext,onComplete和onError方法的调用。 Flux是通用的响应式类型。 请注意,所有事件,甚至是终止事件,都是可选的,意思是:可能没有onNext事件,但只有onComplete事件,这就表示此Flux是一个空的有限序列。删除onComplete则可以得到一个无限的空序列(除了取消测试外,它没有什么用处)。 同样,无限序列不一定为空。例如,Flux.interval(Duration)无限生产Flux<Long>。

map 、flatMap 以及 flatMapSequential 区别:

方法签名

//Map 的方法签名
<V> Flux<V> map(Function<? super T, ? extends V> mapper)
​
//FlatMap的方法签名
<R> Flux<R> flatMap(Function<? super T, ? extends Publisher<? extends R>> mapper)

map:通过对每个元素应用同步函数来转换此Flux发出的元素, 并且是一对一的转换流元素。

flatMap: 将此Flux发出的元素异步转换为 Publisher,然后通过合并将这些内部发布者扁平化为单个Flux 。Flux可能包含N个元素,所以flatMap是一对多的转换。将各个 publisher 合并的过程不会保持 源Flux发布 的顺序,可能出现交错。

flatMapSequential: 将此Flux发出的元素异步转换为 Publisher,然后通过合并将这些内部发布者扁平化为单个Flux 。于 flatMap 不同的是 flatMapSequential 在合并 publisher 时会 按源元素的顺序合并它们。

map是一个同步运算符,它只是一种将一个值转换为另一个值的方法。

flatMap可以是同步的,也可以是异步的,这取决于flatMap中调用的方法是否使用。

示例1:

void demo() {
    //1. Flux.interval 按时生产Long的无限流
    final Flux<Long> flux = Flux.interval(Duration.of(100, ChronoUnit.MILLIS))
            .map(log()) //2. 调用方法log,订阅后log()会在当前线程执行
            .flatMap(logOfFlux()); //3. 通过flatMap调用 log(), 会在当前线程执行
    //完成flux的定义,调用subscribe后才会真正开始执行
    flux.subscribe();
}
​
​
Function<Long, Long> log() {
    return aLong -> {
        log.info("num is {}", aLong);
        return aLong;
    };
}
​
Function<Long, Flux<Long>> logOfFlux() {
    return aLong -> {
        log.info("num is {}", aLong);
        return Flux.just(aLong);
    };
}

在我们的示例代码中,该 flatMap 操作是同步的,因为我们使用Flux.just()方法发出元素。下面我们会介绍如何在 flatMap 中实现异步操作。

上边代码中讲到了 Publisher 调用 subscribe 后才会真正开始执行,但是 subscribe 中的代码并不一定会执行。

当 Mono 是空序列时 :

void monoOfEmpty() {
    Mono.empty()
        .map(m -> func())
        .subscribe(message -> {
          responseObserver.onNext(message);
          responseObserver.onCompleted();
        );
}

会有同学喜欢在 subscribe 中处理响应(例如rpc),但这个场景中responseObserver.onCompleted() 不会被执行。

正确的做法应该是:

void monoOfEmpty() {
    Mono.empty()
        .map(m -> func())
        .doOnSuccess(message -> responseObserver.onCompleted())
        .subscribe(responseObserver::onNext);
}

异步与多线程

Reactor被视为与并发无关的。也就是说,获得Flux或Mono并不一定意味着它在专用线程中运行。 取而代之的是,大多数Operator会继续在执行前一个Operator的线程中工作。除非指定,否则最顶层的Operator(源)本身运行在进行subscribe()调用的线程上。

先分享一个案例:

在 Growing 查询服务 olap 中由于使用错误的操作符带来性能问题

现象:在查看某些看板时,响应时间会超时(一分钟)。

经过分析发现这些看板中的单图查询时间段包含“今天”,由于需要保证实时数据,olap在处理包含“今天”的查询时不会生成缓存;通过日志发现olap在查询clickhouse时一直都是一个线程在提交。

对应代码:

//queries sql List
Flux.fromArray(queries.values())
    //需要使用flatMapSequential 保证Flux订阅时返回的顺序
    .flatMapSequential(query -> {
        //执行sql
        return executor.getFromCacheOrExecute(...);
    });
})
​
Mono<DataSet> getFromCacheOrExecute() {
    return existsCache(hashing)
        .flatMap(exists-> {
             if(exists) {
              //查询缓存
             }else{
             //查询clickhouse
             return writeCache(hashing, doExecute(...))
             }
        });
}
​
//查询缓存
public Mono<Boolean> existsKey(String key) {
    return stringRedisTemplate.hasKey(key);
}
​
//提交查询至clickhouse
Mono<DataSet> doExecute(...) {
    return Mono.fromCallable(() -> execute(...));
}

代码中没有使用subscriptOn或者publishOn操作符,stringRedisTemplate.hasKey(key) 会使后续的操作符都在 lettuce-1 线程中执行 并且整个流的执行都是串行的,我们需要将查询过程改为异步查询,多个sql 可以并发查询。下面就介绍实现异步并发的一些方法,及GrowingIO应用于这个场景的选择。

Reactor提供了两种在流中切换执行上下文(或Scheduler)的方式:

我们可以通过这两种方式达到异步执行的目的

  1. publishOn : 此运算符影响线程上下文,它下面的链中的其余运算符将在其中执行,直到新出现的publishOn。

    void demo() {
        final Flux<Long> flux = Flux.interval(Duration.of(100, ChronoUnit.MILLIS))
                .map(log())//1
                .publishOn(Schedulers.parallel())//2
                .map(log())//3
                .publishOn(Schedulers.elastic())//4
                .flatMap(logOfMono());//5
        flux.subscribe();
    }

1. 会在调用flux.subscribe()的线程上执行

2. 指定后续的运算符执行的上下文

3. 会在parallel线程上执行

4. 指定后续的运算符执行的上下文

5. 会在elastic线程上执行

  1. subscribeOn :整个流在指定的Scheduler的Scheduler.Worker上运行 ,直至出现publishOn ,publishOn后续的操作符由 publishOn 决定执行上下文。

    void demo() {
        final Flux<Long> flux = Flux.interval(Duration.of(100, ChronoUnit.MILLIS))
                .map(log())//1
                .publishOn(Schedulers.parallel())
                .map(log())//2
                .subscribeOn(Schedulers.elastic())
                .flatMap(logOfMono());
        flux.subscribe();
    }

1. 会在elastic线程执行

2. 会在parallel线程上执行,publishOn会“覆盖”subscribeOn的行为

这样我们就可以异步的去执行方法了。这里有一个误区,刚接触reactor的小伙伴会误认为Flux中多个元素经过log()或者 logOfMono() 会分别打印在不同线程上

Flux依次发出了1,2,3三个元素 无论是subscribeOn、还是publishOn,经过flatMap 都会在 parallel-1 线程上执行,也就是说Flux中的所有元素只是从主线程发出,在另一个线程中执行。

在调用一些阻塞方法时(rpc、redis、io),我们期望每个元素经过 flatMap 中时可以运行在不同线程上(串行-> 并发),应该怎么做?

  1. 将Flux转为ParallelFlux,使用runOn来指明需要的线程池

要获得ParallelFlux,可以在Flux 上使用parallel()运算符。

为了告诉ParallelFlux在哪里运行每个元素,必须使用runOn(Scheduler)。如果并行处理后,您想恢复到“正常”状态 Flux并以顺序方式应用运算符链的其余部分,可以使用sequential()

void demo() {
    final Flux<Long> flux = Flux.fromIterable(Lists.newArrayList(3L, 1L, 2L))
            .parallel().runOn(Schedulers.elastic())
            .flatMap(logOfMono())
            .sequential();
    flux.subscribe();
}

2. fluxMap中调用的方法需要在内部通过publishOn表明执行上下文。

void demo() {
    Flux.fromIterable(Lists.newArrayList(3L, 1L, 2L))
        .flatMap(this::blockFunction)
        .subscribe();
}
​
//通过publishOn 来表明 blockFunction() 的上下文
Mono<Long> blockFunctionAsync(Long i) {
    return Mono.just(i).publishOn(Schedulers.elastic()).flatMap(this::blockFunction);
}
​
Mono<Long> blockFunction(Long i) {
    Thread.sleep(i * 1000);
    return Mono.just(i);
}

3. 使用 Mono.fromFuture() 创建流

Mono.fromFuture(CompletableFuture.supplyAsync(() -> blockFunction()));

Mono.fromFuture() 创建一个Mono ,使用提供的 CompletableFuture 产生它的值。

CompletableFuture.supplyAsync() 返回一个 CompletableFuture,它将在ForkJoinPool.commonPool() 上运行任务,异步完成。

ForkJoinPool一个全局线程池,主要应用于计算密集型的场景。

4. 使用Mono.create() 、Flux.create() 方法创建流

//自定义线程池
final Executor executor = new ThreadPoolExecutor(...);

//创建Mono、Flux时 指明上下文
Mono.create(sink -> executor.execute(() -> {
    sink.success(blockFunction());
}));

目前我们推荐使用 这种方法来创建Mono、Flux来实现异步

这样做的好处:

  1. Reactor对新手来说有一定理解成本,在调用一个返回值为 Publisher 的类型的方法时,不点进去无法知道这是同步方法还是异步方法,避免对于调用者造成的心智负担。

  2. 更方便得使用自定义线程池

我们针对 olap 查询业务场景 采取合适的方法:

方法一 : ParallelFlux 不能保证sql返回顺序,不满足该业务场景。

方法二: Reactor对新手来说有一定理解成本,在调用一个返回值为 Publisher 的类型的方法时,不点进去无法知道这是同步方法还是异步方法,对于调用者存在心智负担。

方法三:无法指定自定义线程池

我们采用了第4种方式来实现异步。

reactor提供的线程池

  1. 当前线程执行(Schedulers.immediate()): 在处理时,将直接执行提交的Runnable,从而在当前线程上有效地运行它们(可以视为“空对象”或无操作调度程序)。

  2. 单个可重用线程(Schedulers.single())。请注意,此方法对所有调用方都使用相同的线程,直到调度程序被释放为止。如果需要每次调用一个专用线程,请对每个调用使用Schedulers.newSingle()。

  3. 无限制的弹性线程池(Schedulers.elastic())。它根据需要创建新的工作池并重用空闲的工作池,随着Flux中元素的增多,会无限制创建线程去执行,存在安全隐患,不推荐使用。

  4. 有界弹性线程池(Schedulers.boundedElastic())。根据需要创建新的工作池并重用空闲的工作池。闲置时间过长(默认值为60s)的工作池也将被丢弃。与elastic()有所不同,它对可以创建的线程数进行限制(默认为CPU核心数x 10)。达到上限后,最多可再提交10万个任务,并在有线程可用时重新调度(当任务被设置延迟执行时,延迟计时是在线程可用时开始)。这是I/O阻塞任务的更好选择。Schedulers.boundedElastic()是一种为阻塞处理提供自己的线程的简便方法,这样它就不会占用其他资源。缺点是这个线程池是全局的,假设有两个阻塞方法 getFiles、getData ,期望分别使用不同线程池去方便管理,就不适合用 boundedElastic 了。

  5. 为并行工作而调整的固定工作线程池(Schedulers.parallel())。它创建的工作线程数量与CPU内核数量一样多。

处理错误

在响应式流中,错误(error)是终止(terminal)事件。当有错误发生时,它会导致流序列停止, 并且错误信号会沿着操作链条向下传递,直至遇到定义的 Subscriber 及其 onError 方法。

在 try-catch 代码块中处理异常的几种方法。常见的包括如下几种:

  1. 捕获并返回一个静态的缺省值。

  2. 捕获并执行一个异常处理方法。

  3. 捕获并动态计算一个候补值来顶替。

  4. 捕获,并再包装为某一个 业务相关的异常,然后再抛出业务异常。

  5. 捕获,记录错误日志,然后继续抛出。

  6. 使用 finally 来清理资源

以上所有这些在 Reactor 都有等效的 操作符处理方式。

与第 (1) 条(捕获并返回一个静态的缺省值)对应的是 onErrorReturn:

Flux.just(10)
    .flatMap(this::function)
    .onErrorReturn("Error");//返回一个静态的缺省值

根据错误类型返回对应值

Flux.just(10)
    .flatMap(this::function)
    .onErrorReturn(e -> e.getMessage().equals("boom-1"), "Error-1");

与第 (2、3、4) 条(捕获并执行一个异常处理方法)对应的是 onErrorResume

Flux.just(10)
    .flatMap(m -> function(k).onErrorResume(e -> handleErr(k)));
​
Flux.just(10)
    .flatMap(m -> function(k).onErrorResume(e -> errFunc(k)));
​
Flux.just(10)
    .flatMap(m -> function(k)
        .onErrorResume(e -> Flux.error(new Exception(k)))
    );

对应第 (5) 条(捕获,记录错误日志,并继续抛出)

Flux.just(10)
    .flatMap(k -> function(k)) 
    .doOnError(e -> 
        log.error(e.getLocalizedMessage(), e);
    });

对应(6)doFinally 在序列终止(无论是 onComplete、onError还是取消)的时候被执行, 并且能够判断是什么类型的终止事件

Flux.just(10)
    .doFinally(type -> {
        if (type == SignalType.CANCEL){
            log.info("a log"); 
        }
    })

我需要哪个运算符

官方文档

A.1. 创建一个新序列

  • 发出一个 T ,当已经有值(不需要再去IO): just

  • 基于一个 Optional<T>:Mono.justOrEmpty(Optional<T>)

  • 基于一个可能为 null 的 T:Mono.justOrEmpty(T)

  • 发出一个 T ,且还是由 just 返回

  • 但是需要 “懒”创建的:使用 Mono.fromSupplier 或用 defer 包装 just

//会起到延迟执行的作用,在订阅时才去执行 func()
Mono.defer(()-> Mono.just(func()));
  • 发出许多 T,这些元素我可以明确列举出来:Flux.just(T...)

  • 基于迭代数据结构:

  • 一个数组:Flux.fromArray

  • 一个集合或 iterable:Flux.fromIterable

  • 一个 Integer 的 range:Flux.range

  • 一个 Stream 提供给每一个订阅:Flux.fromStream(Supplier<Stream>)

  • 基于一个参数值给出的源:

  • 一个 Supplier<T>:Mono.fromSupplier

  • 一个任务:Mono.fromCallable,Mono.fromRunnable

  • 一个 CompletableFuture<T>:Mono#fromFuture

  • 直接完成:empty

  • 立即生成错误: error

  • 但是“懒”创建的方式生成 Throwable.error(Supplier<Throwable>)

  • 什么都不做:never

  • 依赖一个可回收的资源:using

  • 可编程地生成事件(可以使用状态):

  • 同步且逐个的:Flux.generate

  • 异步(也可同步)的,每次尽可能多发出元素:Flux.create (Mono#create 也是异步的,只不过只能发一个)

A.2. 对现有序列进行转化

  • 我想转化一个序列:

  • 1对1地转化(比如字符串转化为它的长度): map

  • 类型转化:cast

  • 为了获得每个元素的序号:Flux.index

  • 1对n地转化(如字符串转化为一串字符):flatMap + 使用一个工厂方法

  • 1对n地转化可自定义转化方法和/或状态:handle

  • 对每一个元素执行一个异步操作(如对 url 执行 http 请求): flatMap+ 一个异步的返回类型为 Publisher的方法

  • 忽略一些数据:在 flatMap lambda 中根据条件返回一个 Mono.empty()

  • 保留原来的序列顺序:Flux.flatMapSequential(对每个元素的异步任务会立即执行,但会将结果按照原序列顺序排序)

  • 当 Mono 元素的异步任务会返回多个元素的序列时:Mono.flatMapMany

  • 我想添加一些数据元素到一个现有的序列:

  • 在开头添加:Flux#startWith(T...)

  • 在最后添加:Flux#concatWith(T...)

  • 我想将 Flux转化为集合(一下都是针对 Flux的)

  • 转化为 List:collectList,collectSortedList

  • 转化为 Map:collectMap,collectMultiMap

  • 转化为自定义集合:collect

  • 计数:count

  • reduce 算法(将上个元素的reduce结果与当前元素值作为输入执行reduce方法,如sum) reduce

  • 将每次 reduce 的结果立即发出:scan

  • 转化为一个 boolean 值:

  • 对所有元素判断都为true:all

  • 对至少一个元素判断为true:any

  • 判断序列是否有元素(不为空):hasElements

  • 判断序列中是否有匹配的元素:hasElement

  • 我想合并 publishers…

  • 按序连接: Flux.concat 或者 concatWith(other)

  • 即使有错误,也会等所有的 publishers 连接完成:Flux.concatDelayError

  • 按订阅顺序连接(这里的合并仍然可以理解成序列的连接):Flux.mergeSequential

  • 按元素发出的顺序合并(无论哪个序列的,元素先到先合并): Flux.merge 或者 Flux.mergeWith(other)

  • 元素类型会发生变化:Flux.zip / Flux.zipWith

  • 将元素组合:

  • 2个 Mono 组成1个 Tuple2:Mono.zipWith

  • n个 Monos 的元素都发出来后组成一个 Tuple:Mono#zip

  • 在终止信号出现时“采取行动”:

  • 在 Mono 终止时转换为一个 Mono<Void>:Mono.and

  • 当 n 个 Mono 都终止时返回 Mono<Void>:Mono.when

  • 返回一个存放组合数据的类型,对于被合并的多个序列:

  • 每个序列都发出一个元素时:Flux.zip

  • 任何一个序列发出元素时:Flux.combineLatest

  • 只取各个序列的第一个元素:Flux#first,Mono#first,mono.or (otherMono).or(thirdMono),`flux.or(otherFlux).or(thirdFlux)

  • 由一个序列触发(类似于 flatMap,不过“喜新厌旧”):switchMap

  • 由每个新序列开始时触发(也是“喜新厌旧”风格):switchOnNext

  • 我想重复一个序列: repeat

  • 以一定的间隔重复:Flux.interval(duration).flatMap(tick -> myExistingPublisher)

  • 我有一个空序列,但是…

  • 我想要一个缺省值来代替:defaultIfEmpty

  • 我想要一个缺省的序列来代替:switchIfEmpty

  • 我有一个序列,但是我对序列的元素值不感兴趣: ignoreElements

  • …并且我希望用 Mono 来表示序列已经结束:then

  • …并且我想在序列结束后等待另一个任务完成:thenEmpty

  • …并且我想在序列结束之后返回一个 Mono:Mono#then(mono)

  • …并且我想在序列结束之后返回一个值:Mono#thenReturn(T)

  • …并且我想在序列结束之后返回一个 Flux:thenMany

  • 我有一个 Mono 但我想延迟完成…

  • …当有1个或N个其他 publishers 都发出(或结束)时才完成: Mono.delayUntilOther

  • …使用一个函数式来定义如何获取“其他 publisher”:Mono.delayUntil(Function)

  • 我想基于一个递归的生成序列的规则扩展每一个元素,然后合并为一个序列发出:

  • …广度优先:expand(Function)

  • …深度优先:expandDeep(Function)

A.3. “窥视”(只读)序列

  • 在不对序列造成改变的情况下,我想:

  • 得到通知或执行一些操作:

  • 发出元素:doOnNext

  • 序列完成:Flux#doOnComplete,Mono#doOnSuccess

  • 因错误终止:doOnError

  • 取消:doOnCancel

  • 订阅时:doOnSubscribe

  • 请求时:doOnRequest

  • 完成或错误终止: doOnTerminate(Mono的方法可能包含有结果)

  • 但是在终止信号向下游传递 之后 :doAfterTerminate

  • 所有类型的信号(Signal):Flux.doOnEach

  • 所有结束的情况(完成complete、错误error、取消cancel):doFinally

  • 记录日志:log

  • 我想知道所有的事件:

  • 每一个事件都体现为一个 single 对象:

  • 执行 callback:doOnEach

  • 每个元素转化为 single对象: materialize

  • …在转化回元素:dematerialize

  • 转化为一行日志:log

A.4. 过滤序列

  • 我想过滤一个序列

  • 基于给定的判断条件: filter

  • …异步地进行判断:filterWhen

  • 仅限于指定类型的对象:ofType

  • 忽略所有元素:ignoreElements

  • 去重:

  • 对于整个序列:Flux#distinct

  • 去掉连续重复的元素:Flux#distinctUntilChanged

  • 我只想要一部分序列:

  • 只要 N 个元素:

  • 从序列的第一个元素开始算: Flux.take(long)

  • …取一段时间内发出的元素:Flux.take(Duration)

  • …只取第一个元素放到 Mono 中返回:Flux.next()

  • …使用 request(N) 而不是取消:Flux.limitRequest(long)

  • 从序列的最后一个元素倒数:Flux.takeLast

  • 直到满足某个条件(包含):Flux.takeUntil(基于判断条件),Flux#takeUntilOther(基于对 publisher 的比较)

  • 直到满足某个条件(不包含):Flux.takeWhile

  • 最多只取 1 个元素:

  • 给定序号:Flux.elementAt

  • 最后一个: .takeLast(1)

  • …如果为序列空则发出错误信号:Flux.last()

  • …如果序列为空则返回默认值:Flux.last(T)

  • 跳过一些元素:

  • 从序列的第一个元素开始跳过: Flux.skip(long)

  • …跳过一段时间内发出的元素:Flux.skip(Duration)

  • 跳过最后的 n 个元素:Flux.skipLast

  • 直到满足某个条件(包含):Flux.skipUntil(基于判断条件),Flux.skipUntilOther (基于对 publisher 的比较)

  • 直到满足某个条件(不包含):Flux.skipWhile

  • 采样:

  • 给定采样周期: Flux.sample(Duration)

  • 取采样周期里的第一个元素而不是最后一个:sampleFirst

  • 基于另一个 publisher:Flux.sample(Publisher)

  • 基于 publisher“超时”:Flux.sampleTimeout (每一个元素会触发一个 publisher,如果这个 publisher 不被下一个元素触发的 publisher 覆盖就发出这个元素)

  • 我只想要一个元素(如果多于一个就返回错误)…

  • 如果序列为空,发出错误信号:Flux.single()

  • 如果序列为空,发出一个缺省值:Flux.single(T)

  • 如果序列为空就返回一个空序列:Flux.singleOrEmpty

A.5. 错误处理

  • 我想创建一个错误序列: error

  • …替换一个完成的 Flux:.concat(Flux.error(e))

  • …替换一个完成的 Mono:.then(Mono.error(e))

  • …如果元素超时未发出:timeout

  • …“懒”创建:error(Supplier<Throwable>)

  • 我想要类似 try/catch 的表达方式:

  • 抛出异常:error

  • 捕获异常:

  • 然后返回缺省值:onErrorReturn

  • 然后返回一个 Flux 或 Mono:onErrorResume

  • 包装异常后再抛出:.onErrorMap(t -> new RuntimeException(t))

  • finally 代码块:doFinally

  • Java 7 之后的 try-with-resources 写法:using 工厂方法

  • 我想从错误中恢复…

  • 返回一个缺省的:

  • 的值:onErrorReturn

  • Publisher:Flux.onErrorResume 和 Mono.onErrorResume

  • 重试: retry

  • …由一个用于伴随 Flux 触发:retryWhen

  • 我想处理背压错误(向上游发出“MAX”的 request,如果下游的 request 比较少,则应用策略)…

  • 抛出 IllegalStateException:Flux#onBackpressureError

  • 丢弃策略: Flux.onBackpressureDrop

  • …但是不丢弃最后一个元素:Flux.onBackpressureLatest

  • 缓存策略(有限或无限): Flux.onBackpressureBuffer

  • …当有限的缓存空间用满则应用给定策略:Flux.onBackpressureBuffer 带有策略 BufferOverflowStrategy

A.6. 基于时间的操作

  • 我想将元素转换为带有时间信息的 Tuple2<Long, T>

  • 从订阅时开始:elapsed

  • 记录时间戳:timestamp

  • 如果元素间延迟过长则中止序列:timeout

  • 以固定的周期发出元素:Flux.interval

  • 在一定的延迟后发出 0:static Mono.delay

  • 我想引入延迟:

  • 对每一个元素:Mono.delayElement,Flux.delayElements

  • 延迟订阅:delaySubscription

A.7. 拆分 Flux

  • 我想将一个 Flux<T> 拆分为一个 Flux<Flux<T>>:

  • 以个数为界: window(int)

  • …会出现重叠或丢弃的情况:window(int, int)

  • 以时间为界: window(Duration)

  • …会出现重叠或丢弃的情况:window(Duration, Duration)

  • 以个数或时间为界:windowTimeout(int, Duration)

  • 基于对元素的判断条件: windowUntil

  • …触发判断条件的元素会分到下一波(cutBefore 变量):.windowUntil(predicate, true)

  • …满足条件的元素在一波,直到不满足条件的元素发出开始下一波:windowWhile (不满足条件的元素会被丢弃)

  • 通过另一个 Publisher 的每一个 onNext 信号来拆分序列:window(Publisher),windowWhen

  • 我想将一个 Flux<T>的元素拆分到集合…

  • 拆分为一个一个的 List:

  • 以个数为界: buffer(int)

  • …会出现重叠或丢弃的情况:buffer(int, int)

  • 以时间为界: buffer(Duration)

  • …会出现重叠或丢弃的情况:buffer(Duration, Duration)

  • 以个数或时间为界:bufferTimeout(int, Duration)

  • 基于对元素的判断条件: bufferUntil(Predicate)

  • …触发判断条件的元素会分到下一个buffer:.bufferUntil(predicate, true)

  • …满足条件的元素在一个buffer,直到不满足条件的元素发出开始下一buffer:bufferWhile(Predicate)

  • 通过另一个 Publisher 的每一个 onNext 信号来拆分序列:buffer(Publisher),bufferWhen

  • 拆分到指定类型的 "collection":buffer(int, Supplier<C>)

  • 我想将 Flux<T> 中具有共同特征的元素分组到子 Flux:groupBy(Function<T,K>) TIP:注意返回值是 Flux<GroupedFlux<K, T>>,每一个 GroupedFlux 具有相同的 key 值 K,可以通过 key() 方法获取。

A.8. 回到同步的世界

  • 我有一个 Flux<T>,我想:

  • 在拿到第一个元素前阻塞: Flux.blockFirst

  • …并给出超时时限:Flux.blockFirst(Duration)

  • 在拿到最后一个元素前阻塞(如果序列为空则返回 null): Flux#blockLast

  • …并给出超时时限:Flux.blockLast(Duration)

  • 同步地转换为 Iterable<T>:Flux.toIterable

  • 同步地转换为 Java 8 Stream<T>:Flux.toStream

  • 我有一个 Mono<T>,我想:

  • 在拿到元素前阻塞: Mono.block

  • …并给出超时时限:Mono#block(Duration)

  • 转换为 CompletableFuture<T>:Mono.toFuture

响应式编程框架Reactor之 Flux 和 Mono 的介绍和区别 基本概念Mono是Reactor中的一个类,它表示一个异步的单个值或零个值的结果。Mono可以看作是一个特殊的Publisher,用于产生数据流,但这个数据流最多只包含一个元素,或者在没有任何元素时直接发出完成信号或错误信号。作用Mono主要用于处理那些预期会返回一个单一结果或不需要返回任何结果的异步操作,如数据库查询(假设查询结果是唯一的)、网络请求等。它提供了丰富的操作符来支持数据的转换、过滤、组合等操作,以及错误处理和资源管理等。创建方式可以通过Mono的静态方法创建,如等。 阅读详情

相关推荐

ROS2零拷贝避坑指南:Fast DDS共享内存配置常见问题与排查方法

本文深入解析ROS2 Humble中Fast DDS的零拷贝机制,提供共享内存配置的常见问题排查方法。从环境变量设置、XML配置文件到运行时诊断工具,全面指导开发者高效实现进程间数据传输优化,避免低效拷贝问题。特别针对/dev/shm无文件生成等典型场景给出解决方案。

weixin_30379531的博客 478

Mono.fromFuture() VS WebClient

刚开始尝试使用 Spring WebFlux 的时候,很多人都会使用 Mono.fromFuture() 将异步请求转成 Mono 对象,或者 Mono.fromSupplier() 将请求转成 MOno 对象,这两种方式在响应式编程中都是不建议的,都会阻塞当前线程。因此,可以使用 Mono.fromSupplier() 方法将一个纯计算型的操作转换为 Mono 对象,而将一个异步返回结果的操作转换为 Mono 对象时,可以使用 Mono.fromFuture() 方法。

shb1224的博客 652

Python抓取摄像头图像,进行AI识别(一)

本文介绍了如何用Python获取海康威视摄像头的图像并进行AI识别。主要内容包括:1)准备工作,包括摄像头IP设置和密码重置方法;2)使用Python的Requests库通过ISAPI接口获取实时图像;3)示例代码展示如何保存摄像头图像。整个过程为后续AI图像识别提供了基础,通过简单的Python代码即可实现专业监控设备的图像采集功能。

Hahafantuan的博客 1513

响应式编程简介

对于响应式编程的解释 个人理解: Java中传统的线程接口有两种,一种是Runable,一种是Callable;一个没有返回值,一个有返回值;Callable的返回值用Future来接收。无论是Runable还是Callable,都要等线程执行完才能继续操作下去,这个过程中就造成了阻塞。 如果想我把任务分配下去,并且告诉他任务执行完成就怎么怎么样,然后我就不用管了,这样一种思想可以理解成是响应式编程,就像是“好莱坞原则”(演员不用主动联系导演,导演会去联系演员,也就是你不要来找我,我会去找你)。 响

weixin_45577053的博客 4897

Rocky Linux 8.10下用cephadm快速搭建Ceph集群(附华为云镜像源配置)

本文详细介绍了在Rocky Linux 8.10操作系统上使用cephadm快速部署Ceph集群的实战指南,包括环境准备、系统配置、华为云镜像源优化、集群初始化及节点扩展等关键步骤。通过配置华为云镜像源,显著提升部署效率,适合企业级存储解决方案的需求。

weixin_30839881的博客 146

Arduino Uno 实验15——MQ-135 气体传感器模块

MQ-135 气体传感器模块简介(空气质量检测 有害气体检测)   MQ135气体传感器所使用的气敏材料是在清洁空气中电导率较低的二氧化锡(SnO2)。当传感器所处环境中存在污染气体时,传感器的电导率随空气中污染气体浓度的增加而增大。MQ135气体传感器对氨气、硫化物、苯系蒸汽的灵敏度高,对烟雾和其它有害气体的监测也很理想。这种传感器可检测多种有害气体,是一款适合多种应用的低成本传感器。   主要用途:家庭和环境的有害气体检测装置   主要芯片:LM393、MQ-135气体传感器 不同气体的型号选择

weixin_42645653的博客 2万+

CompletableFuture 和 Mono.fromFuture用法介绍结合@RestController使用

在 Spring Boot 的中,结合和可以实现高效的异步响应式编程。这种方式特别适合处理耗时的 I/O 操作,如数据库查询、外部服务调用等,而不会阻塞主线程。以下是一个完整的示例,展示如何在中使用和。

记录有价值的内容 691

27、振动信号实时并行处理与风力发电机模型鲁棒控制设计

本文探讨了振动信号实时并行处理与风力发电机模型鲁棒控制设计两大核心技术。在振动信号处理方面,提出基于FPGA、RT和PC三级架构的并行采集与处理系统,实现高效、实时的振动分析,适用于电机状态监测与预测性维护。在风力发电控制方面,研究风力农场布局优化(WFLO)问题,比较九种多目标元启发式算法性能,并提出基于扰动观察法的最大功率点跟踪(MPPT)控制策略,提升发电效率与系统鲁棒性。研究成果为工业设备智能监控与可再生能源优化提供了有效技术路径。

sunny的博客 72

Reactor 第十篇 定制一个生产的WebClient

刚开始尝试使用 Spring WebFlux 的时候,很多人都会使用 Mono.fromFuture() 将异步请求转成 Mono 对象,或者 Mono.fromSupplier() 将请求转成 MOno 对象,这两种方式在响应式编程中都是不建议的,都会阻塞当前线程。因此,可以使用 Mono.fromSupplier() 方法将一个纯计算型的操作转换为 Mono 对象,而将一个异步返回结果的操作转换为 Mono 对象时,可以使用 Mono.fromFuture() 方法。

伍六七的博客 1317

4、风险管理、信息安全与信息管理的核心原则

本文深入探讨了风险管理、信息安全与信息管理的核心原则,涵盖Risk IT框架、ISO 31000标准以及信息安全与信息管理的关键原则。通过案例分析与实际应用挑战的解析,帮助组织更好地应对数字化时代的风险与安全问题,实现可持续发展。

bread的博客 120

构建具有预测维护能力的AI Agent

随着工业4.0的推进,工业设备的复杂性和自动化程度不断提高,设备的稳定运行对于企业的生产效率和经济效益至关重要。预测维护作为一种先进的设备维护策略,能够通过对设备状态的实时监测和分析,提前预测设备故障,从而避免设备意外停机,降低维护成本。本文的目的是详细阐述如何构建具有预测维护能力的AI Agent,涵盖从数据采集、处理、模型训练到实际应用的整个流程,为企业和开发者提供全面的技术指导。本文将按照以下结构进行组织:首先介绍核心概念与联系,包括预测维护和AI Agent的基本原理和架构;

2501_92132293的博客 943

响应式编程库Reactor 3 Reference Guide参考文档中文版(v3.2.0)

中文翻译源码地址: https://github.com/get-set/reactor-core/tree/master-zh/src/docs/asciidoc。官方参考文档地址: http://projectreactor.io/docs/core/release/reference/

猪之仆的博客 1483

深入解析机器人软件开发中的核心:状态机管理(FSM)—— 理论、实践与优化

状态机(FSM)是机器人软件开发中管理复杂行为逻辑的核心工具,广泛应用于导航、任务执行、故障处理等场景。本文系统探讨状态机的基础理论(定义、要素)、主流设计模式(经典FSM、分层状态机HSM、行为树BT),以及工业级实现方法(代码架构、线程安全、调试技巧)。通过移动机器人导航和机械臂任务执行的案例,分析分层状态机的实际应用,并总结常见陷阱(如状态爆炸、阻塞操作)与优化策略(异步化、事件过滤)。

郑伟强dev的专栏 292

Mono和Flux的用法详解

WebFlux的Flux和Mono用法 Flux just 可以指定序列中包含的全部元素。创建出来的 Flux 序列在发布这些元素之后会自动结束。 fromArray(),fromIterable()和 fromStream() 可以从一个数组、Iterable 对象或 Stream 对象中创建 Flux 对象。 empty() 创建一个不包含任何元素,只发布结束消息的序列,在响应式编程中,流的...

lz710117239的博客 5万+

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

Mono类深度解析:响应式编程中的异步处理

Mono 是 Project Reactor 提供的响应式类型,代表最多包含一个元素的异步序列。它实现了 Reactive Streams 的 Publisher 接口,专门用于处理0或1个元素的数据流。核心特性单值或空序列发射非阻塞异步处理延迟执行(Lazy Evaluation)背压(Backpressure)支持丰富的操作符集合Mono 作为响应式编程的基础构建块,为现代异步系统开发提供了强大而灵活的工具集。通过本文的全面介绍,您应该已经掌握了从基础创建到高级应用的全套技能。

a_beiyo的博客 1819

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

Reactor中文文档

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34291004的博客 7969

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

附2:Reactor 3 之选择合适的操作符——响应式Spring的道法术器

本系列文章索引《响应式Spring的道法术器》 前情提要Reactor Operators 本节的内容来自我翻译的Reactor 3 参考文档——如何选择操作符。由于部分朋友打开github.io网速比较慢或上不去,贴出来方便大家查阅。 如果一个操作符是专属于Flux或Mono的,那么会给它注明前缀。 公共的操作符没有前缀。如果一个具体的用例涉及多个操作符的组合,这里以方法调用的方式展现, 会以一个点(.)开头,并将参数置于圆括号内,比如:.methodCall(parameter)。...

ywb201314的专栏 1431

reactor官方文档译文(1)Reactor简介

原文地址:http://projectreactor.io/docs/reference/ Reactor简介 Reactor是一个基础库,用在构建实时数据流应用、要求有容错和低延迟至毫秒、纳秒、皮秒的服务。 — PrefaceTL;DR 什么是Reactor?   让我们大致了解一下Reactor。在你使用喜欢的搜索敲入一些关键词如Reactive、spring Reacti...

420

响应式编程库Reactor(一)

响应式编程是一种关注于数据流(data streams)和变化传递(propagation of change)的异步编程方式。这意味着它可以用既有的编程语言表达静态(如数组)或动态(如事件源)的数据流。了解历史:● 在响应式编程方面,微软跨出了第一步,它在 .NET 生态中创建了响应式扩展库(Reactive Extensions library, Rx)。接着 RxJava 在JVM上实现了响应式编程。后来,在 JVM 平台出现了一套标准的响应式 编程规范,它定义了一系列标准接口和交互规范。

二掌柜的博客 1549

Mono 的创建

Reactor 是一个基于 JVM 之上的异步应用基础库。为 Java 、Groovy 和其他 JVM 语言提供了构建基于事件和数据驱动应用的抽象库,可用它构建时效性流式数据应用,实现应用高效、异步地传递消息。 Reactor 性能相当高,在最新的硬件平台上,使用无堵塞分发器每秒钟可处理 1500 万事件。 Reactor 中的两个重要的概念是Flux 和 Mono。Mono 表示包含 0 或者 1 个元素的异步序列。Flux 表示包含 0 到 N 个元素的异步序列。他们包含

码易的博客 2361

响应式编程库Reactor 3 Reference Guide参考文档中文版

Project Reactor 是 Spring WebFlux 的御用响应式编程库,与 Spring 是兄弟项目。 关于如何基于Spring的组件进行响应式应用的开发,欢迎阅读我的系列文章《响应式Spring的道法术器》。 官方参考文档地址:http://projectreactor.io/docs/core/release/reference/ 中文翻译文档地址:http://h...

刘康的专栏 1万+

众包翻译文档分享 ——《Reactor 指南中文版》

《Reactor 指南中文版》日前在开源中国众包平台翻译完成,现发布在社区与各位 OSCer 共享: http://projectreactor.mydoc.io/ Reactor 官方文档翻译众包悬赏现已结赏,悬赏和结赏详情请看这里,感谢各位 OSCer 的支持。 Reactor 是一个基于 JVM 之上的异步应用基础库。为 Java 、Groovy ...

weixin_34417200的博客 488

CompletableFuture的常用用法

CompletableFuture.supplyAsync创建带返回值的异步任务 CompletableFuture.allOf //allof等待所有任务执行完成才执行,如果有一个任务异常终止,则.get时会抛出异常,都是正常执行,.get返回null //anyOf是只有一个任务执行完成,无论是正常执行或者执行异常,都会执行.get的结果就是已执行完成的任务的执行结果 thenApply 表示某个任务执行完成后执行的动作,即回调方法,会将该任务的执行结果即方法返回值作为入参传递到回调方法中the..

qq_39618511的博客 2004
上一篇: GrowingIO Terraform 实践
GrowingIO_
博客等级 码龄8年 56粉丝 61原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值