线程模型
调度器:Scheduler是一个拥有多个实现类的抽象接口。Schedulers类(工具类)提供的静态方法可搭建以下几种线程执行环境:
| Schedulers 方法 | 描述 |
|---|---|
| .immediate() | 在当前线程中执行订阅 |
| .single() | 在单个可重用线程中执行订阅,对所有调用方重复使用同一线程 |
| .newSingle() | 在每个调用专用线程中执行订阅 |
| .elastic() | 在从无限弹性池中提取的工作进程中执行订阅,根据需要创建新的工作线程,并释放空闲的工作线程(默认情况下 60 秒) |
| .parallel() | 在从固定大小的池中提取的工作进程中执行订阅,该池的大小取决于 CPU 核心的数量。 |
调整调度器的方法:publishOn和subscribeOn
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
引入依赖:
org.springframework.boot spring-boot-starter-webflux默认服务器:netty
@RestController
public class HelloController {
@GetMapping("/hello")
public Mono<String> hello(){
return Mono.just("hello StringWebFlux!!");
}
}