携手创作,共同成长!这是我参与「掘金日新计划 · 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));
- 第一个参数获取资源;
- 第二个参数利用资源生成数据流;
- 第三个参数最终清理资源。
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
- 用
LongAdder进行统计; doFinally用SignalType检查了终止信号的类型;- 如果是取消,那么统计数据自增;
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
}
});
}