Ke *_*Vin 12 java performance spring asynchronous completable-future
我想要实现的是,通过以这种简单的方式使用多线程,在我的 RESTApi 控制器中使用 @Async 和 CompletableFuture 可以获得更好的性能吗?
这是我所做的,这是我的控制器:
@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;
}
Run Code Online (Sandbox Code Playgroud)
VS
@PostMapping("/store")
public ResponseEntity<ResponseRequest<CategoryBpsjResponseDto>> saveNewCategoryBPSJ(@Valid @RequestBody InputRequest<CategoryBPSJRequestDto> request) {
return ResponseEntity.ok(new ResponseRequest<>("okay", categoryBPSJService.save(request));
}
Run Code Online (Sandbox Code Playgroud)
正如你在我的第一个控制器函数中看到的,我在我的函数响应中添加了 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;
}
Run Code Online (Sandbox Code Playgroud)
我只是通过 JPA 连接返回简单的对象,这样我的请求性能会提高吗?或者我错过了什么来增加它?或者我的控制器上有没有 CompletableFuture 和 @Async 没有区别?
*注意:我的项目基于 java 13
sp0*_*00m 18
使用 CompletableFuture 不会神奇地提高服务器的性能。
如果您使用的是Spring MVC,通常是在 Jetty 或 Tomcat 之上的 Servlet API 上构建的,那么每个请求将有一个线程。这些线程来自的池通常非常大,因此您可以拥有相当数量的并发请求。在这里,阻塞请求线程不是问题,因为无论如何该线程都只处理该单个请求,这意味着不会阻塞其他请求(除非池中不再有可用线程)。这意味着,您的 IO可以是阻塞的,您的代码可以是同步的。
如果您使用Spring WebFlux,通常在 Netty 之上,请求将作为消息/事件处理:一个线程可以处理多个请求,这允许减少池的大小(线程很昂贵)。在这种情况下,线程阻塞是一个问题,因为它可能/将导致其他请求等待 IO 完成。这意味着,您的 IO必须是非阻塞的,您的代码必须是异步的,以便可以释放线程并“同时”处理另一个请求,而不仅仅是等待操作完成。仅供参考,这个反应式堆栈看起来很吸引人,但由于代码库的异步性质,它还有许多其他需要注意的缺点。
JPA 是阻塞的,因为它依赖于 JDBC(在 IO 上阻塞)。这意味着,在 Spring WebFlux 中使用 JPA 没有多大意义,应该避免,因为它违背了“不阻塞请求线程”的原则。人们已经找到了解决方法(例如,从另一个线程池中运行 SQL 查询),但这并没有真正解决潜在的问题:IO 将阻塞,争用可能/将会发生。人们正在研究 Java 的异步 SQL 驱动程序(例如Spring Data R2DBC和底层特定于供应商的驱动程序),例如可以在 WebFlux 代码库中使用。Oracle 也开始研究他们自己的异步驱动程序 ADBA, (这可能很快会完全改变 Java 中处理并发的方式)。
您似乎在使用 Spring MVC,这意味着依赖于每个请求的线程模型。只是在你的代码中删除 CompletableFuture 不会改善事情。假设您将所有服务层逻辑委托给另一个线程池而不是默认请求线程池:您的请求线程将可用,是的,但是现在争用将发生在您的另一个线程池上,这意味着您将只是在移动您的问题大约。
某些情况下推迟到另一个池可能仍然很有趣,例如计算密集型操作(如密码哈希),或某些会触发大量(阻塞)IO 的操作等,但请注意争用仍然可能发生,这意味着请求仍然可以被阻止/等待。
如果您确实观察到代码库的性能问题,请先对其进行分析。使用像YourKit这样的工具(还有很多其他可用的)甚至是像NewRelic这样的APM(还有很多其他可用的)。了解瓶颈在哪里,解决最坏的问题,然后重复。话虽如此,一些常见的怀疑:IO 太多(尤其是使用 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;
Run Code Online (Sandbox Code Playgroud)
这不会使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;
Run Code Online (Sandbox Code Playgroud)
看看这里发生了什么?categoryBPSJ
将被同步调用,然后您将创建一个已经完成的未来保存结果。如果您真的想在这里使用 CompletableFuture,则必须使用供应商:
CompletableFuture<CategoryBpsjResponseDto> future = CompletableFuture.supplyAsync(
() -> categoryBPSJService.save(request),
someExecutor
);
return future.thenApply(categoryBPSJ -> ResponseEntity.ok(new ResponseRequest<>("Okay", categoryBPSJ)));
Run Code Online (Sandbox Code Playgroud)
Spring 的@Async
基本上只是上面的语法糖,使用要么/或。出于技术 AOP/代理的原因,使用注解的方法@Async
确实需要返回 CompletableFuture,在这种情况下,返回已经完成的未来是可以的:无论如何,Spring 都会让它在执行程序中运行。服务层通常是“异步”的,但控制器只是消耗和组合返回的未来:
CompletableFuture<CategoryBpsjResponseDto> = categoryBPSJService.save(request);
return future.thenApply(categoryBPSJ -> ResponseEntity.ok(new ResponseRequest<>("Okay", categoryBPSJ)));
Run Code Online (Sandbox Code Playgroud)
通过调试代码确保它的行为符合您的预期,IDE 会显示当前哪个线程被断点阻塞。
旁注:这是我对阻塞与非阻塞、MVC 与 WebFlux、同步与异步等的理解的简化总结。这是非常肤浅的,我的一些观点可能不够具体,无法 100% 正确。