webflux小结

本文介绍了Reactor中的Flux和Mono,它们作为响应式流的发布者,能处理元素值、错误信号和完成信号。文章详细阐述了两者之间的转换、常用操作符如map、flatMap、filter、retry等的用法,以及错误处理和背压策略,如onBackPressureBuffer和limitRate等。
摘要由CSDN通过智能技术生成

Flux与Mono

Reactor中的发布者(Publisher)由Flux和Mono两个类定义,它们都提供了丰富的操作符(operator)。
一个Flux对象代表一个包含0…N个元素的响应式序列,而一个Mono对象代表一个包含零/一个(0…1)元素的结果。

既然是“数据流”的发布者,Flux和Mono都可以发出三种“数据信号”:元素值、错误信号、完成信号,错误信号和完成信号都是终止信号,完成信号用于告知下游订阅者该数据流正常结束,错误信号终止数据流的同时将错误传递给下游订阅者。

这三种信号都不是一定要具备的:

  • 首先,错误信号和完成信号都是终止信号,二者不可能同时共存;
  • 如果没有发出任何一个元素值,而是直接发出完成/错误信号,表示这是一个空数据流;
  • 如果没有错误信号和完成信号,那么就是一个无限数据流。

操作符(Operator)

Flux和Mono提供了多种创建数据流的方法,just就是一种比较直接的声明数据流的方式,其参数就是数据元素。

Flux.just(1, 2, 3, 4, 5);
Mono.just(1);

Flux,还可以通过如下方式声明(分别基于数组、集合和Stream生成):

Integer[] array = new Integer[]{1,2,3,4,5};
Flux.fromArray(array);
List<Integer> list = Arrays.asList(array);
Flux.fromIterable(list);
Stream<Integer> stream = list.stream();
Flux.fromStream(stream);

Mono和flux互转:
Flux -> Mono<List>
1.第一种:如果 flux 是空的, single 会返回一个 Mono.error, 消费的时候就会抛异常

Flux<String> flux;
Mono<List<String>> mono = flux.buffer().single()

2.第二种:collectList 是把数据存在内存里,注意数据量对内存的影响

Flux<String> flux;
Mono<List<String>> mono = flux.collectList();

Mono -> Flux

List<A> list;
Flux<A> flux = Flux.fromIterable(list).collectList();

Flux<String> newFlux =listMono.flatMapMany(Flux::fromIterable);

map 和flatMap区别

  • map操作可以将数据元素进行转换/映射,得到一个新元素。
  • flatMap操作可以将每个数据元素转换/映射为一个流,然后将这些流合并为一个大的数据流。
    map和flatMap都可以对RxJava传入的数据进行变换。

举例略

注意:flatMap

流的合并是异步的,先来先到,并非是严格按照原始序列的顺序。想要按顺序获取数据可以用concatMap,concatMap与flatMap使用基本一致,它可以保证数据有序

filter
filter操作可以对数据元素进行筛选。

StepVerifier.create(Flux.range(1, 6)
            .filter(i -> i % 2 == 1)    // 1
            .map(i -> i * i))
            .expectNext(1, 9, 25)   // 2
            .verifyComplete();

doFinally
doFinally在序列终止(无论是 onComplete、onError还是取消)的时候被执行, 并且能够判断是什么类型的终止事件(完成、错误还是取消),以便进行针对性的处理。如:

LongAdder statsCancel = new LongAdder();    // 用LongAdder进行统计;

Flux<String> flux =
Flux.just("foo", "bar")
    .doFinally(type -> {
        if (type == SignalType.CANCEL)  // doFinally用SignalType检查了终止信号的类型;
          statsCancel.increment();  // 如果是取消,那么统计数据自增
    })
    .take(1);   // take(1)能够在发出1个元素后取消流。

retry
retry,用它可以对出现错误的序列进行重试。
请注意:retry对于上游Flux是采取的重订阅(re-subscribing)的方式,因此重试之后实际上已经一个不同的序列了, 发出错误信号的序列仍然是终止了的。举例如下:

Flux.range(1, 6)
    .map(i -> 10 / (3 - i))
    .retry(1)
    .subscribe(System.out::println, System.err::println);
Thread.sleep(100);  // 确保序列执行完

输出:

5
10
5
10
java.lang.ArithmeticException: / by zero

zip - 一对一合并
Flux的zip方法接受Flux或Mono为参数,Mono的zip方法只能接受Mono类型的参数。
它对两个Flux/Mono流每次各取一个元素,合并为一个二元组(Tuple2)
zip是静态方法,示例:

private Flux<String> getZipDescFlux() {
    String desc = "Zip two sources together, that is to say wait for all the sources to emit one element and combine these elements once into a Tuple2.";
    return Flux.fromArray(desc.split("\\s+"));  // 将英文说明用空格拆分为字符串流;
}

@Test
public void testSimpleOperators() throws InterruptedException {
    CountDownLatch countDownLatch = new CountDownLatch(1);  // 定义一个CountDownLatch,初始为1,则会等待执行1次countDown方法后结束,不使用它的话,测试方法所在的线程会直接返回而不会等待数据流发出完毕;
    Flux.zip(
            getZipDescFlux(),
            Flux.interval(Duration.ofMillis(200)))  // 使用Flux.interval声明一个每200ms发出一个元素的long数据流;因为zip操作是一对一的,故而将其与字符串流zip之后,字符串流也将具有同样的速度;
            .subscribe(t -> System.out.println(t.getT1()), null, countDownLatch::countDown);    // zip之后的流中元素类型为Tuple2,使用getT1方法拿到字符串流的元素;定义完成信号的处理为countDown;
    countDownLatch.await(10, TimeUnit.SECONDS);     // countDownLatch.await(10, TimeUnit.SECONDS)会等待countDown倒数至0,最多等待10秒钟。
}

zip静态方法之外,还有zipWith等非静态方法,效果与之类似:

getZipDescFlux().zipWith(Flux.interval(Duration.ofMillis(200)))

数据流转换方法

concat
先运行完flux1之后再运行flux2

Flux.concat(hotFlux1(), hotFlux2())
                .subscribe(i -> System.out.print("->"+i));
Thread.sleep(200);

merge
是按照时间先后执行

Flux.merge(hotFlux1(), hotFlux2())
            .subscribe(i -> System.out.print("->"+i));
Thread.sleep(200);

对比concat和merge:
concat是合并的flux,按照顺序分别运行,flux1运行完成以后再运行flux2
merge是同时运行,根据时间先后运行

补充:
concatWith用法和concat基本相同,写法略有不同:

flux1().concatWith(flux2())
            .log()
            .subscribe();

mergeWith用法和merge相同,写法不同

hotFlux1().mergeWith(hotFlux2())
            .subscribe(i -> System.out.print("->"+i));
Thread.sleep(200);

mergeSequential和concat有些相似,得到的结果类似
跟concat不同在于,订阅的源是hot型,接收数据后根据订阅顺序重新排序

Flux.mergeSequential(hotFlux1(), hotFlux2())
            .subscribe(i -> System.out.print("->"+i));
Thread.sleep(200);

mergeOrdered用法
合并接收之后再排序

Flux.mergeOrdered(Comparator.reverseOrder(), hotFlux1(), hotFlux2())
                .subscribe(i -> System.out.print("->"+i));
Thread.sleep(200);

combineLatest用法
跟concat和merge不同该方法是将多个源的最后得到元素通过函数进行融合的到新的值

Flux.combineLatest(hotFlux1(), hotFlux2(), (v1, v2) -> v1 + ":" + v2)
            .subscribe(i -> System.out.print("->"+i));
Thread.sleep(200);

Error处理

Reactor提供了其他的用于在链中处理错误的操作符(error-handling operators),使得对于错误信号的处理更加及时,处理方式更加多样化。

借助命令式编程风格的 try 代码块来作比较。我们都很熟悉在 try-catch 代码块中处理异常的几种方法。常见的包括如下几种:

  • 捕获并返回一个静态的缺省值。
  • 捕获并执行一个异常处理方法或动态计算一个候补值来顶替。
  • 捕获,并再包装为某一个业务相关的异常,然后再抛出业务异常。
  • 捕获,记录错误日志,然后继续抛出。 使用 finally 来清理资源,或使用 Java 7 引入的"try-with-resource"。

以上所有这些在 Reactor 都有相应的基于 error-handling 操作符处理方式。

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

onErrorReturn方法能够在收到错误信号的时候提供一个缺省值:

Flux.range(1, 6)
    .map(i -> 10/(i-3))
    .onErrorReturn(0)   // 当发生异常时提供一个缺省值0
    .map(i -> i*i)
    .subscribe(System.out::println, System.err::println);
  1. 捕获并执行一个异常处理方法或计算一个候补值来顶替
    onErrorResume方法能够在收到错误信号的时候提供一个新的数据流:
Flux.just(endpoint1, endpoint2)
    .flatMap(k -> callExternalService(k))   // 调用外部服务;
    .onErrorResume(e -> getFromCache(k));   // 如果外部服务异常,则从缓存中取值代替。提供新的数据流
  1. 捕获,并再包装为某一个业务相关的异常,然后再抛出业务异常
    有时候,我们收到异常后并不想立即处理,而是会包装成一个业务相关的异常交给后续的逻辑处理,可以使用onErrorMap方法:
Flux.just("timeout1")
    .flatMap(k -> callExternalService(k))   // 调用外部服务;
    .onErrorMap(original -> new BusinessException("error", original)); // 如果外部服务异常,将其包装为业务相关的异常后再次抛出。

这一功能其实也可以用onErrorResume实现

Flux.just("timeout1")
    .flatMap(k -> callExternalService(k))
    .onErrorResume(original -> Flux.error(
        new BusinessException("error", original)
    );
  1. 捕获,记录错误日志,然后继续抛出
    如果对于错误你只是想在不改变它的情况下做出响应(如记录日志),并让错误继续传递下去, 那么可以用doOnError 方法。如doOnXxx是只读的,对数据流不会造成影响:
Flux.just(endpoint1, endpoint2)
    .flatMap(k -> callExternalService(k)) 
    .doOnError(e -> {   // 只读地拿到错误信息,错误信号会继续向下游传递;
        log("error log : " + k);    // 记录日志。
    })
    .onErrorResume(e -> getFromCache(k)); 
  1. 使用 finally 来清理资源,或使用 Java 7 引入的 “try-with-resource”
Flux.using(
        () -> getResource(),    // 第一个参数获取资源;
        resource -> Flux.just(resource.getAll()),   // 第二个参数利用资源生成数据流;
        MyResource::clean   // 第三个参数最终清理资源。
);

背压策略
reactor提供了集中背压策略
onBackPressureBuffer - 顾名思义,来不及消费的数据先缓存在队列里(默认策略)
onBackPressureDrop - drop,丢掉,来不及消费的数据直接扔掉
onBackPressureLatest - 保留最新数据,一旦下游请求出现,立即推向下游
onBackPressureError - 下游消费速度跟不上,直接抛异常报错
limitRate(n) - 限速,上游一次最多发n个
举例推荐看:https://blog.csdn.net/FightingITPanda/article/details/119209864

官方文档
https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Flux.html#zipWith-org.reactivestreams.Publisher

参考文章:
https://blog.csdn.net/wangkai525and526/article/details/107729472
https://www.jianshu.com/p/fcb4f4aebf68
https://cloud.tencent.com/developer/article/1526029

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值