易筋SpringBoot 2.1 | 第三十一篇:SpringBoot Reactor响应式编程实战一

写作时间:2019-11-30
Spring Boot: 2.2.1 ,JDK: 1.8, IDE: IntelliJ IDEA

1. 说明

此篇从代码层面对响应式Reactor的实战,包括源代码的解析。

2. 核心概念

先看看Reactor的主要角色图:在这里插入图片描述
Operators - Publisher(生成者) / Subscriber(订阅者) 通过push的方式交互

  1. Nothing Happens Until You subscribe(), 必须调用subscribe()方法才会执行。
  2. Flux [ 0..N ] 执行序列 - onNext(), onComplete(), onError()
  3. Mono [ 0..1 ] - onNext(), onComplete(), onError()

Backpressure 反压力:表示上游的生产者可以生产多个产品,订阅者可以根据自身处理能力慢条斯理按个执行。
4. Subscription订阅
5. onRequest()请求, onCancel()取消, onDispose()处理

线程调度Schedulers
6. 单个线程:
immediate() 在当前的线程数执行 /
可复用的线程single() / 独占,
新起的线程newSingle()
7. 线程池:
缓存的线程池elastic(),线程 60秒后被回收/
parallel()可复用的线程池,会创建跟CPU核数匹配的线程,线程不会被回收 /
newParallel()可以独占,新起线程池

错误处理
8. onError相当于try{} catch{} /
onErrorReturn 遇到异常的时候返回默认值 /
onErrorResume 用一段Lambda闭包处理异常逻辑
9. doOnError 异常处理 / doFinally 表示正常、异常都会处理的逻辑。

3. 工程建立

参照教程【SpringBoot 2.1 | 第一篇:构建第一个SpringBoot工程】新建一个Spring Boot项目,名字叫RPCClient, 在目录src/main/java/resources 下找到配置文件application.properties,重命名为application.yml

Spring Boot 版本选择2.2.1,依赖勾选Developer Tools > Lombok.
在这里插入图片描述

pom.xml添加reactor的依赖

<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-core</artifactId>
</dependency>

4. 在主线程调用

package com.zgpeace.reactor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

@SpringBootApplication
@Slf4j
public class ReactorApplication implements ApplicationRunner {
  public static void main(String[] args) {
    SpringApplication.run(ReactorApplication.class, args);
  }

  @Override
  public void run(ApplicationArguments args) throws Exception {
    fluxInMainThread();
    return;
  }

  private void fluxInMainThread() throws InterruptedException {
    Flux.range(1, 6)
        .doOnRequest(n -> log.info("Request {} number", n)) // 注意顺序造成的区别
        .doOnComplete(() -> log.info("Publisher Complete 1"))
        //.publishOn(Schedulers.elastic())
        .map( i -> {
              log.info("Publish {}, {}", Thread.currentThread(), i);
              //return 10 / (i - 3);
              return i;
            }
        )
        .doOnComplete( () -> log.info("Publisher Complete 2"))
        //.subscribeOn(Schedulers.single())
        //.onErrorResume(e -> {
        //  log.error("Exception {}", e.toString());
        //  return Mono.just(-1);
        //})
        //.onErrorReturn(-1)
        .subscribe( i -> log.info("Subscribe {}: {}", Thread.currentThread(), i),
            e -> log.error("error {}", e.toString()),
            () -> log.info("Subscriber Complete")
            //s -> s.request(4)
        );

    Thread.sleep(2000);
  }

}

运行结果

[           main] com.zgpeace.reactor.ReactorApplication   : Request 9223372036854775807 number
[           main] com.zgpeace.reactor.ReactorApplication   : Publish Thread[main,5,main], 1
[           main] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[main,5,main]: 1
[           main] com.zgpeace.reactor.ReactorApplication   : Publish Thread[main,5,main], 2
[           main] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[main,5,main]: 2
[           main] com.zgpeace.reactor.ReactorApplication   : Publish Thread[main,5,main], 3
[           main] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[main,5,main]: 3
[           main] com.zgpeace.reactor.ReactorApplication   : Publish Thread[main,5,main], 4
[           main] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[main,5,main]: 4
[           main] com.zgpeace.reactor.ReactorApplication   : Publish Thread[main,5,main], 5
[           main] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[main,5,main]: 5
[           main] com.zgpeace.reactor.ReactorApplication   : Publish Thread[main,5,main], 6
[           main] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[main,5,main]: 6
[           main] com.zgpeace.reactor.ReactorApplication   : Publisher Complete 1
[           main] com.zgpeace.reactor.ReactorApplication   : Publisher Complete 2
[           main] com.zgpeace.reactor.ReactorApplication   : Subscriber Complete

代码解析:

  1. Request 9223372036854775807 number 表示请求数据为long的最大值,表示不管生产多少,订阅者都要消费掉
  2. 发现Publish生产者和Subscribe消费者都在主线程执行
  3. 执行结束以后Publisher执行Complete逻辑(可以有多个),Subscriber执行Complete逻辑

5. 生产者、消费者在不同的线程池

生产者在线程池Schedulers.elastic()执行
消费者在复用线程Schedulers.single()执行

@Override
public void run(ApplicationArguments args) throws Exception {
  //fluxInMainThread();
  fluxInDifferentThread();

  return;
}

private void fluxInDifferentThread() throws InterruptedException {
  Flux.range(1, 6)
      .doOnRequest(n -> log.info("Request {} number", n)) // 注意顺序造成的区别
      .doOnComplete(() -> log.info("Publisher Complete 1"))
      .publishOn(Schedulers.elastic())
      .map( i -> {
            log.info("Publish {}, {}", Thread.currentThread(), i);
            //return 10 / (i - 3);
            return i;
          }
      )
      .doOnComplete( () -> log.info("Publisher Complete 2"))
      .subscribeOn(Schedulers.single())
      //.onErrorResume(e -> {
      //  log.error("Exception {}", e.toString());
      //  return Mono.just(-1);
      //})
      //.onErrorReturn(-1)
      .subscribe( i -> log.info("Subscribe {}: {}", Thread.currentThread(), i),
          e -> log.error("error {}", e.toString()),
          () -> log.info("Subscriber Complete")
          //s -> s.request(4)
      );

  Thread.sleep(2000);
}

运行结果:

[       single-1] com.zgpeace.reactor.ReactorApplication   : Request 256 number
[       single-1] com.zgpeace.reactor.ReactorApplication   : Publisher Complete 1
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 1
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: 1
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 2
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: 2
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 3
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: 3
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 4
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: 4
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 5
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: 5
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 6
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: 6
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publisher Complete 2
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscriber Complete

解析:

  1. [ single-1] Request 256 number 在single申请请求数为256(以前是在主线程申请)
  2. [ elastic-2]后面的生产者消费者都从线程池elastic-2中获取线程。

6. 异常处理返回默认值

代码实现

@Override
public void run(ApplicationArguments args) throws Exception {
  //fluxInMainThread();
  //fluxInDifferentThread();
  fluxOnErrorReturn();

  return;
}

private void fluxOnErrorReturn() throws InterruptedException {
  Flux.range(1, 6)
      .doOnRequest(n -> log.info("Request {} number", n)) // 注意顺序造成的区别
      .doOnComplete(() -> log.info("Publisher Complete 1"))
      .publishOn(Schedulers.elastic())
      .map( i -> {
            log.info("Publish {}, {}", Thread.currentThread(), i);
            return 10 / (i - 3);
            //return i;
          }
      )
      .doOnComplete( () -> log.info("Publisher Complete 2"))
      .subscribeOn(Schedulers.single())
      //.onErrorResume(e -> {
      //  log.error("Exception {}", e.toString());
      //  return Mono.just(-1);
      //})
      .onErrorReturn(-1)
      .subscribe( i -> log.info("Subscribe {}: {}", Thread.currentThread(), i),
          e -> log.error("error {}", e.toString()),
          () -> log.info("Subscriber Complete")
          //s -> s.request(4)
      );

  Thread.sleep(2000);
}

解析:
当有异常发生时候,返回-1, .onErrorReturn(-1)

运行结果:

[       single-1] com.zgpeace.reactor.ReactorApplication   : Request 256 number
[       single-1] com.zgpeace.reactor.ReactorApplication   : Publisher Complete 1
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 1
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: -5
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 2
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: -10
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 3
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: -1
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscriber Complete

解析:

  1. 当执行到3的时候,除数为0,发生异常,返回-1.
  2. 因为是异常退出,所以没有执行完成,Publisher Complete 2 没有执行。
  3. 流程已经结束,订阅者执行完成Subscriber Complete

7. 异常发生处理闭包逻辑

@Override
public void run(ApplicationArguments args) throws Exception {
  //fluxInMainThread();
  //fluxInDifferentThread();
  //fluxOnErrorReturn();
  fluxOnErrorResume();

  return;
}

private void fluxOnErrorResume() throws InterruptedException {
  Flux.range(1, 6)
      .doOnRequest(n -> log.info("Request {} number", n)) // 注意顺序造成的区别
      .doOnComplete(() -> log.info("Publisher Complete 1"))
      .publishOn(Schedulers.elastic())
      .map( i -> {
            log.info("Publish {}, {}", Thread.currentThread(), i);
            return 10 / (i - 3);
            //return i;
          }
      )
      .doOnComplete( () -> log.info("Publisher Complete 2"))
      .subscribeOn(Schedulers.single())
      .onErrorResume(e -> {
        log.error("Exception {}", e.toString());
        return Mono.just(-1);
      })
      //.onErrorReturn(-1)
      .subscribe( i -> log.info("Subscribe {}: {}", Thread.currentThread(), i),
          e -> log.error("error {}", e.toString()),
          () -> log.info("Subscriber Complete")
          //s -> s.request(4)
      );

  Thread.sleep(2000);
}

执行结果:

[       single-1] com.zgpeace.reactor.ReactorApplication   : Request 256 number
[       single-1] com.zgpeace.reactor.ReactorApplication   : Publisher Complete 1
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 1
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: -5
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 2
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: -10
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 3
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Exception java.lang.ArithmeticException: / by zero
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: -1
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscriber Complete

解析:
打印异常信息为除数为0, Exception java.lang.ArithmeticException: / by zero

8. 消费者只消费部分生产者产品

这里把消费者设置为4,而生产还是6。
并把.publishOn(Schedulers.elastic())挪到最前面执行。

@Override
public void run(ApplicationArguments args) throws Exception {
  //fluxInMainThread();
  //fluxInDifferentThread();
  //fluxOnErrorReturn();
  //fluxOnErrorResume();
  fluxWithSubscribe();

  return;
}

private void fluxWithSubscribe() throws InterruptedException {
  Flux.range(1, 6)
      .publishOn(Schedulers.elastic())
      .doOnRequest(n -> log.info("Request {} number", n)) // 注意顺序造成的区别
      .doOnComplete(() -> log.info("Publisher Complete 1"))
      .map( i -> {
            log.info("Publish {}, {}", Thread.currentThread(), i);
            //return 10 / (i - 3);
            return i;
          }
      )
      .doOnComplete( () -> log.info("Publisher Complete 2"))
      .subscribeOn(Schedulers.single())
      .onErrorResume(e -> {
        log.error("Exception {}", e.toString());
        return Mono.just(-1);
      })
      //.onErrorReturn(-1)
      .subscribe( i -> log.info("Subscribe {}: {}", Thread.currentThread(), i),
          e -> log.error("error {}", e.toString()),
          () -> log.info("Subscriber Complete"),
          s -> s.request(4)
      );

  Thread.sleep(2000);
}

运行结果

[       single-1] com.zgpeace.reactor.ReactorApplication   : Request 4 number
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 1
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: 1
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 2
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: 2
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 3
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: 3
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 4
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: 4

解析:

  1. Request 4 number 生产者只会请求4个线程。
  2. 这里Publisher Complete 1Publish Complete 2Subscriber Complete 都没有打印, 因为上面逻辑都在线程[elastic-2,5,main]完毕的时候处理,目前都没有结束。

9. 消费者只消费部分生产者产品(并调换线程位置)

public void run(ApplicationArguments args) throws Exception {
  //fluxInMainThread();
  //fluxInDifferentThread();
  //fluxOnErrorReturn();
  //fluxOnErrorResume();
  //fluxWithSubscribe();
  fluxWithSubscribeAndChangePublishOnThreadOrder();

  return;
}

private void fluxWithSubscribeAndChangePublishOnThreadOrder() throws InterruptedException {
  Flux.range(1, 6)
      .doOnRequest(n -> log.info("Request {} number", n)) // 注意顺序造成的区别
      .publishOn(Schedulers.elastic())
      .doOnComplete(() -> log.info("Publisher Complete 1"))
      .map( i -> {
            log.info("Publish {}, {}", Thread.currentThread(), i);
            //return 10 / (i - 3);
            return i;
          }
      )
      .doOnComplete( () -> log.info("Publisher Complete 2"))
      .subscribeOn(Schedulers.single())
      .onErrorResume(e -> {
        log.error("Exception {}", e.toString());
        return Mono.just(-1);
      })
      //.onErrorReturn(-1)
      .subscribe( i -> log.info("Subscribe {}: {}", Thread.currentThread(), i),
          e -> log.error("error {}", e.toString()),
          () -> log.info("Subscriber Complete"),
          s -> s.request(4)
      );

  Thread.sleep(2000);
}

结果打印:

[       single-1] com.zgpeace.reactor.ReactorApplication   : Request 256 number
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 1
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: 1
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 2
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: 2
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 3
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: 3
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 4
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: 4

解析:
Request 256 number 订阅者线程池被生产者线程池覆盖掉了,所以显示还是256.

10. 消费者只消费部分产品,并且发生异常

运行结果

[       single-1] com.zgpeace.reactor.ReactorApplication   : Request 256 number
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 1
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: -5
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 2
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: -10
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 3
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Exception java.lang.ArithmeticException: / by zero
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: -1
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscriber Complete

解析:
打印Subscriber Complete, 因为异常导致订阅者提前结束。

11. 源码解析

还记得说Publisher在subscribe()调用之前啥都没有处理么?

上面的结果显示,线程先打印subscirbe()订阅者的线程[ single-1],后面才是生产者的线程[ elastic-2]

[       single-1] com.zgpeace.reactor.ReactorApplication   : Request 4 number
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Publish Thread[elastic-2,5,main], 1
[      elastic-2] com.zgpeace.reactor.ReactorApplication   : Subscribe Thread[elastic-2,5,main]: 1

.map的实现

public final <V> Flux<V> map(Function<? super T, ? extends V> mapper) {
  if (this instanceof Fuseable) {
	return onAssembly(new FluxMapFuseable<>(this, mapper));
  }
  return onAssembly(new FluxMap<>(this, mapper));
}

protected static <T> Flux<T> onAssembly(Flux<T> source) {
	Function<Publisher, Publisher> hook = Hooks.onEachOperatorHook;
	if(hook != null) {
		source = (Flux<T>) hook.apply(source);
	}
	if (Hooks.GLOBAL_TRACE) {
		AssemblySnapshot stacktrace = new AssemblySnapshot(null, Traces.callSiteSupplierFactory.get());
		source = (Flux<T>) Hooks.addAssemblyInfo(source, stacktrace);
	}
	return source;
}

解析:
Publisher的map仅仅是做了数据的封装,没有任何行动。

.subscribe()的调用

public final Disposable subscribe(@Nullable Consumer<? super T> consumer, 
@Nullable Consumer<? super Throwable> errorConsumer, 
@Nullable Runnable completeConsumer, 
@Nullable Consumer<? super Subscription> subscriptionConsumer) {
  return (Disposable)this.subscribeWith(new LambdaSubscriber(consumer, errorConsumer, completeConsumer, subscriptionConsumer, (Context)null));
}

LambdaSubscriber(@Nullable Consumer<? super T> consumer, @Nullable Consumer<? super Throwable> errorConsumer, @Nullable Runnable completeConsumer, @Nullable Consumer<? super Subscription> subscriptionConsumer, @Nullable Context initialContext) {
  this.consumer = consumer;
  this.errorConsumer = errorConsumer;
  this.completeConsumer = completeConsumer;
  this.subscriptionConsumer = subscriptionConsumer;
  this.initialContext = initialContext == null ? Context.empty() : initialContext;
}

public final <E extends Subscriber<? super T>> E subscribeWith(E subscriber) {
  this.subscribe(subscriber);
  return subscriber;
}

解析: 上面都是数据组装。
consumer : 过程处理消费者
errorConsumer: 错误处理消费者
completeConsumer:完成处理消费者
subscriptionConsumer: 订阅处理消费者
处理逻辑在this.subscribe(subscriber)

public final void subscribe(Subscriber<? super T> actual) {
  CorePublisher publisher = Operators.onLastAssembly(this);
  CoreSubscriber subscriber = Operators.toCoreSubscriber(actual);
  if (publisher instanceof OptimizableOperator) {
    OptimizableOperator operator = (OptimizableOperator)publisher;

    while(true) {
      subscriber = operator.subscribeOrReturn(subscriber);
      if (subscriber == null) {
        return;
      }

      OptimizableOperator newSource = operator.nextOptimizableSource();
      if (newSource == null) {
        publisher = operator.source();
        break;
      }

      operator = newSource;
    }
  }

  publisher.subscribe(subscriber);
}

代码解析:
调用publisher.subscribe(subscriber), 真正触发publisher的执行

继续点击publisher.subscribe(subscriber)为接口

void subscribe(CoreSubscriber<? super T> subscriber);

继续看一个实现接口的实例FluxRepeat.java
在这里插入图片描述
FluxRepeat.java 的实现

void resubscribe() {
	if (WIP.getAndIncrement(this) == 0) {
		do {
			if (isCancelled()) {
				return;
			}

			long c = produced;
			if (c != 0L) {
				produced = 0L;
				produced(c);
			}

			source.subscribe(this);

		} while (WIP.decrementAndGet(this) != 0);
	}
}

才会真正调用source.subscribe(this);

所以在没有subscribe()之前,Publisher的所有逻辑都没有发生。

12. 代码下载

https://github.com/zgpeace/Spring-Boot2.1/tree/master/reactor/ReactorSimple

13. 参考

https://github.com/geektime-geekbang/geektime-spring-family/tree/master/Chapter%205/simple-reactor-demo

发布了127 篇原创文章 · 获赞 12 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/zgpeace/article/details/103321024