服务器端的异步和同步HTTP请求,性能比较

时间:2019-04-26 07:52:05

标签: java multithreading http asynchronous jmeter

我试图弄清楚异步和同步HTTP请求处理的优缺点。我正在将Dropwizard和Jersey用作我的框架。 测试是比较异步和同步HTTP请求处理,这是我的代码

@Path("/")
public class RootResource {

    ExecutorService executor;

    public RootResource(int threadPoolSize){
        executor = Executors.newFixedThreadPool(threadPoolSize);
    }

    @GET
    @Path("/sync")
    public String sayHello() throws InterruptedException {
        TimeUnit.SECONDS.sleep(1L);
        return "ok";
    }

    @GET
    @Path("/async")
    public void sayHelloAsync(@Suspended final AsyncResponse asyncResponse) throws Exception {
        executor.submit(() -> {
            try {
                doSomeBusiness();
                asyncResponse.resume("ok");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }


    private void doSomeBusiness() throws InterruptedException {
        TimeUnit.SECONDS.sleep(1L);
    }

}

sync API将在Jetty维护的工作线程中运行,而 async API将主要在海关线程池中运行。这是Jmeter的结果

  • 测试1,500个Jetty工作线程,/ sync端点

    enter image description here

  • 测试2,500个自定义线程,/ async端点

    enter image description here 结果表明,两种方法之间没有太大差异。

我的问题是:两种方法之间有什么区别?在哪种情况下应该使用哪种模式?

相关主题:Performance difference between Synchronous HTTP Handler and Asynchronous HTTP Handler

更新


我按照建议的10个延迟进行了测试

  • sync-500-server-thread

sync-500-server-thread

  • async-500-workerthread

async-500-workerthread

5 个答案:

答案 0 :(得分:8)

以下是我的想法。

无论是同步请求还是异步请求,都与HTTP的性能无关,而与应用程序的性能有关

同步请求将阻塞应用程序,直到它收到响应为止,而基本上,在异步请求中,您将在一个单独的工作线程中分配此工作,该工作线程将负责其余的工作。因此,在异步设计中,您的主线程仍然可以继续自己的工作。

比方说,由于某些限制(不是服务器的资源限制),您的服务器只能处理有限数量的连接(基本上,每个连接将在单独的线程中处理,这在我们使用的服务器之间有所不同)。如果您的服务器可以处理的线程数多于连接数,并且如果您不想由于创建的异步工作而返回任何数据,则可以设计异步逻辑。因为您将创建一个新线程来处理请求的任务。

但是,如果您希望在响应中返回操作结果,则不会有任何变化。

答案 1 :(得分:5)

您将@Suspended与异步结合使用,而异步仍在等待响应

  

@Suspended将暂停/暂停当前线程,直到得到响应为止

如果要在异步中获得更好的性能,请使用ExecutorServiceFutureimmediate response编写另一种异步方法

private ExecutorService executor;
private Future<String> futureResult;
@PostConstruct
public void onCreate() {
    this.executor = Executors.newSingleThreadExecutor();
}
@POST
public Response startTask() {
    futureResult = executor.submit(new ExpensiveTask());
    return Response.status(Status.ACCEPTED).build();
}
@GET
public Response getResult() throws ExecutionException, InterruptedException {
    if (futureResult != null && futureResult.isDone()) {
        return Response.status(Status.OK).entity(futureResult.get()).build();
    } else {
        return Response.status(Status.FORBIDDEN).entity("Try later").build();
    }
}

答案 2 :(得分:3)

让我们考虑以下情况:

Single Backend system
                    ____________
                   |  System A  |
 HTTP Request -->  |            |
                   |  1.        |
                   |  2.        |
 HTTP Response <-- |            |
                   |____________|

您有一个后端系统,该系统根据在特定顺序上接收到的请求进行一些处理(操作1,然后是操作2)。如果您以同步或异步方式处理请求并不重要,则需要完成的计算量是相同的(也许有些微小的变化,例如您在测试中遇到的变化)。

现在,让我们考虑一个多后端方案:

Multi-Backend System
                        ____________
                       |  System A  |       __________
     HTTP Request -->  |            | -->  |          |
                       |  1.        |      | System B |
                       |            | <--  |__________|
                       |            |       __________  
                       |  2.        | -->  |          |
     HTTP Response <-- |            |      | System C |
                       |____________| <--  |__________|

仍然需要执行2个处理步骤,但是这次,我们将在每个步骤上调用另一个后端系统。

SYNC 处理:

  1. 呼叫系统B
  2. 等待系统B的回复
  3. 呼叫系统C
  4. 等待系统C的回复

花费的总时间:B + C

ASYNC 处理:

  1. 呼叫系统B
  2. 前进,因为通话没有阻塞
  3. 呼叫系统C
  4. 前进,因为通话没有阻塞
  5. 从系统B接收响应
  6. 从系统C接收响应
  7. 完成对客户的呼叫

花费的总时间:max(B,C)

为什么最大?由于所有呼叫均处于非阻塞状态,因此您仅需等待最慢的后端即可回复。

答案 3 :(得分:0)

我是根据我个人的经验编写此程序的,他为斯里兰卡的前端出租车海陵服务使用异步处理程序编写了一个Promotions引擎,该引擎甚至与Uber竞争。

在使用异步处理程序而不是同步处理程序时,它比性能更具有可伸缩性,可用性和资源利用率。

如果使用同步处理程序,则最大并发请求数由可用于接受新连接的线程数定义,此后您的服务将根本无法接受请求。

就像使用异步处理程序一样,accept线程数与您可以满足的并发请求无关。因此,您的服务可以从100 rps扩展到100万rps,并具有高可用性。

如果您担心延迟和吞吐量,则可以将非阻塞API与异步处理程序一起使用,从而获得很好的改进。非阻塞套接字(NIO),非阻塞存储(Mongo DB反应式,Redis反应式),消息队列(Kafka,RabbitMQ)等

答案 4 :(得分:0)

异步编程的主要性能优势是您可以减少系统上的线程数量。 documentation of Jetty 看起来是一个很好的参考。

<块引用>

servlet API(2.5 之前的版本)仅支持同步调用样式,因此 servlet 需要执行的任何等待都必须带有阻塞。不幸的是,这意味着分配给请求的线程必须在等待期间与其所有资源一起保留:内核线程、堆栈内存和经常池化的缓冲区、字符转换器、EE 身份验证上下文等。 保留这些是浪费系统资源等待中的资源。如果等待是异步完成的,则可以显着提高可扩展性和服务质量。

问题的代码没有利用这个优势,因为它仍然为每个请求阻塞一个线程,它只是一个与自定义线程池不同的线程:

@GET
@Path("/async")
public void sayHelloAsync(@Suspended final AsyncResponse asyncResponse) throws Exception {
    executor.submit(() -> {
        try {
            doSomeBusiness();
            asyncResponse.resume("ok");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
}


private void doSomeBusiness() throws InterruptedException {
    TimeUnit.SECONDS.sleep(1L);
}

请注意,这种实现尤其有问题,因为除了阻塞线程之外,线程数量还受到线程池的严格限制。

如果您想利用异步请求处理的优势,您必须重新实现 TimeUnit.SECONDS.sleep(1L),使其不会阻塞任何线程。

相关问题