繁体   English   中英

在 controller 中使用 @Async 和 CompletableFuture 可以提高我们 api 的性能吗?

[英]is using @Async and CompletableFuture in controller can increase performance of our api?

我想要实现的是,通过以这种简单的方式使用多线程,在我的 RESTApi 的 controller 中使用 @Async 和 CompletableFuture 可以获得更好的性能吗?

这是我所做的,这是我的 controller:

@PostMapping("/store")
@Async
public CompletableFuture<ResponseEntity<ResponseRequest<CategoryBpsjResponseDto>>> saveNewCategoryBPSJ(@Valid @RequestBody InputRequest<CategoryBPSJRequestDto> request) {
    
    CompletableFuture<ResponseEntity<ResponseRequest<CategoryBpsjResponseDto>>> future = new CompletableFuture<>();

    future.complete(ResponseEntity.ok(new ResponseRequest<>("Okay", categoryBPSJService.save(request))));
    return future;
}

VS

@PostMapping("/store")
public ResponseEntity<ResponseRequest<CategoryBpsjResponseDto>> saveNewCategoryBPSJ(@Valid @RequestBody InputRequest<CategoryBPSJRequestDto> request) {
    
    return ResponseEntity.ok(new ResponseRequest<>("okay", categoryBPSJService.save(request));
}

正如你在我的第一个 controller function 中看到的那样,我在我的 function 响应中添加了 CompletableFuture,但在我的服务中,我确实保存在这一行categoryBPSJService.save(request)不是异步的,只是一个简单的 function,看起来像这样:

public CategoryBpsjResponseDto save(InputRequest<CategoryBPSJRequestDto> request) {
    CategoryBPSJRequestDto categoryBPSJDto = request.getObject();

    Boolean result = categoryBPSJRepository.existsCategoryBPSJBycategoryBPSJName(categoryBPSJDto.getCategoryBPSJName());

    if(result){
        throw new ResourceAlreadyExistException("Category BPSJ "+ categoryBPSJDto.getCategoryBPSJName() + " already exists!");
    }

    CategoryBPSJ categoryBPSJ = new CategoryBPSJ();
    categoryBPSJ = map.DTOEntity(categoryBPSJDto);

    categoryBPSJ.setId(0L);
    categoryBPSJ.setIsDeleted(false);

    CategoryBPSJ newCategoryBPSJ = categoryBPSJRepository.save(categoryBPSJ);
    
    CategoryBpsjResponseDto categoryBpsjResponseDto = map.entityToDto(newCategoryBPSJ);

    return categoryBpsjResponseDto;

}

我只是通过 JPA 连接返回简单的 Object,这样我的请求性能会提高吗? 还是我错过了一些东西来增加它? 或者在我的 controller 上使用或不使用 CompletableFuture 和 @Async 都没有区别吗?

*注意:我的项目是基于 java 13

使用 CompletableFuture 不会神奇地提高服务器的性能。

如果您使用的是Spring MVC ,通常在 Jetty 或 Tomcat 之上的 Servlet API 上构建,则每个请求将有一个线程。 从中获取这些线程的池通常非常大,因此您可以有相当数量的并发请求。 在这里,阻塞一个请求线程不是问题,因为这个线程无论如何都只处理那个请求,这意味着其他请求不会被阻塞(除非池中不再有可用线程)。 这意味着,您的 IOs可以是阻塞的,您的代码可以是同步的。

但是,如果您使用的是Spring WebFlux ,通常在 Netty 之上,请求将作为消息/事件处理:一个线程可以处理多个请求,这样可以减少池的大小(线程很昂贵)。 在这种情况下,阻塞线程一个问题,因为它可能/将导致其他请求等待 IO 完成。 这意味着,您的 IOs必须是非阻塞的,您的代码必须是异步的,以便可以释放线程并“同时”处理另一个请求,而不是仅仅等待空闲等待操作完成。 仅供参考,这个反应式堆栈看起来很吸引人,但由于代码库的异步性质,它还有许多其他需要注意的缺点。

JPA 是阻塞的,因为它依赖于 JDBC(在 IO 上阻塞)。 这意味着,使用 JPA 和 Spring WebFlux 没有多大意义,应该避免,因为它违背了“不阻塞请求线程”的原则。 人们找到了解决方法(例如,从另一个线程池中运行 SQL 查询),但这并没有真正解决根本问题:IOs 将阻塞,竞争可能/将会发生。 人们正在为 Java 开发异步 SQL 驱动程序(例如Spring Data R2DBC和底层供应商特定驱动程序),例如可以在 WebFlux 代码库中使用这些驱动程序。 Oracle 也开始研究他们自己的异步驱动程序 ADBA,但他们放弃了该项目,因为他们通过Project Loom专注于程(这可能很快就会完全改变 Java 中处理并发的方式)。

您似乎正在使用 Spring MVC,这意味着依赖于 thread-per-request model。仅在代码中删除 CompletableFuture 不会改善情况。 假设您将所有服务层逻辑委托给另一个线程池而不是默认请求线程池:您的请求线程将可用,是的,但是现在将在您的另一个线程池上发生争用,这意味着您只是在转移您的问题大约。

有些情况可能仍然很有趣,可以推迟到另一个池,例如计算密集型操作(如密码哈希),或某些会触发大量(阻塞)IOs 等的操作,但请注意竞争仍然可能发生,这意味着请求仍然可以被阻止/等待。

如果您确实发现代码库存在性能问题,请先对其进行概要分析 使用像YourKit (许多其他可用的)这样的工具,甚至像NewRelic (还有许多其他可用的)这样的 APM。 了解瓶颈在哪里,解决最坏的问题,重复。 话虽这么说,一些常见的嫌疑人:

  • IOs 太多(尤其是 JPA,例如select n+1 ),
  • 太多的序列化/反序列化(尤其是 JPA,例如eager fetching )。

基本上,JPA通常的嫌疑人:它是一个强大的工具,但很容易配置错误,您需要考虑 SQL才能正确使用恕我直言。 我强烈建议在开发时记录生成的 SQL 查询,您可能会感到惊讶。 Vlad Mihalcea 的博客是 JPA 相关内容的良好资源。 阅读也很有趣: Martin Fowler 的 OrmHate


关于您的特定代码片段,假设您要在没有 Spring 的@Async支持的情况下使用 vanilla Java:

CompletableFuture<ResponseEntity<ResponseRequest<CategoryBpsjResponseDto>>> future = new CompletableFuture<>();
future.complete(ResponseEntity.ok(new ResponseRequest<>("Okay", categoryBPSJService.save(request))));
return future;

这不会使categoryBPSJService.save(request)异步运行。 如果您稍微拆分代码,它会变得更加明显:

CategoryBpsjResponseDto categoryBPSJ = categoryBPSJService.save(request)
CompletableFuture<ResponseEntity<ResponseRequest<CategoryBpsjResponseDto>>> future = new CompletableFuture<>();
future.complete(ResponseEntity.ok(new ResponseRequest<>("Okay", categoryBPSJ)));
return future;

看看这里发生了什么? categoryBPSJ将被同步调用,然后您将创建一个已完成的未来来保存结果。 如果你真的想在这里使用 CompletableFuture,你必须使用供应商:

CompletableFuture<CategoryBpsjResponseDto> future = CompletableFuture.supplyAsync(
    () -> categoryBPSJService.save(request),
    someExecutor
);
return future.thenApply(categoryBPSJ -> ResponseEntity.ok(new ResponseRequest<>("Okay", categoryBPSJ)));

Spring 的@Async基本上只是上面的语法糖,使用 either/or。 出于技术 AOP/代理原因,使用@Async注释的方法确实需要返回一个 CompletableFuture,在这种情况下返回一个已经完成的未来是好的:Spring 无论如何都会让它在执行程序中运行。 服务层通常是“异步”层,controller 只是在返回的未来消费和组合:

CompletableFuture<CategoryBpsjResponseDto> = categoryBPSJService.save(request);
return future.thenApply(categoryBPSJ -> ResponseEntity.ok(new ResponseRequest<>("Okay", categoryBPSJ)));

通过调试代码确保它的所有行为都符合您的预期,IDE 会显示当前哪个线程被断点阻止。


旁注:这是我对阻塞与非阻塞、MVC 与 WebFlux、同步与异步等的理解的简单总结。这很肤浅,我的一些观点可能不够具体,无法 100% 正确。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM