2019独角兽企业重金招聘Python工程师标准>>>
- 响应式编程概述 官方定义:响应式编程(Reactive programming),它是异步编程下的一个子集,也是一种范式,在这种范式下,由新信息的有效性(availability)推动逻辑的前进,而不是让一条执行线程去推动控制流(区别于命令式编程)。
ps:何为范式?
可以通过对比迭代器模式来了解响应式编程的基本思想:
Event
Iterable
Observable
get
T next()
onNext(T)
error
throws Exception
onError(Exception)
complete
!hasNext()
onCompleted()
Observable 那一列便代表响应式编程的 API 使用方式,显而易见,响应式编程既是观察者模式的一种扩展。针对响应式流,Iterable基于Pull方式,而Observable基于Push方式。Iterable模式是典型的命令式编程范式,因为什么时候获取下一个元素取决于调用方;而在响应式编程范式下,当发布者通知订阅者有新数据后,通过一种声明式来定义对数据流的处理逻辑(借助于lambda)。
总结起来,响应式编程是一种基于数据流(data stream)和变化传递(propagation of change)的声明式(declarative)的编程范式。
响应式流规范(Reactive Streams)的统一标准与接口定义:
process a potentially unbounded number of elements(具有处理无限数量的元素的能力) in sequence(保证按序处理) asynchronously passing elements between components(异步地传递元素) with mandatory non-blocking backpressure(强制实现非阻塞的回压机制)
public interface Publisher
public interface Subscriber
public interface Subscription { public void request(long n); public void cancel(); } Subscription扮演Publisher和Subscriber之间的“掮客”(Broker)
public interface Processor
当调用subscribe方法时,发布者会回调订阅者的onSubscribe方法,并传入Subscription对象,之后订阅者就通过Subscription向发布者索要数据,如下图所示:
基础业务/公共服务中心 > 2018/10/04 > Reactor入门实战 > reactive.png
① 订阅者调用Subscription的request方法向发布者请求n个元素
② 发布者通过不断回调订阅者的onNext方法向订阅者最多发出n个元素
③ 如果元素发送完毕,则发布者回调订阅者的onComplete方法告知订阅者并终止流;如果有错误发生,则回调订阅者的onError方法发出错误信号,同样也会终止流。
ps:在Java 9版本中,响应式流的规范已被纳入到JDK(java.util.concurrent.Flow)
回到正题,Reactor既是目前其中一个实现了响应式流规范的JAVA编程库,与另一流行的编程库RxJava相比,它没有任何历史包袱,专注于Server端的响应式开发,而RxJava更多倾向于Android端的响应式开发。
ps:Reactor与Spring是兄弟项目,Spring5力推的WebFlux就是使用它来支撑响应式流。
- Reactor快速上手 2.1 配置Maven
3.1.8.RELEASE 3.1.8.RELEASE
2.2 Flux与Mono Reactor中的发布者(Publisher)由Flux和Mono两个类定义,它们都提供了丰富的操作符(operator)。一个Flux对象代表一个包含0..N个元素的响应式序列,而一个Mono对象代表一个包含零/一个(0..1)元素的结果。
既然是“数据流”的发布者,Flux和Mono都可以发出三种“数据信号”:元素值、错误信号、完成信号,错误信号和完成信号都是终止信号,完成信号用于告知下游订阅者该数据流正常结束,错误信号终止数据流的同时将错误传递给下游订阅者。
// 声明一组数据流, 订阅后简单的将其按序打印 System.out.println("example 1:"); Flux.just(1, 2, 3, 4, 5).subscribe(System.out::println);
// 声明List数据流的错误方式 System.out.println("\nexample 2:"); Flux.just(Lists.newArrayList(1, 2, 3, 4, 5)).subscribe(System.out::println);
// 声明List数据流的正确方式(fromStream/fromIterable) System.out.println("\nexample 3:"); Flux.fromStream(Lists.newArrayList(1, 2, 3, 4, 5).stream()).subscribe(System.out::println);
// 可以对完成信号编写处理逻辑 System.out.println("\nexample 4:"); Flux.range(1, 5).subscribe(System.out::println, System.err::println, () -> System.out.println("completed"));
/**
- 复杂数据流通过generate/create生成, 主要区别在于:
- generate方法是同步地、逐个地产生值, 而create方法的生成方式既可以是同步, 也可以是异步的, 并且还可以每次发出多个元素 */ System.out.println("\nexample 5:"); final int length = 5; final Random random = new Random(); Flux.generate(ArrayList::new, (list, sink) -> { int value = random.nextInt(100); list.add(value); sink.next(value); if (list.size() == length) { sink.complete(); } return list; }).subscribe(System.out::println);
// 使用create生成复杂数据流 System.out.println("\nexample 6:"); Flux.create(sink -> { for (int i = 0; i < length; i++) { /** * 加上这个打印后审视subscribe的输出结果, 可以观测到数据流的确是响应式的: * 框架实现遵循了Reactive Streams规范, 通过调用Subscription.request(i)来获取下一个数据 */ System.out.println(String.format("i = %d", i)); sink.next(random.nextInt(100)); } sink.complete(); }).subscribe(System.out::println);
// 只声明了数据流但不调用subscribe方法, 此时不会触发数据流, 也不会有任何元素被发送, 即: 订阅前什么都不会发生 System.out.println("\nexample 7:"); Flux.create(sink -> { for (int i = 0; i < length; i++) { System.out.println(String.format("i = %d", i)); sink.next(random.nextInt(100)); } sink.complete(); });
2.3 测试工具(StepVerifier) StepVerifier是官方提供的测试集,针对响应式流的特性提供了许多强大的测试方法来对流中的元素进行逐一校验,可以结合JUnit等工具来编写单元测试。后面的例子中会频繁使用StepVerifier来做代码演示,所以,先了解下它的基本用法是很有必要的。
private Flux
private Mono
@Test public void testCount() { // 用create方法来对一个数据流包装后添加测试代码 StepVerifier.create(generateDigitsFlux()) .expectNextCount(5) .expectComplete() .verify(); }
@Test public void testNext() { // expectNext接受1...N个可变参数, 并按其顺序比较接收到的元素值是否匹配 StepVerifier.create(generateDigitsFlux()) .expectNext(1, 2, 3, 4, 5) .expectComplete() .verify(); // expectNextSequence接收一个iterator, 并按其顺序比较接收到的元素值是否匹配 StepVerifier.create(generateDigitsFlux()) .expectNextSequence(Lists.newArrayList(1, 2, 3, 4, 5)) .expectComplete() .verify(); }
@Test public void testError() { // 终止方法(如expectError, expectComplete...)返回的是StepVerifier, 而不是Step对象, 所以不能继续添加expect方法来做下一步测试 StepVerifier.create(generateMonoWithError()) .expectError() .verify(); StepVerifier.create(generateMonoWithError()) .expectError(IOException.class) .verify(); StepVerifier.create(generateMonoWithError()) .expectErrorMessage(ResultCode.REMOTE_EXCEPTION.message()) .verify(); }
2.4 操作符(Operator) Reactor提供了许多函数式风格的方法用于操作流中的元素,称之为操作符(Operator),用法与Java Stream API相通,都支持lambda表达式,且类似操作的方法名都是一样的,所以理解起来应该很容易。
针对响应式流的特性,Reactor的操作符更加丰富,可以说,如果没有这些操作符的支持,用Reactor编写程序即便不是寸步难行,也算退到了刀耕火种的时代。
Reactor支持的操作符众多,且很多操作符都有不同的变体,这里只挑几个最常用的来做演示,更多的用法还得在使用过程中持续学习&熟悉。
@Test public void testFilter() { StepVerifier.create(Flux.range(1, 5) .filter(i -> i % 2 == 0)) .expectNext(2, 4) .expectComplete(); }
@Test public void testReduce() { StepVerifier.create(Flux.range(1, 5) .reduce((x, y) -> x + y)) .expectNext(15) .expectComplete(); // reduceWith可以给定一个初始值 StepVerifier.create(Flux.range(1, 5) .reduceWith(() -> 5, (x, y) -> x + y)) .expectNext(20) .expectComplete(); }
@Test public void testMap() { StepVerifier.create(Flux.range(1, 5) .map(i -> String.valueOf(i))) .expectNext("1", "2", "3", "4", "5") .expectComplete(); }
@Test public void testFlatMap() { // flatMap会将多个流中的元素按照实际生成顺序返回 StepVerifier.create(Flux.range(1, 5) .flatMap(i -> Flux.fromStream(Stream.of(i, -i)) .delayElements(Duration.ofMillis(50))) // doOnNext是个"偷窥式"方法, 不会消费元素 .doOnNext(i -> System.out.print(i + " "))) .expectNextCount(10) .verifyComplete(); System.out.println(); // 而flatMapSequential则按照所有流的订阅顺序返回 StepVerifier.create(Flux.range(1, 5) .flatMapSequential(i -> Flux.fromStream(Stream.of(i, -i)) .delayElements(Duration.ofMillis(50))) .doOnNext(i -> System.out.print(i + " "))) .expectNextCount(10) .verifyComplete(); }
@Test public void testMerge() { // merge与mergeSequential的区别与flatMap与flatMapSequential一样 StepVerifier.create(Flux.merge( Flux.range(1, 5).delayElements(Duration.ofMillis(50)), Flux.range(6, 5).delayElements(Duration.ofMillis(30))) .doOnNext(i -> System.out.print(i + " "))) .expectNextCount(10) .verifyComplete(); System.out.println(); StepVerifier.create(Flux.mergeSequential( Flux.range(1, 5).delayElements(Duration.ofMillis(50)), Flux.range(6, 5).delayElements(Duration.ofMillis(30))) .doOnNext(i -> System.out.print(i + " "))) .expectNextCount(10) .verifyComplete(); }
@Test public void testZip() { // 都是合并方法, 但与merge不同的是, zip将多个流中的元素进行一对一的合并 StepVerifier.create(Flux.zip( Flux.range(1, 5), Flux.just("a", "b", "c", "d", "e")) .doOnNext(arr -> System.out.print(arr + " "))) .expectNextCount(5) .verifyComplete(); }
@Test public void testTake() { // 从头开始获取指定数量的元素 StepVerifier.create(Flux.range(1, 10) .take(3)) .expectNext(1, 2, 3) .expectComplete(); // 从尾开始获取指定数量的元素 StepVerifier.create(Flux.range(1, 10) .takeLast(3)) .expectNext(8, 9, 10) .expectComplete(); // 当满足条件时, 才提取符合条件的元素 StepVerifier.create(Flux.range(1, 10) .takeWhile(i -> i < 5)) .expectNext(1, 2, 3, 4) .expectComplete(); // 注意takeWhile仅当一开始满足条件时才做提取, 并不会迭代提取符合条件的元素 StepVerifier.create(Flux.range(1, 10) .takeWhile(i -> i > 5)) .expectNextCount(0) .expectComplete(); // 提取元素直到满足条件后退出 StepVerifier.create(Flux.range(1, 10) .takeUntil(i -> i > 5)) .expectNext(1, 2, 3, 4, 5, 6) .expectComplete(); // 注意takeUntil如果没有满足条件的元素, 将提取所有元素! StepVerifier.create(Flux.range(1, 10) .takeUntil(i -> i < 0)) .expectNext(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .expectComplete(); }
2.5 错误处理(Error-Handling) 在响应式流中,error是终止信号,当有错误发生时,它会导致流序列停止,并且错误信号会沿着操作链向下传递,直至遇到subscribe中的错误处理方法:
public final Disposable subscribe( @Nullable Consumer super T> consumer, @Nullable Consumer super Throwable> errorConsumer, @Nullable Runnable completeConsumer) 实践中,都应该编写errorConsumer妥善处理错误,避免将错误不经修饰的返回给用户。此外,Reactor还提供了用于在链中处理错误的操作符,丰富了对错误处理方式的多样化。
private Flux
private Flux
@Test public void testOnErrorReturn() { // onErrorReturn捕获异常后返回一个给定的缺省值 StepVerifier.create(generateTextFluxError() .onErrorReturn("-1") .map(s -> Integer.valueOf(s)) .reduce((x, y) -> x + y)) .expectNext(-1) .verifyComplete(); /** * onErrorReturn只能返回一个缺省值, 如果应用了操作符后出现异常, 不会继续下一个元素的计算, 故该计算结果是3而不是12 * 因此, 在实际应用中, 对Mono做此操作的场景或许更多一些? */ StepVerifier.create(generateTextFlux() .map(s -> Integer.valueOf(s)) .onErrorReturn(0) .reduce((x, y) -> x + y)) .expectNext(3) .verifyComplete(); // 当捕获指定异常时才返回一个给定的缺省值 StepVerifier.create(generateTextFlux() .map(s -> Integer.valueOf(s)) .onErrorReturn(NumberFormatException.class, 0) .reduce((x, y) -> x + y)) .expectNext(3) .verifyComplete(); }
@Test public void testOnErrorResume() { /** * onErrorResume与onErrorReturn的唯一区别是可以编写异常处理函数来返回一个给定的缺省值 * 实际上, onErrorReturn正是通过调用onErrorResume来返回缺省值 */ StepVerifier.create(generateTextFluxError() // onErrorResume还有个实用的场景是: 当发生错误时, 根据异常类型来切换到另一个流 .onErrorResume(e -> generateTextFlux().filter(s -> !Strings.isNullOrEmpty(s))) .map(s -> Integer.valueOf(s)) .reduce((x, y) -> x + y)) .expectNext(12) .verifyComplete(); }
@Test public void testOnErrorMap() { // onErrorMap捕获异常后, 将其包装为某一个业务相关的异常再对外抛出 StepVerifier.create(generateTextFlux() .map(s -> Integer.valueOf(s)) .reduce((x, y) -> x + y) .onErrorMap(throwable -> new BusinessException(ResultCode.SERVICE_ERROR.code(), throwable.getMessage()))) .expectError(BusinessException.class) .verify(); }
@Test public void testDoOnError() { // doOnError捕获异常后, 可以对异常信息进行处理后(如记录错误日志), 再继续抛出该异常 StepVerifier.create(generateTextFlux() .map(s -> Integer.valueOf(s)) .reduce((x, y) -> x + y) .doOnError(e -> log.info(String.format("errMsg-1: %s", e.getMessage())))) .expectError(NumberFormatException.class) .verify(); // 也可以继续调用其他错误处理操作符来做进一步处理 StepVerifier.create(generateTextFlux() .map(s -> Integer.valueOf(s)) .reduce((x, y) -> x + y) .doOnError(e -> log.info(String.format("errMsg-2: %s", e.getMessage()))) .onErrorReturn(0)) .expectNext(0) .verifyComplete(); }
@Test public void testOnErrorRetry() { /** * retry用于对出现错误的流进行重试, 而实际上是对上游进行重新订阅 * 所以, 每次重试的流都不是同一序列, 有可能流中的元素都不相同(除非Publisher保证幂等性) */ generateTextFlux() .map(s -> Integer.valueOf(s)) .reduce((x, y) -> x + y) .doOnError(e -> log.info(String.format("errMsg: %s", e.getMessage()))) .onErrorMap(throwable -> new BusinessException(ResultCode.SERVICE_ERROR.code(), throwable.getMessage())) .retry(2, throwable -> throwable instanceof BusinessException) .subscribe(System.out::println, throwable -> { // do something... log.error(throwable.getClass().getSimpleName(), throwable); }); }
2.6 线程调度器(Schedulers) 在Reactor中,对于多线程并发调度的处理变得非常简单,它的线程调度器(Schedulers)类似于JDK的ExecutorService,不同的调度器定义不同的线程执行环境。
Schedulers提供了一组静态方法来指定线程执行环境,常用的主要有3种:single,elastic,parallel,默认使用Reactor预置的线程池,加上new前缀的话则创建新的线程池。
Reactor
JDK
Schedulers.newSingle()
Executors.newSingleThreadExecutor()
Schedulers.newElastic()
Executors.newCachedThreadPool()
Schedulers.newParallel()
Executors.newFixedThreadPool()
Reactor的线程模型:
基础业务/公共服务中心 > 2018/10/04 > Reactor入门实战 > schedulers.png
@Test public void testCurrentThread() { // 单个流的顺序操作默认在当前线程执行 Flux.range(1, 10) // log()及其变体方法用于打印reactor的执行日志, 会自动读取本地logger配置, 注意默认的日志级别为info .log() .blockLast(); }
@Test public void testDefaultScheduling() { // 通过延迟发送元素来触发Schedulers自动进行线程调度(根据操作符内置的Scheduler) Flux.range(1, 10) .delayElements(Duration.ofMillis(10)) .log() .blockLast(); }
@Test public void testAssignScheduling() throws InterruptedException { // 通过publishOn和subscribeOn来手动指定Scheduler(ps: subscribeOn和publishOn都支持work-stealing) Flux.range(1, 10) // publishOn方法会将链中其后的操作符调度到给定Scheduler的Worker上执行, 对应的方法调用就是onNext、onError、onComplete .publishOn(Schedulers.parallel()) .log() .publishOn(Schedulers.newParallel("myParallel")) .filter(i -> i % 2 == 0) .log() // subscribeOn无论出现在什么位置, 都只影响源头的执行环境, 在本例中就是range, 对应的方法调用就是onSubscribe、request // 因为Reactor是自下而上的订阅链, 通过subscribe()方法将线程执行环境传递到"源头", 直至遇到publishOn改变了执行环境才切换 .subscribeOn(Schedulers.elastic()) .log() .subscribe(i -> System.out.println(String.format("[%s] i = %d", Thread.currentThread().getName(), i)));
TimeUnit.MILLISECONDS.sleep(100);
}
@Test public void testParallelFlux() throws InterruptedException { /** * 上一个例子手动指定了Scheduler, 但通过观察日志输出可知, 每一个publishOn之后的任务仍然是在同一个线程上顺序执行 * 本例通过使用Reactor提供的ParallelFlux机制, 来实现让任务分布到不同的线程上并行执行 */ Flux.range(1, 10) // 不带参数的话线程数默认等于CPU核数 .parallel() // .parallel(2) .runOn(Schedulers.parallel()) .log() .subscribe(i -> System.out.println(String.format("[%s] i = %d", Thread.currentThread().getName(), i)));
TimeUnit.MILLISECONDS.sleep(100);
}
/**
- 下面这个例子演示了如何用Reactor来实现多个数据流的fork-join操作 */ @Test public void testForkJoin() { // 通过zip将所有流的元素串联起来 // ps: 如果所有流返回的数据类型一致, 也可以用merge将其串联起来, 但不能用concat, 后者是顺序执行, 不满足fork-join List
> list = Flux.zip( // 每个Publisher都必须单独调用subscribeOn来指定源头的执行线程, 以实现fork的效果 getIntFlux().subscribeOn(Schedulers.elastic()), getFloatFlux().subscribeOn(Schedulers.elastic()), getStringFlux().subscribeOn(Schedulers.elastic())) // 通过block来实现join的效果 .collectList().block(); // join后的处理逻辑, 这里只将结果简单的打印出来 System.out.println(list); }
private Flux
private Flux
private Flux
2.7 回压机制(Backpressure) 当Publisher产生数据的速度过快时,会使得Subscriber的处理速度无法跟上数据生产的速度,从而给订阅者造成很大的压力。当压力过大时,有可能造成Subscriber自身的崩溃,所产生的级联效应甚至可能导致整个系统的瘫痪。
回压的作用在于提供一种从Subscriber到Publisher的反馈渠道,Subscriber可以通过调用request()方法来声明其一次所能处理的元素个数,而Publisher就只会产生指定数量的元素,直到下一次 request()方法调用,这实际上就变成了推拉结合的模式。
ps: 思考一下与java.util.concurrent.RejectedExecutionHandler的异同?
当通过request声明每次从上游请求的元素个数后,自然会引发一个问题:如果Subscriber仍然处理不过来,Publisher堆积的未发送元素将会越来越多,如果不加以处理,最终将导致Publisher不可用。
对此,Reactor提供了以下几种回压策略:
ERROR:当下游跟不上节奏的时候发出一个错误信号
DROP:当下游没有准备好接收新元素的时候抛弃这个元素
LATEST:当下游跟不上节奏时只向其发送最新的元素,之前的元素都将被丢弃
BUFFER:缓存下游来不及处理的元素(显然,如果缓存不限大小的话可能导致OOM)
@Test public void testRequest() throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(1); Flux.range(1, 10) // 打印每次请求的元素个数 .doOnRequest(n -> System.out.println("Request elements: " + n)) // 通过设置prefetch参数来控制每次从上游请求的元素个数: request(n) .publishOn(Schedulers.newSingle("mySingle"), 2) .subscribeOn(Schedulers.single()) .subscribe(System.out::println, System.err::println, () -> countDownLatch.countDown()); countDownLatch.await(); }
/**
-
后面的例子为了演示不同的回压策略, 这里需要先模拟一个反应迟缓的订阅者 */ class SlowSubscriber extends BaseSubscriber
{ private final AtomicInteger count = new AtomicInteger();
private final CountDownLatch countDownLatch;
public SlowSubscriber(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; }
@Override protected void hookOnSubscribe(Subscription subscription) { // hookOnSubscribe将在订阅时触发 System.out.println("Subscribed..."); // 订阅的首次只向上游请求一个元素 request(1); }
@Override protected void hookOnNext(Integer value) { // hookOnNext将在每收到一个元素时触发 try { // 制造一个DEBUFF让它昏睡1秒 TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Get value [" + value + "]"); count.incrementAndGet(); // 处理完成后再向上游请求一个元素 request(1); }
@Override protected void hookOnComplete() { System.out.println("Completed! count = " + count.get()); countDownLatch.countDown(); } }
@Test public void testBackpressure() throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(1); // range的回压策略默认是BUFFER Flux.range(1, 10) // 发布者与订阅者必须运行在不同的线程环境才需要进行回压处理, 因为在同一线程运行的话, 就是阻塞了, 不存在回压 .publishOn(Schedulers.newSingle("mySingle")) .doOnRequest(n -> System.out.println("Request elements: " + n)) .subscribeOn(Schedulers.single()) .subscribe(new SlowSubscriber(countDownLatch)); countDownLatch.await(); }
@Test public void testBackpressureBuffer() throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(1); final int count = 10; Flux.range(1, count) .publishOn(Schedulers.newSingle("mySingle")) // 如果将maxSize调为小于count-1, 则会抛出OverflowException .onBackpressureBuffer(count - 1) .doOnRequest(n -> System.out.println("Request elements: " + n)) .subscribeOn(Schedulers.single()) .subscribe(new SlowSubscriber(countDownLatch)); countDownLatch.await(); }
@Test public void testBackpressureDrop() throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(1); Flux.range(1, 10) .publishOn(Schedulers.newSingle("mySingle")) .onBackpressureDrop() .doOnRequest(n -> System.out.println("Request elements: " + n)) .subscribeOn(Schedulers.single()) .subscribe(new SlowSubscriber(countDownLatch)); countDownLatch.await(); }
@Test public void testBackpressureLatest() throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(1); Flux.range(1, 10) .publishOn(Schedulers.newSingle("mySingle")) .onBackpressureLatest() .doOnRequest(n -> System.out.println("Request elements: " + n)) .subscribeOn(Schedulers.single()) .subscribe(new SlowSubscriber(countDownLatch)); countDownLatch.await(); }
@Test public void testBackpressureError() throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(1); Flux.range(1, 10) .publishOn(Schedulers.newSingle("mySingle")) .onBackpressureError() .doOnRequest(n -> System.out.println("Request elements: " + n)) .doOnError(e -> log.info(String.format("error: %s", e))) .onErrorReturn(-1) .subscribeOn(Schedulers.single()) .subscribe(new SlowSubscriber(countDownLatch)); countDownLatch.await(); }
- Reactor在项目中的实际应用 在刚开发完的一个项目中,使用了Reactor来实现发布-订阅模型,实现比较简单,算是一次试水,这里不详细解释具体业务逻辑,仅就如何使用Reactor做下简单介绍。
该系统类似一个网关服务,负责桥接内部系统与外部系统的接口相互调用,以屏蔽内部系统与外部系统之间接口调用的复杂性,使双方的对接尽可能的关注于自身业务逻辑,而不关心彼此之间是如何进行“交谈”的。
其中,有异步调用的场景,内部系统可以通过REST接口/MQ请求网关服务,由网关服务异步调用外部系统后,再通知内部系统本次调用结果,其处理逻辑如下图所示:
基础业务/公共服务中心 > 2018/10/04 > Reactor入门实战 > 异步调用_LI.jpg
仅演示与Reactor相关的代码,部分敏感词已作替换处理:
@Override public void asyncInvoke#hidden#(Invoke#hidden#Param param) { createAsyncInvokeFlux(param) .publishOn(Schedulers.elastic()) .log() .doOnError(e -> log.error("调用#hidden#接口出错! {} [err-msg] {}", param.toSimpleString(), e.getMessage())) // 当发生非业务类异常时才进行重试 .retry(3, throwable -> !(throwable instanceof BusinessException)) .subscribeOn(Schedulers.parallel()) .log() .subscribe(e -> { // 回调业务方接口通知其执行成功 doCallback(param, true); }, throwable -> { // 先写入失败记录, 再回调业务方接口通知其执行失败 log.error("调用#hidden#接口失败, 将写入失败记录! {}", param.toSimpleString(), throwable); insertFailedRecord(param, AsyncFailedEvent.EXEC, throwable); doCallback(param, false); }); }
private void doCallback(Invoke#hidden#Param param, boolean succ) { if (Strings.isNullOrEmpty(param.getCallbackURL())) { return; } // 不设置Scheduler让其运行在当前线程 createAsyncCallbackFlux(param, succ) .log() .doOnError(e -> log.error("回调业务方接口出错! {} [err-msg] {}", param.toSimpleString(), e.getMessage())) // httpRpcClient将错误统一包装成BusinessException, 故仅当捕获该异常时才重试 .retry(2, throwable -> throwable instanceof BusinessException) .subscribe(e -> log.info("回调业务方接口成功. {}", param.toSimpleString()), throwable -> { log.error("回调业务方接口失败, 将写入失败记录! {}", param.toSimpleString(), throwable); insertFailedRecord(param, AsyncFailedEvent.CALLBACK, throwable); }); }
private Flux
private Flux
private void insertFailedRecord(Invoke#hidden#Param param, AsyncFailedEvent event, Throwable throwable) { AsyncFailedRecord record = new AsyncFailedRecord(); try { record.setBizCode(param.getBizCode()); record.setBizAction(param.getBizAction()); record.setRequestId(param.getRequestId()); record.setCallback(param.getCallbackURL()); record.setEvent(event.getValue()); record.setCreateTime(new Date()); // 记录原始入参数据, 当做自动/手动补偿时需经wrapper处理 record.setData(param.getData()); AsyncFailedRecordService.assmbleErrorMsg(record, throwable); asyncFailedRecordService.insert(record); } catch (Throwable t) { String errMsg = String.format("Insert AsyncFailedRecord Error! entity: %s", record.toString()); log.error(errMsg, t); alertService.postAlert(ERR_ALERT_KEY, errMsg, t); } }
#. 参考/引用资料 响应式编程与响应式系统 - linux.cn
Reactive Streams Specification for the JVM - github
Reactor 3 Reference Guide
Reactor 3 Reference Guide(中文版)
响应式Spring的道法术器 (Spring WebFlux 快速上手 + 全面介绍)- 51cto