为何现在响应式编程在业务开发微服务开发不普及
主要因为数据库 IO,不是 NIO。
不论是Java自带的Future框架,还是 Spring WebFlux,还是 Vert.x,他们都是一种非阻塞的基于Ractor模型的框架(后两个框架都是利用netty实现)。
在阻塞编程模式里,任何一个请求,都需要一个线程去处理,如果io阻塞了,那么这个线程也会阻塞在那。但是在非阻塞编程里面,基于响应式的编程,线程不会被阻塞,还可以处理其他请求。举一个简单例子:假设只有一个线程池,请求来的时候,线程池处理,需要读取数据库 IO,这个 IO 是 NIO 非阻塞 IO,那么就将请求数据写入数据库连接,直接返回。之后数据库返回数据,这个链接的 Selector 会有 Read 事件准备就绪,这时候,再通过这个线程池去读取数据处理(相当于回调),这时候用的线程和之前不一定是同一个线程。这样的话,线程就不用等待数据库返回,而是直接处理其他请求。这样情况下,即使某个业务 SQL 的执行时间长,也不会影响其他业务的执行。
但是,这一切的基础,是 IO 必须是非阻塞 IO,也就是 NIO(或者 AIO)。官方JDBC没有 NIO,只有 BIO 实现。这样无法让线程将请求写入链接之后直接返回,必须等待响应。但是也就解决方案,就是通过其他线程池,专门处理数据库请求并等待返回进行回调,也就是业务线程池 A 将数据库 BIO 请求交给线程池B处理,读取完数据之后,再交给 A 执行剩下的业务逻辑。这样A也不用阻塞,可以处理其他请求。但是,这样还是有因为某个业务 SQL 的执行时间长,导致B所有线程被阻塞住队列也满了从而A的请求也被阻塞的情况,这是不完美的实现。真正完美的,需要 JDBC 实现 NIO。
Java 自带的 Future 框架可以这么用JDBC:
@GetMapping
public DeferredResult<Result> get() {
DeferredResult<Result> deferredResult = new DeferredResult<>();
CompletableFuture.supplyAsync(() -> {
return 阻塞数据库IO;
//dbThreadPool用来处理阻塞的数据库IO
}, dbThreadPool).thenComposeAsync(result -> {
//spring 的 DeferredResult 来实现异步回调写入结果返回
deferredResult.setResult(result);
});
return deferredResult;
}
WebFlux 也可以使用阻塞JDBC,但是同理:
@GetMapping
public Mono<Result> get() {
return Mono.fromFuture(CompletableFuture.supplyAsync(() -> {
return 阻塞数据库IO;
//dbThreadPool用来处理阻塞的数据库IO
}, dbThreadPool));
}
Vert.x 也可以使用阻塞的JDBC,也是同理:
@GetMapping
public DeferredResult<Result> get() {
DeferredResult<Result> deferredResult = new DeferredResult<>();
getResultFromDB().setHandler(asyncResult -> {
if (asyncResult.succeeded()) {
deferredResult.setResult(asyncResult.result());
} else {
deferredResult.setErrorResult(asyncResult.cause());
}
});
return deferredResult;
}
private WorkerExecutor dbThreadPool = vertx.createSharedWorkerExecutor("DB", 16);
private Future<Result> getResultFromDB() {
Future<Result> result = Future.future();
dbThreadPool.executeBlocking(future -> {
return 阻塞数据库IO;
}, false, asyncResult -> {
if (asyncResult.succeeded()) {
result.complete(asyncResult.result());
} else {
result.fail(asyncResult.cause());
}
});
return result;
}
相当于通过另外的线程池(当然也可以通过原有线程池,反正就是要用和请求不一样的线程,才能实现回调,而不是当次就阻塞等待),封装了阻塞 JDBC IO。
但是,这样几乎对数据库IO主导的应用性能没有提升,还增加了线程切换,得不偿失。所以,需要使用真正实现了 NIO 的数据库客户端。目前有这些 NIO 的 JDBC 客户端,但是都不普及:
- Vert.x 客户端:https://vertx.io/docs/vertx-jdbc-client/java/
- r2jdbc 客户端:http://r2dbc.io/
- Jasync-sql 客户端:https://github.com/jasync-sql/jasync-sql
每日一刷,轻松提升技术,斩获各种offer: