响应式编程 - 入门介绍
概念
与传统编程模型对比
传统编程模型,主要特点是同步阻塞式-Blocking; 而响应式编程(Reactive Programming) 主要特点是异步非阻塞 Non-Blocking。
同步阻塞式参考下面的图:
同步阻塞的方式很好理解,也容易实现,在大部分时候都是不错的选择。但它的问题是当并发量高的时候,会有资源的浪费,遇到性能瓶颈。而我们也希望能够最大程度地利用硬件资源,利用多核CPU提高服务的一个吞吐量,所以有了异步非阻塞的编程模式。
响应式数据流异步非阻塞参考下图:
无论是查询数据库还是Web端过来的请求,都被看做一个一个的事件,而这些事件组成了一个事件流。对于这些事件,我们注册相关的订阅者去响应它,从而从编程的角度上实现异步。
这其中涉及到几个关键字:响应式数据流, 事件驱动, 发布订阅模式。
Java异步非阻塞实现
Java提供了两种方式来实现异步编程:
- Callback 使用回调,例如一个lambda或者一个匿名类作为参数,当异步执行有结果后进行调用
- Future/CompletableFuture 立刻返回一个
Future<T>
对象。
但是这两种方式都有各自的缺陷。
😟 Callback的问题
很难组合,容易形成嵌套,甚至导致“回调地狱”,会导致代码的可读性和可维护性都较差。
🌰 举个栗子, 我们需要通过userId获取该用户最喜欢的5个UI,如果该用户没有设置喜欢的UI,就返回建议给他。这里有三个服务,getFavorites()
,getSuggestions()
,getDetails()
分别获取用户喜欢的UI的id,获取给用户的建议,获取UI的具体信息。
userService.getFavorites(userId, new Callback<List<String>>() {
public void onSuccess(List<String> list) {
if (list.isEmpty()) {
suggestionService.getSuggestions(new Callback<List<Favorite>>() {
public void onSuccess(List<Favorite> list) {
UiUtils.submitOnUiThread(() -> {
list.stream()
.limit(5)
.forEach(uiList::show);
});
}
public void onError(Throwable error) {
UiUtils.errorPopup(error);
}
});
} else {
list.stream()
.limit(5)
.forEach(favId -> favoriteService.getDetails(favId,
new Callback<Favorite>() {
public void onSuccess(Favorite details) {
UiUtils.submitOnUiThread(() -> uiList.show(details));
}
public void onError(Throwable error) {
UiUtils.errorPopup(error);
}
}
));
}
}
public void onError(Throwable error) {
UiUtils.errorPopup(error);
}
});
而响应式编程,以project reactor框架为例,只需要以下代码实现:
userService.getFavorites(userId)
.flatMap(favoriteService::getDetails)
.switchIfEmpty(suggestionService.getSuggestions())
.take(5)
.publishOn(UiUtils.uiThreadScheduler())
.subscribe(uiList::show, UiUtils::errorPopup);
😟 Future的问题
Future要比Callback好一些,即使Java 8引入了CompletableFuture,这些API仍然存在一些问题:
- 如果使用了
get()
方法,仍然会让你的实现最后沦为阻塞式 - 不支持lazy computation
- 对多个值处理和异常处理等支持较少
🌰 举个栗子
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");
应用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"
);
Project Reactor
如今有很多优秀的响应式编程框架,如RxJava, Project Reactor, Vert.x, Akka, Spring的WebFlux等。上面举例子的时候也用了Project Reactor来做说明。
关于Project Reactor, 有两个基本的组件需要了解,一个是Mono,一个是Flux
Mono
Mono可以理解为发射一个事件的发布平台。有很多静态工厂方法可以创建一个Mono,如Mono.just("foo")
Mono参考文档
🌰举个栗子,假如我需要在一个水果loaderCache更新方法里,实现异步,延迟更新里面的水果图片对象,可以这样做:
private FruitLoader updateFruitLoader(Integer key) {
//...
Mono.delay(Duration.ofSeconds(1)).subscribeOn(scheduler).subscribe(t -> {
FruitLoader loader = fruitLoaderCache.get(key);
if (loader != null) {
Optional.ofNullable(loader.loadFruit()).ifPresent(fruitType -> {
fruitPictureCache.refresh(fruitType);
});
}
});
//...
}
Flux
Flux可以理解为可以发射N个事件的发布平台,同样有很多静态工厂方法可以构建:Flux参考文档
//todo
参考资料:
https://time.geekbang.org/dailylesson/detail/100075754
https://time.geekbang.org/dailylesson/detail/100056946
https://tech.io/playgrounds/929/reactive-programming-with-reactor-3/Flux
https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Flux.html
https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Mono.html
https://www.cnblogs.com/littleatp/p/11515458.html
https://blog.csdn.net/get_set/article/details/79576874
https://projectreactor.io/docs/core/release/reference/#getting-started
https://www.cnblogs.com/cjsblog/p/12580518.html
https://juejin.cn/post/6844904193946451981