gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-03 10:03:57 26 4
gpt4 key购买 nike

我想要实现的是,通过以这种简单的方式使用多线程,在我的 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 函数中看到的,我在我的函数响应中添加了 CompletableFuture,但在我的服务中,我确实保存在这一行 categoryBPSJService.save(request)不是异步的,只是一个看起来像这样的简单函数:
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 连接返回简单的对象,这样我的请求性能会提高吗?或者我错过了什么来增加它?或者我的 Controller 上有没有 CompletableFuture 和 @Async 没有区别?
*注意:我的项目基于 java 13

最佳答案

使用 CompletableFuture 不会神奇地提高服务器的性能。
如果您使用的是 Spring MVC ,通常基于 Servlet API 构建在 Jetty 或 Tomcat 之上,每个请求将有一个线程。这些线程来自的池通常非常大,因此您可以拥有相当数量的并发请求。在这里,阻塞请求线程不是问题,因为无论如何该线程只处理该单个请求,这意味着不会阻塞其他请求(除非池中不再有可用线程)。这意味着,您的 IO 可以是阻塞的,您的代码可以是同步的。
如果您使用的是 Spring WebFlux但是,通常在 Netty 之上,请求被处理为消息/事件:一个线程可以处理多个请求,这允许减少池的大小(线程很昂贵)。在这种情况下,线程阻塞是一个问题,因为它可能/将导致其他请求等待 IO 完成。这意味着,您的 IO 必须是非阻塞的,您的代码必须是异步的,以便可以释放线程并“同时”处理另一个请求,而不仅仅是等待操作完成。仅供引用,这个响应式(Reactive)堆栈看起来很吸引人,但由于代码库的异步性质,它还有许多其他缺点需要注意。
JPA 是阻塞的,因为它依赖于 JDBC(在 IO 上阻塞)。这意味着,在 Spring WebFlux 中使用 JPA 没有多大意义,应该避免,因为它违背了“不阻塞请求线程”的原则。人们已经找到了解决方法(例如,从另一个线程池中运行 SQL 查询),但这并没有真正解决潜在的问题:IO 将阻塞,争用可能/将会发生。人们正在研究 Java 的异步 SQL 驱动程序(例如 Spring Data R2DBC 和底层供应商特定的驱动程序),例如可以在 WebFlux 代码库中使用。 Oracle 也开始开发他们自己的异步驱动程序 ADBA,但是 they abandoned the project因为他们通过 Project Loom 专注于光纤(这可能很快会完全改变 Java 中处理并发的方式)。
您似乎在使用 Spring MVC,这意味着依赖于每个请求的线程模型。只是在你的代码中删除 CompletableFuture 不会改善事情。假设您将所有服务层逻辑委托(delegate)给另一个线程池而不是默认请求线程池:您的请求线程将可用,是的,但是现在将在您的另一个线程池上发生争用,这意味着您将只是在移动您的问题周围。
某些情况下推迟到另一个池可能仍然很有趣,例如计算密集型操作(如密码哈希),或某些会触发大量(阻塞)IO 的操作等,但请注意争用仍然可能发生,这意味着请求仍可能被阻塞/等待。
如果您确实观察到代码库的性能问题,先介绍一下 .使用类似 YourKit 的工具(许多其他可用)甚至 APM,如 NewRelic (还有许多其他可用的)。了解瓶颈在哪里,解决最坏的问题,然后重复。话虽如此,一些常见的怀疑:IO 太多(尤其是使用 JPA,例如 select n+1 ),太多的序列化/反序列化(尤其是使用 JPA,例如 eager fetching )。基本上,JPA 是常见的嫌疑人:它是一个强大的工具,但很容易错误配置,您需要考虑 SQL 以使其正确恕我直言。我强烈推荐 logging the generated SQL queries在开发时,您可能会感到惊讶。 Vlad Mihalcea's blog是 JPA 相关事物的好资源。也很有趣:OrmHate by Martin Fowler .

关于您的特定代码片段,假设您将使用没有 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将被同步调用,然后您将创建一个已经完成的 future 保存结果。如果您真的想在这里使用 CompletableFuture,则必须使用供应商:
CompletableFuture<CategoryBpsjResponseDto> future = CompletableFuture.supplyAsync(
() -> categoryBPSJService.save(request),
someExecutor
);
return future.thenApply(categoryBPSJ -> ResponseEntity.ok(new ResponseRequest<>("Okay", categoryBPSJ)));
Spring's @Async 基本上只是上面的语法糖,使用要么/或。出于技术 AOP/代理的原因,使用 @Async 注释的方法确实需要返回一个 CompletableFuture,在这种情况下返回一个已经完成的 future 是可以的:无论如何,Spring 都会让它在执行程序中运行。服务层通常是“异步”的,但 Controller 只是消耗和组合返回的 future :
CompletableFuture<CategoryBpsjResponseDto> = categoryBPSJService.save(request);
return future.thenApply(categoryBPSJ -> ResponseEntity.ok(new ResponseRequest<>("Okay", categoryBPSJ)));
通过调试代码确保它的行为符合您的预期,IDE 会显示当前哪个线程被断点阻塞。

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

关于java - 在 Controller 中使用 @Async 和 CompletableFuture 可以提高我们 api 的性能吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65120202/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com