java—在控制器中使用@async和completablefuture是否可以提高api的性能?

mcdcgff0  于 2021-07-07  发布在  Java
关注(0)|答案(1)|浏览(448)

我想实现的是,我能通过使用@async和completablefuture获得更好的性能吗?作为restapi控制器的结果,我能以这种简单的方式使用多线程吗?
这是我的工作,这是我的控制器:

@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;
}

@PostMapping("/store")
public ResponseEntity<ResponseRequest<CategoryBpsjResponseDto>> saveNewCategoryBPSJ(@Valid @RequestBody InputRequest<CategoryBPSJRequestDto> request) {

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

正如您在我的第一个控制器函数中所看到的,我在函数响应中添加了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连接的简单对象,用这种方法我的请求性能会提高吗?还是我错过了什么东西来增加它?或者在我的控制器上有或没有completablefuture和@async没有区别?

  • 注意:我的项目是基于Java13的
iswrvxsc

iswrvxsc1#

使用completablefuture并不能神奇地提高服务器的性能。
如果您使用的是springmvc,它构建在通常位于jetty或tomcat之上的servlet api之上,那么每个请求将有一个线程。这些线程来自的池通常相当大,因此可以有相当数量的并发请求。在这里,阻塞一个请求线程并不是一个问题,因为这个线程只处理一个请求,这意味着其他请求不会被阻塞(除非池中不再有可用的线程)。这意味着,你的ios可以是阻塞的,你的代码可以是同步的。
但是,如果您使用的是springwebflux,通常在netty之上,请求被作为消息/事件处理:一个线程可以处理多个请求,这允许减少池的大小(线程很昂贵)。在这种情况下,阻塞线程是一个问题,因为它可能/将导致其他请求等待io完成。这意味着,您的ios必须是非阻塞的,您的代码必须是异步的,这样线程就可以被释放并“同时”处理另一个请求,而不是仅仅等待操作完成。仅供参考,这个React式堆栈看起来很吸引人,但由于代码库的异步特性,它还有许多其他缺点需要注意。
jpa是阻塞的,因为它依赖于jdbc(jdbc在ios上阻塞)。这意味着,在springwebflux中使用jpa没有多大意义,应该避免,因为它违背了“不要阻塞请求线程”的原则。人们已经找到了解决方法(例如,从另一个线程池中运行sql查询),但这并不能真正解决根本问题:ios将阻塞,争用可能/将发生。人们正在为java开发异步sql驱动程序(例如springdatar2dbc和底层特定于供应商的驱动程序),例如,可以在webflux代码库中使用这些驱动程序。oracle也开始开发自己的异步驱动程序adba,但是他们放弃了这个项目,因为他们通过projectloom专注于光纤(这可能很快就会完全改变java处理并发的方式)。
您似乎在使用springmvc,这意味着依赖于每个请求的线程模型。仅仅在代码中删除completablefuture并不能改善事情。假设您将所有服务层逻辑委托给另一个线程池,而不是默认的请求线程池:您的请求线程将可用,是的,但是争用现在将发生在您的另一个线程池上,这意味着您将只是在转移您的问题。
有些情况下,推迟到另一个池可能仍然很有趣,例如计算密集型操作(如密码短语哈希)或某些会触发大量(阻止)ios的操作等,但请注意,争用仍然可能发生,这意味着请求仍然可能被阻止/等待。
如果您发现了代码库的性能问题,请首先分析它。使用诸如yourkit(许多其他可用工具)或甚至newrelic(许多其他可用工具)之类的apms。了解瓶颈在哪里,解决问题,重复。也就是说,一些常见的疑点是:ios太多(特别是jpa,例如select n+1),序列化/反序列化太多(特别是jpa,例如eager fetching)。基本上,jpa是一个常见的疑点:它是一个强大的工具,但很容易被误解,您需要考虑sql来正确使用它。我强烈建议在开发时记录生成的sql查询,您可能会感到惊讶。vladmihalcea的博客是jpa相关内容的一个很好的资源。有趣的阅读:奥姆哈特由马丁福勒。
关于您的特定代码片段,假设您使用的是香草java而不是spring @Async 支持:

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

Spring的 @Async 基本上只是上述语法,请使用或。出于技术上的aop/proxying原因,使用 @Async 确实需要返回一个可完成的未来,在这种情况下,返回一个已经完成的未来是可以的:spring将使它在一个执行器中运行。服务层通常是“异步”的,但是控制器只是在返回的将来使用和组合:

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

通过调试您的代码,ide显示当前被断点阻塞的线程,确保它的行为符合您的预期。
旁注:这是我对阻塞与非阻塞、mvc与webflux、sync与async等的理解的一个简单总结。这是相当肤浅的,我的一些观点可能不够具体到100%正确。

相关问题