Reactor简介
Reactor是一个支持背压(backpressure)的实现了Reactive Streams
规范的运行在JVM上的非阻塞反应式编程的类库。
为什么要使用Reactor
互联网下的高并发问题
在现代互联网的架构下,解决高并发问题的方式大致有两个方向
- 并行化(parallelize ),使用多线程提高硬件资源的利用率。
- 异步化,尽可能的提高现在资源的利用率。
并行化的解决方案
现在多线程似乎已经成为了解决并发问题的标准模式。通常我们使用阻塞代码来编写程序,当出现性能瓶颈的时候,我们可以引入额外的线程来运行阻塞的代码, 但是这种方式很快会带来争用和并发问题,并且被阻塞的线程资源处在空闲状态而被浪费,所以并行化并不是解决问题的灵丹妙药。
- 线程上下文的切换比较耗时(system call)。
- 线程本身就是一种比较宝贵的资源。
- 需要解决线程的同步问题。
异步化的解决方案
Java提供了两种异步化的API,Callback
和Future
。但是这两种技术都有他们的局限性。
- Callback很难组合在一起,代码的维护和阅读都比较困难。一旦陷入回调地狱(Callback Hell)代码将会变得无法维护。
- Java8中的
CompletableFuture
虽然对Future进行了增强,但是在事件编排能力依旧表现不佳。- future.get()依然是阻塞的。
- 不能支持惰性计算(lazy computation)。
- 对异常的高级处理支持比较弱。
回调地狱的例子
一个需求:
- 通过用户ID获取获取用户的收藏夹,并将前五个收藏夹的信息展示到用户的UI上,如果收藏夹为空,则展示5个建议。
// 1.通过userId获取收藏夹
userService.getFavorites(userId, new Callback<List<String>>() {
public void onSuccess(List<String> list) {
if (list.isEmpty()) { // 2.收藏夹为空获取建议
suggestionService.getSuggestions(new Callback<List<Favorite>>() {
public void onSuccess(List<Favorite> list) {
UiUtils.submitOnUiThread(() -> { // 3.使用UI线程处理
list.stream()
.limit(5)
.forEach(uiList::show);
});
}
public void onError(Throwable error) {
UiUtils.errorPopup(error);
}
});
} else {
list.stream()
.limit(5) // 4.通过收藏夹ID获取详情
.forEach(favId -> favoriteService.getDetails(favId,
new Callback<Favorite>() {
public void onSuccess(Favorite details) {// 5.UI线程处理
UiUtils.submitOnUiThread(() -> uiList.show(details));
}
public void onError(Throwable error) {
UiUtils.errorPopup(error);
}
}
));
}
}
public void onError(Throwable error) {
UiUtils.errorPopup(error);
}
});
使用Reactor
userService.getFavorites(userId)
.flatMap(favoriteService::getDetails)
.switchIfEmpty(suggestionService.getSuggestions())
.take(5)
.publishOn(UiUtils.uiThreadScheduler()) // 切换到ui线程
.subscribe(uiList::show, UiUtils::errorPopup);
如果获取收藏夹超时(800ms)则从缓存中获取,这在回调中实现起来是一个复杂的任务,但是在Reactor将会非常简单。
userService.getFavorites(userId)
.timeout(Duration.ofMillis(800)) // 增加超时处理
.onErrorResume(cacheService.cachedFavoritesFor(userId)) //从缓存中获取
.flatMap(favoriteService::getDetails)
.switchIfEmpty(suggestionService.getSuggestions())
.take(5)
.publishOn(UiUtils.uiThreadScheduler())
.subscribe(uiList::show, UiUtils::errorPopup);
CompletableFuture组合操作的例子
我们首先获取ID的列表,然后我们要通过ID获取一个名称(name)和一个统计信息(Stat),并将它们成对组合,所有这些都是异步的。
CompletableFuture<List<String>> ids = ifhIds();
CompletableFuture<List<String>> result = ids.thenComposeAsync(l -> {
Stream<CompletableFuture<String>> zip =
l.stream().map(i -> {
CompletableFuture<String> nameTask = ifhName(i);
CompletableFuture<Integer> statTask = ifhStat(i);
return nameTask.thenCombineAsync(statTask, (name, stat) -> "Name " + name + " has stats " + stat);
});
List<CompletableFuture<String>> combinationList = zip.collect(Collectors.toList());
CompletableFuture<String>[] combinationArray = combinationList.toArray(new CompletableFuture[combinationList.size()]);
CompletableFuture<Void> allDone = CompletableFuture.allOf(combinationArray);
return allDone.thenApply(v -> combinationList.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList()));
});
List<String> results = result.join();
assertThat(results).contains(
"Name NameJoe has stats 103",
"Name NameBart has stats 104",
"Name NameHenry has stats 105",
"Name NameNicole has stats 106",
"Name NameABSLAJNFOAJNFOANFANSF has stats 121");
可以看出CompletableFuture在组合上没有成熟的API,事件编排能也很有限,写起来代码依旧很复杂。
使用Reactor
Flux<String> ids = ifhrIds();
Flux<String> combinations =
ids.flatMap(id -> {
Mono<String> nameTask = ifhrName(id);
Mono<Integer> statTask = ifhrStat(id);
return nameTask.zipWith(statTask,
(name, stat) -> "Name " + name + " has stats " + stat);
});
Mono<List<String>> result = combinations.collectList();
List<String> results = result.block();
assertThat(results).containsExactly(
"Name NameJoe has stats 103",
"Name NameBart has stats 104",
"Name NameHenry has stats 105",
"Name NameNicole has stats 106",
"Name NameABSLAJNFOAJNFOANFANSF has stats 121"
);
可以看出Reactor编程可以通过异步化更好的利用当前CPU的资源,更强大的事件编排能力。
响应式流规范
必须具备的能力
响应式流规范规定了响应式流必须具备的能力
- 可以处理无限的元素。
- 有序的处理。
- 在组件之间异步地传递元素。
- 必须实现非阻塞的背压(backpressure)。
必须实现的API组件
- Publisher
public interface Publisher<T> { public void subscribe(Subscriber<? super T> s); }
- Subscriber
public interface Subscriber<T> { public void onSubscribe(Subscription s); public void onNext(T t); public void onError(Throwable t); public void onComplete(); }
- Subscription
public interface Subscription { public void request(long n); public void cancel(); }
- Processer
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> { }
核心协议
Publisher是一个潜在的无限的(unbounded)有序的(sequenced)元素提供者。Publisher根据从Subscriber接收到的需求发布元素。
订阅后的回调用表达式表示是:
onSubscribe onNext* (onError | onComplete)?
以一个onSubscribe开始,中间有0个或多个onNext,最后有0个或1个onError或onComplete事件。