响应式编程:线程模型、错误处理、SpringWebFlux|青训营

40 阅读3分钟

线程模型

调度器:Scheduler是一个拥有多个实现类的抽象接口。Schedulers类(工具类)提供的静态方法可搭建以下几种线程执行环境:

Schedulers 方法描述
.immediate()在当前线程中执行订阅
.single()在单个可重用线程中执行订阅,对所有调用方重复使用同一线程
.newSingle()在每个调用专用线程中执行订阅
.elastic()在从无限弹性池中提取的工作进程中执行订阅,根据需要创建新的工作线程,并释放空闲的工作线程(默认情况下 60 秒)
.parallel()在从固定大小的池中提取的工作进程中执行订阅,该池的大小取决于 CPU 核心的数量。

调整调度器的方法:publishOnsubscribeOn

  • publishOn会影响链中其后的操作符,比如第一个publishOn调整调度器为elastic,则filter的处理操作是在弹性线程池中执行的;同理,flatMap是执行在固定大小的parallel线程池中的;
  • subscribeOn无论出现在什么位置,都只影响源头的执行环境,也就是range方法是执行在单线程中的,直至被第一个publishOn切换调度器之前,所以range后的map也在单线程中执行。
Flux.range(1, 1000)
.map(…)
.publishOn(Schedulers.elastic()).filter(…)
.publishOn(Schedulers.parallel()).flatMap(…)
.subscribeOn(Schedulers.single())

错误处理

subscribe方法的第二个参数定义了对错误信号的处理

  • onErrorReturn:在收到错误信号的时候提供一个默认值
Flux.range(1, 6)
    .map(i -> 10/(i-3)) //-5,-10
    .onErrorReturn(0)   //当i为3时出错
    .map(i -> i*i) //25,100
    .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))) 
        //将错误e映射为一个随机int值,[0,6):提供新的数据流
    .map(i -> i*i)  //25,100,16(随机)
    .subscribe(System.out::println, System.err::println);
Flux.just(endpoint1, endpoint2)
    .flatMap(k -> callExternalService(k))   // 调用外部服务
    .onErrorResume(e -> getFromCache(k));   // 如果外部服务异常,则从缓存中取值代替
  • onErrorContinue:出现错误跳过错误,使用原数据继续执行

在这里插入图片描述

@Test
public void onErrorContinue () {
    Flux.interval(Duration.ofMillis(100))
        .map(i -> {
                if (i == 2) throw new RuntimeException("fake a mistake");
                return String.valueOf(100/(i-5));
            })
            // 遇到error之后跳过,可以通过不同错误类型做不同处理
        .onErrorContinue((err, val) -> log
                    .error("处理第{}个元素时遇到错误,错误类型为:{}, 错误信息为: {}", val, err.getClass(), err.getMessage()))
        .subscribe(log::info);
​
    try{
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
  • onErrorMap:替换错误内容

在这里插入图片描述

@Test
public void onErrorMap () {
    Flux.interval(Duration.ofMillis(100))
            .map(i -> {
                if (i == 2) throw new RuntimeException("fake a mistake");
                return String.valueOf(100/(i-5));
            })
            // 当发生错误时更换错误内容
            .onErrorMap(e -> new RuntimeException("change error type"))
            .subscribe(log::info);
    try{
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
  • 捕获并再包装为某一个业务相关的异常,然后再抛出业务异常。
  • 捕获,记录错误日志,然后继续抛出。
  • 使用 finally 来清理资源,或使用 Java 7 引入的 “try-with-resource”。
  • 重试:retry
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:

回压

自定义具有流量控制能力的订阅者订阅快发布者:

// doOnRequest:每次收到请求时打印请求个数

// 订阅时执行 hookOnSubscribe

 request(1); // 在订阅时向上游(发布者)请求一个元素

@Test
public void testBackpressure() {
    Flux.range(1, 6)    //快publisher
         .doOnRequest(n -> System.out.println("Request " + n + " values...")) 
         .subscribe(new BaseSubscriber<Integer>() {  
                @Override
                protected void hookOnSubscribe(Subscription subscription) { 
                   System.out.println("Subscribed and make a request...");
                    request(1); // 在订阅时向上游(发布者)请求一个元素
                }
​
                @Override
                protected void hookOnNext(Integer value) {  //每次收到一个元素时执行
                    try {
                        TimeUnit.SECONDS.sleep(1);  // 模拟慢订阅者
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Get value [" + value + "]"); //打印收到元素
                    request(1);  //每次处理完一个元素再申请一个元素
                }
            });
}

Spring WebFlux

img

引入依赖:

   org.springframework.boot    spring-boot-starter-webflux

默认服务器:netty

@RestController
public class HelloController {
    @GetMapping("/hello")
    public Mono<String> hello(){
        return Mono.just("hello StringWebFlux!!");
    }
}