异步和同步模式中的并发

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的应用程序相比只有一点改进。

据我所知,单个请求的响应时间在异步模式和同步模式之间不会有太大变化,但是 throughputconcurrency 应该在异步模式(基于 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 秒的查询,一旦您解决了这些问题,您的数据库就会成为瓶颈。