WebFlux小记

382 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第15天,点击查看活动详情

Reactor、WebFlux的联系和区别?

WebFlux 是spring的 reactor 框架,nacos支持响应式编程

另外Reactive不支持MySQL,进一步也不支持MySQL事务。

Reactor中发布者是 Flux和Mono,其余的是他们的操作符,Flux可以包含多个元素,Mono最多只能包含一个元素。

数据流的三种信号

元素值、错误信号、完成信号。

创建数据源的方法

just
Flux.just(1, 2, 3, 4)
Mono.just(1)
基于数组、集合和Stream生产
Flux.fromArray(array);
Flux.fromIterrable(list);
Flux.fromStream(stream);
空数据流和错误数据流
Flux.empty();
Mono.justOrEmpty(Optional.empty());
Flux.error(new Exception("some error"));

订阅信号

subscribe
// 订阅并触发数据流
subscribe(); 
// 订阅并指定对正常数据元素如何处理
subscribe(Consumer<? super T> consumer); 
// 订阅并定义对正常数据元素和错误信号的处理
subscribe(Consumer<? super T> consumer,
          Consumer<? super Throwable> errorConsumer); 
// 订阅并定义对正常数据元素、错误信号和完成信号的处理
subscribe(Consumer<? super T> consumer,
          Consumer<? super Throwable> errorConsumer,
          Runnable completeConsumer); 
// 订阅并定义对正常数据元素、错误信号和完成信号的处理,以及订阅发生时的处理逻辑
subscribe(Consumer<? super T> consumer,
          Consumer<? super Throwable> errorConsumer,
          Runnable completeConsumer,
          Consumer<? super Subscription> subscriptionConsumer); 

测试与调试

StepVerifier
@Test
public void testViaStepVerifier() {
    StepVerifier.create(generateFluxFrom1To6())
            .expectNext(1, 2, 3, 4, 5, 6)
            .expectComplete()
            .verify();
    StepVerifier.create(generateMonoWithError())
            .expectErrorMessage("some error")
            .verify();
}

expectNext用于测试下一个期望的数据元素,expectErrorMessage用于校验下一个元素是否为错误信号,expectComplete用于测试下一个元素是否为完成信号。

操作符

map--同步将元素映射为新元素
StepVerifier.create(Flux.range(1, 6)    // 1
            .map(i -> i * i))   // 2
            .expectNext(1, 4, 9, 16, 25, 36)    //3
            .expectComplete();  // 4
flatmap

通常用于每个元素又会引入数据流的情况

urlFlux.flatMap(url -> requestUrl(url));
filter

filter接受一个Predicate的函数式接口为参数,这个函数式的作用是进行判断并返回boolean

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

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.”,我们希望将这句话拆分为一个一个的单词并以每200ms一个的速度发出,除了前面flatMap的例子中用到的delayElements

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+"));  // 1
}

@Test
public void testSimpleOperators() throws InterruptedException {
    CountDownLatch countDownLatch = new CountDownLatch(1);  // 2
    Flux.zip(
            getZipDescFlux(),
            Flux.interval(Duration.ofMillis(200)))  // 3
            .subscribe(t -> System.out.println(t.getT1()), null, countDownLatch::countDown);    // 4
    countDownLatch.await(10, TimeUnit.SECONDS);     // 5
}

错误处理

onErrorReturn

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

Flux.range(1, 6)
                .map(i -> 10 / (i - 3))
                .onErrorReturn(0)
                .map(i -> i * i)
                .subscribe(System.out::println, System.err::println);
onErrorResume

捕获并执行一个异常处理方法或计算一个候补值来顶替

Flux.range(1, 6)
                .map(i -> 10 / (i - 3))
                .onErrorResume(e -> Mono.just(new Random().nextInt(6)))
                .map(i -> i * i)
                .subscribe(System.out::println, System.err::println);
onErrorContinue

另外补充onErrorContinue, 在返回值后,不会立即退出,而会执行下面的流(ps,不会终止程序的报错)

Flux.range(1, 6)
                .map(i -> 10 / (i - 3))
                .onErrorContinue( (e,a) -> System.out.println("error" + e))
                .map(i -> i * i)
                .subscribe(System.out::println, System.err::println);
onErrorMap

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

Flux.just("timeout1")
    .flatMap(k -> callExternalService(k))
    .onErrorResume(original -> Flux.error(
        new BusinessException("SLA exceeded", original)
    );
doOnError

如果对于错误你只是想在不改变它的情况下做出响应(如记录日志),并让错误继续传递下去, 那么可以用doOnError 方法。

Flux.just(endpoint1, endpoint2)
    .flatMap(k -> callExternalService(k)) 
    .doOnError(e -> {   // 1
        log("uh oh, falling back, service failed for key " + k);    // 2
    })
    .onErrorResume(e -> getFromCache(k)); 

  1. 第一个参数获取资源;
  2. 第二个参数利用资源生成数据流;
  3. 第三个参数最终清理资源。
doFinally

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

LongAdder statsCancel = new LongAdder();    // 1

Flux<String> flux =
Flux.just("foo", "bar")
    .doFinally(type -> {
        if (type == SignalType.CANCEL)  // 2
          statsCancel.increment();  // 3
    })
    .take(1);   // 4

  1. LongAdder进行统计;
  2. doFinallySignalType检查了终止信号的类型;
  3. 如果是取消,那么统计数据自增;
  4. take(1)能够在发出1个元素后取消流。
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);  // 确保序列执行完

retry(1)是重试1次的意思

回压

.subscribe(System.out::println)发起一个无限的请求,就是对于数据流中的元素无论快慢都照单全收

其实他是subscribe(Subscribe subscribe)的变体,接受一个Subscribe参数可以更灵活的定义

subscribe


@Test
public void testBackpressure() {
    Flux.range(1, 6)    // 1
            .doOnRequest(n -> System.out.println("Request " + n + " values..."))    // 2
            .subscribe(new BaseSubscriber<Integer>() {  // 3
                @Override
                protected void hookOnSubscribe(Subscription subscription) { // 4
                    System.out.println("Subscribed and make a request...");
                    request(1); // 5
                }

                @Override
                protected void hookOnNext(Integer value) {  // 6
                    try {
                        TimeUnit.SECONDS.sleep(1);  // 7
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Get value [" + value + "]");    // 8
                    request(1); // 9
                }
            });
}