异步和同步模式中的并发
concurrency in asynchronous and synchronous pattern
我们有一个基于 servlet 的应用程序,它同步处理请求,每个请求将花费几乎 4000ms
,因为它必须对远程数据库执行大量 sql 查询并执行很多计算工作。
我们使用ab
来测试应用,并发和吞吐量比较小。
在我看来,在传统的 servlet 模型中,请求是同步服务的:为每个请求创建一个线程,这个线程将等待直到处理完成,这意味着在我的例子中这个 servlet 线程将等待 4000ms
,在挂起期间,它不能做任何事情。一种资源浪费。
一段时间以来,我对vertx
感兴趣,所以我根据vertx
编写了应用程序。我知道 vertx
中的 event loop
模型无法被阻止。所以块作业(需要 4000ms
在工作线程中执行,如下所示:
router.route().blockingHandler(context -> {
List result=new ArrayList();
String[] layers = getLayers(context);
final int[] len=new int[]{layers.length};
layers.forEach(l_>{
context.vertx().executeBlocking(f->{
List d = doDataseJob(l);
d = doCalculationJob(d)
f.complete(d);
},false,r->{
len[0]--;
result.addAll(r.result())
if(len[0]==0){
//all blocking jobs have done
//return
context.response().end(.......);
}
});
});
});
但是再次通过ab
测试并发后,我们发现与基于servlet的应用程序相比只有一点改进。
据我所知,单个请求的响应时间在异步模式和同步模式之间不会有太大变化,但是 throughput
和 concurrency
应该在异步模式(基于 vertx 的应用程序)中得到改进,因为刚刚转发请求的 event loop
线程可以处理比以前更多的请求。
我错过了什么吗?还是我用错了vertx
?
更新 1:完成繁重的工作 return Future
:
@Override
public Future doHeavyJob(String layer) {
Future future = Future.future();
new Thread(()->{
List d = doDataseJob(tile, layer, future);
d = doCalculationJob(d);
future.complete(d)
}).start();
return future;
}
正如我在另一个主题中提到的,您使用 Vert.x 的方式有误。
没有并发增益,因为您所做的正是您的 servlet 之前所做的:将一个非常长的作业放在线程池上。
使用 EventLoop 将工作放在这个 pull 上的事实不会改变任何东西。它甚至可能会使情况变得更糟,因为 Vert.x 工作线程池默认情况下非常小,只有 20 个线程。如果您的 servlet 容器配置了更多线程,那么在该设置中它的性能实际上会优于 Vert.x。
你应该怎么做:
- 如果有独立的查询,并行执行它们,使用 Futures 组合它们的结果
- 将您的 DAO 封装在 Verticle 中,并使用 EventBus 进行通信
请注意,如果您有长达 4 秒的查询,一旦您解决了这些问题,您的数据库就会成为瓶颈。
我们有一个基于 servlet 的应用程序,它同步处理请求,每个请求将花费几乎 4000ms
,因为它必须对远程数据库执行大量 sql 查询并执行很多计算工作。
我们使用ab
来测试应用,并发和吞吐量比较小。
在我看来,在传统的 servlet 模型中,请求是同步服务的:为每个请求创建一个线程,这个线程将等待直到处理完成,这意味着在我的例子中这个 servlet 线程将等待 4000ms
,在挂起期间,它不能做任何事情。一种资源浪费。
一段时间以来,我对vertx
感兴趣,所以我根据vertx
编写了应用程序。我知道 vertx
中的 event loop
模型无法被阻止。所以块作业(需要 4000ms
在工作线程中执行,如下所示:
router.route().blockingHandler(context -> {
List result=new ArrayList();
String[] layers = getLayers(context);
final int[] len=new int[]{layers.length};
layers.forEach(l_>{
context.vertx().executeBlocking(f->{
List d = doDataseJob(l);
d = doCalculationJob(d)
f.complete(d);
},false,r->{
len[0]--;
result.addAll(r.result())
if(len[0]==0){
//all blocking jobs have done
//return
context.response().end(.......);
}
});
});
});
但是再次通过ab
测试并发后,我们发现与基于servlet的应用程序相比只有一点改进。
据我所知,单个请求的响应时间在异步模式和同步模式之间不会有太大变化,但是 throughput
和 concurrency
应该在异步模式(基于 vertx 的应用程序)中得到改进,因为刚刚转发请求的 event loop
线程可以处理比以前更多的请求。
我错过了什么吗?还是我用错了vertx
?
更新 1:完成繁重的工作 return Future
:
@Override
public Future doHeavyJob(String layer) {
Future future = Future.future();
new Thread(()->{
List d = doDataseJob(tile, layer, future);
d = doCalculationJob(d);
future.complete(d)
}).start();
return future;
}
正如我在另一个主题中提到的,您使用 Vert.x 的方式有误。
没有并发增益,因为您所做的正是您的 servlet 之前所做的:将一个非常长的作业放在线程池上。
使用 EventLoop 将工作放在这个 pull 上的事实不会改变任何东西。它甚至可能会使情况变得更糟,因为 Vert.x 工作线程池默认情况下非常小,只有 20 个线程。如果您的 servlet 容器配置了更多线程,那么在该设置中它的性能实际上会优于 Vert.x。
你应该怎么做:
- 如果有独立的查询,并行执行它们,使用 Futures 组合它们的结果
- 将您的 DAO 封装在 Verticle 中,并使用 EventBus 进行通信
请注意,如果您有长达 4 秒的查询,一旦您解决了这些问题,您的数据库就会成为瓶颈。