简体   繁体   English

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

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

What I am trying to achieve is can I get a better performance by using @Async and CompletableFuture as result in my controller of my RESTApi by using the multi threading in this simple way?我想要实现的是,通过以这种简单的方式使用多线程,在我的 RESTApi 的 controller 中使用 @Async 和 CompletableFuture 可以获得更好的性能吗?

here is what I do, here is my controller:这是我所做的,这是我的 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 VS

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

as you can see in my first controller function, I add the CompletableFuture on my function response, but in my service, which is I do save in this line categoryBPSJService.save(request) is not async, just a simple function that looked like this:正如你在我的第一个 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;

}

I just return simple Object with JPA connection, with this is way will my request performance increased?我只是通过 JPA 连接返回简单的 Object,这样我的请求性能会提高吗? or am I missing something to increase it?还是我错过了一些东西来增加它? or it make no difference with or without CompletableFuture and @Async on my controller?或者在我的 controller 上使用或不使用 CompletableFuture 和 @Async 都没有区别吗?

*note: my project is based on java 13 *注意:我的项目是基于 java 13

Using CompletableFuture won't magically improve the performances of your server.使用 CompletableFuture 不会神奇地提高服务器的性能。

If you're using Spring MVC , built on the Servlet API usually on top of Jetty or Tomcat, you'll have one thread per request.如果您使用的是Spring MVC ,通常在 Jetty 或 Tomcat 之上的 Servlet API 上构建,则每个请求将有一个线程。 The pool those threads are taken from is usually pretty big, so that you can have a decent amount of concurrent requests.从中获取这些线程的池通常非常大,因此您可以有相当数量的并发请求。 Here, blocking a request thread isn't that of an issue, as this thread is handling that single request only anyway, meaning other requests won't be blocked (unless there's no thread available in the pool anymore).在这里,阻塞一个请求线程不是问题,因为这个线程无论如何都只处理那个请求,这意味着其他请求不会被阻塞(除非池中不再有可用线程)。 This means, your IOs can be blocking, your code can be synchronous.这意味着,您的 IOs可以是阻塞的,您的代码可以是同步的。

If you're using Spring WebFlux though, usually on top of Netty, requests are handled as messages/events: one thread can handle multiple requests, which allows reducing the size of the pool (threads are expensive).但是,如果您使用的是Spring WebFlux ,通常在 Netty 之上,请求将作为消息/事件处理:一个线程可以处理多个请求,这样可以减少池的大小(线程很昂贵)。 In this case, blocking on a thread is an issue, as it can/will lead to other requests waiting for the IO to finish.在这种情况下,阻塞线程一个问题,因为它可能/将导致其他请求等待 IO 完成。 This means, your IOs must be non-blocking, your code must be asynchronous, so that the thread can be released and handle another request "in the meantime" instead of just waiting idle for the operation to finish.这意味着,您的 IOs必须是非阻塞的,您的代码必须是异步的,以便可以释放线程并“同时”处理另一个请求,而不是仅仅等待空闲等待操作完成。 Just FYI, this reactive stack looks appealing, but it comes with many other drawbacks to be aware of, because of the asynchronous nature of the codebase.仅供参考,这个反应式堆栈看起来很吸引人,但由于代码库的异步性质,它还有许多其他需要注意的缺点。

JPA is blocking, because it relies on JDBC (which blocks on the IOs). JPA 是阻塞的,因为它依赖于 JDBC(在 IO 上阻塞)。 This means, using JPA with Spring WebFlux doesn't make much sense and should be avoided, as it goes against the principle of "do not block a request thread".这意味着,使用 JPA 和 Spring WebFlux 没有多大意义,应该避免,因为它违背了“不阻塞请求线程”的原则。 People have found workarounds (eg running the SQL queries from within another thread pool), but this doesn't really solve the underlying issue: the IOs will block, contention can/will occur.人们找到了解决方法(例如,从另一个线程池中运行 SQL 查询),但这并没有真正解决根本问题:IOs 将阻塞,竞争可能/将会发生。 People are working on asynchronous SQL drivers for Java (eg Spring Data R2DBC and the underlying vendor-specific drivers), which can be used from within a WebFlux codebase for instance.人们正在为 Java 开发异步 SQL 驱动程序(例如Spring Data R2DBC和底层供应商特定驱动程序),例如可以在 WebFlux 代码库中使用这些驱动程序。 Oracle started working on their own asynchronous driver too, ADBA, but they abandoned the project because of their focus on fibers via Project Loom (which might soon totally change the way concurrency is handled in Java). Oracle 也开始研究他们自己的异步驱动程序 ADBA,但他们放弃了该项目,因为他们通过Project Loom专注于程(这可能很快就会完全改变 Java 中处理并发的方式)。

You seem to be using Spring MVC, meaning relying on the thread-per-request model. Just dropping CompletableFuture in your code won't improve things.您似乎正在使用 Spring MVC,这意味着依赖于 thread-per-request model。仅在代码中删除 CompletableFuture 不会改善情况。 Say you delegate all your service layer logic onto another thread pool than the default request thread pool: your request threads will be available, yes, but contention will now happen on that other thread pool of yours, meaning you'll just be moving your problem around.假设您将所有服务层逻辑委托给另一个线程池而不是默认请求线程池:您的请求线程将可用,是的,但是现在将在您的另一个线程池上发生争用,这意味着您只是在转移您的问题大约。

Some cases might still be interesting to postpone to another pool, eg computationally intensive operations (like passphrase hashing), or certain actions that would trigger a lot of (blocking) IOs, etc., but be aware that contention can still happen, meaning requests can still be blocked/waiting.有些情况可能仍然很有趣,可以推迟到另一个池,例如计算密集型操作(如密码哈希),或某些会触发大量(阻塞)IOs 等的操作,但请注意竞争仍然可能发生,这意味着请求仍然可以被阻止/等待。

If you do observe performance issues with your codebase, profile it first .如果您确实发现代码库存在性能问题,请先对其进行概要分析 Use tools like YourKit (many others available) or even APMs like NewRelic (many others available as well).使用像YourKit (许多其他可用的)这样的工具,甚至像NewRelic (还有许多其他可用的)这样的 APM。 Understand where the bottlenecks are, fix the worsts, repeat.了解瓶颈在哪里,解决最坏的问题,重复。 That being said, some usual suspects:话虽这么说,一些常见的嫌疑人:

  • too many IOs (especially with JPA, eg select n+1 ), IOs 太多(尤其是 JPA,例如select n+1 ),
  • too many serialisations/deserialisations (especially with JPA, eg eager fetching ).太多的序列化/反序列化(尤其是 JPA,例如eager fetching )。

Basically, JPA is the usual suspect: it's a powerful tool, but very easy to misconfigure, you need to think SQL to get it right IMHO.基本上,JPA通常的嫌疑人:它是一个强大的工具,但很容易配置错误,您需要考虑 SQL才能正确使用恕我直言。 I highly recommend logging the generated SQL queries when developing, you might be surprised.我强烈建议在开发时记录生成的 SQL 查询,您可能会感到惊讶。 Vlad Mihalcea's blog is a good resource for JPA related things. Vlad Mihalcea 的博客是 JPA 相关内容的良好资源。 Interesting read as well: OrmHate by Martin Fowler .阅读也很有趣: Martin Fowler 的 OrmHate


Concerning your specific code snippet, say you're going vanilla Java without Spring's @Async support:关于您的特定代码片段,假设您要在没有 Spring 的@Async支持的情况下使用 vanilla Java:

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

This won't make categoryBPSJService.save(request) run asynchronously.这不会使categoryBPSJService.save(request)异步运行。 It will be made more obvious if you split your code a bit:如果您稍微拆分代码,它会变得更加明显:

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

See what happened here?看看这里发生了什么? categoryBPSJ will be called synchronously, and you'll then create an already-completed future holding the result. categoryBPSJ将被同步调用,然后您将创建一个已完成的未来来保存结果。 If you really wanted to use a CompletableFuture here, you'd have to use a supplier:如果你真的想在这里使用 CompletableFuture,你必须使用供应商:

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

Spring's @Async is basically just syntax sugar for the above, use either/or. Spring 的@Async基本上只是上面的语法糖,使用 either/or。 For technical AOP/proxying reasons, a method annotated with @Async does need to return a CompletableFuture indeed, in which case returning an already-completed future is fine: Spring will make it run in an executor anyway.出于技术 AOP/代理原因,使用@Async注释的方法确实需要返回一个 CompletableFuture,在这种情况下返回一个已经完成的未来是好的:Spring 无论如何都会让它在执行程序中运行。 The service layer is usually the one being "async" though, the controller just consumes and composes over the returned future:服务层通常是“异步”层,controller 只是在返回的未来消费和组合:

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

Make sure it all behaves as you expect by debugging your code, IDEs show which thread is currently being blocked by a breakpoint.通过调试代码确保它的所有行为都符合您的预期,IDE 会显示当前哪个线程被断点阻止。


Side note: that's a simplified summary of what I understood from blocking vs non-blocking, MVC vs WebFlux, sync vs async, etc. It's quite superficial, some of my points might not be specific enough to be 100% true.旁注:这是我对阻塞与非阻塞、MVC 与 WebFlux、同步与异步等的理解的简单总结。这很肤浅,我的一些观点可能不够具体,无法 100% 正确。

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

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