在控制器中使用@Async和CompletableFuture可以提高我们api的性能? [英] is using @Async and CompletableFuture in controller can increase performance of our api?

查看:236
本文介绍了在控制器中使用@Async和CompletableFuture可以提高我们api的性能?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我要实现的目标是通过使用@Async和CompletableFuture来获得更好的性能,从而通过这种简单的方式使用多线程将我的RESTApi用作我的控制器?

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?

这是我的工作,这是我的控制器:

here is what I do, here is my 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));
}

正如您在我的第一个控制器函数中看到的那样,我在函数响应上添加了CompletableFuture,但是在我的服务中,这确实保存在了这一行中 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 :

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都没有区别?

I just return simple Object with JPA connection, with this is way will my request performance increased? or am I missing something to increase it? or it make no difference with or without CompletableFuture and @Async on my controller?

*注意:我的项目基于Java 13

*note : my project is based on java 13

推荐答案

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

Using CompletableFuture won't magically improve the performances of your server.

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

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. 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.

如果您使用的是

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). In this case, blocking on a thread is an issue, as it can/will lead to other requests waiting for the IO to finish. 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. 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正在阻止,因为它依赖JDBC(在IO上阻止).这意味着,将JPA与Spring WebFlux一起使用没有多大意义,应避免使用,因为这违背了不阻止请求线程"的原则.人们已经找到了解决方法(例如,从另一个线程池中运行SQL查询),但这并不能真正解决根本问题:IO将阻塞,争用可能发生/将发生.人们正在研究Java的异步SQL驱动程序(例如 Spring Data R2DBC 和底层供应商-特定的驱动程序),例如可以在WebFlux代码库中使用.Oracle也开始使用自己的异步驱动程序ADBA,但纤维,他们放弃了该项目.html"rel =" noreferrer> Project Loom (这可能很快会完全改变Java处理并发的方式).

JPA is blocking, because it relies on JDBC (which blocks on the IOs). 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". People have found workarounds (e.g. 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. People are working on asynchronous SQL drivers for Java (e.g. Spring Data R2DBC and the underlying vendor-specific drivers), which can be used from within a WebFlux codebase for instance. 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).

您似乎正在使用Spring MVC,这意味着依赖于每个请求线程模型.仅将CompletableFuture放入代码中并不能改善情况.假设您将所有服务层逻辑委托给默认请求线程池之外的另一个线程池:是的,您的请求线程将可用,但是现在您的其他线程池将发生争用,这意味着您将只是在解决问题周围.

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. 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.

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

Some cases might still be interesting to postpone to another pool, e.g. 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.

如果这样做发现代码库存在性能问题,请首先对其进行概要分析.使用诸如 YourKit 之类的工具(还有许多其他工具),甚至可以使用诸如 选择n + 1 ),太多的序列化/反序列化(尤其是对于JPA,例如记录生成的SQL查询,您可能会感到惊讶. Vlad Mihalcea的博客是与JPA相关的好资源.同样有趣的是: Martin Fowler的OrmHate .

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). Understand where the bottlenecks are, fix the worsts, repeat. That being said, some usual suspects: too many IOs (especially with JPA, e.g. select n+1), too many serialisations/deserialisations (especially with JPA, e.g. 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. I highly recommend logging the generated SQL queries when developing, you might be surprised. Vlad Mihalcea's blog is a good resource for JPA related things. Interesting read as well: OrmHate by Martin Fowler.

关于您的特定代码段,假设您在不使用Spring的 @Async 支持的情况下使用普通Java:

Concerning your specific code snippet, say you're going vanilla Java without Spring's @Async support:

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

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

This won't make categoryBPSJService.save(request) run asynchronously. 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;

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

See what happened here? categoryBPSJ will be called synchronously, and you'll then create an already-completed future holding the result. If you really wanted to use a CompletableFuture here, you'd have to use a supplier:

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

Spring的 @Async 基本上只是语法糖对于以上内容,请使用"/"或"/".出于技术上的AOP/代理原因,使用 @Async 注释的方法确实确实需要返回CompletableFuture,在这种情况下,返回已经完成的future很好:Spring仍将使其在执行程序中运行.服务层通常是一个异步"服务层.但是,控制器只是消耗并组成返回的未来:

Spring's @Async is basically just syntax sugar for the above, use 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. The service layer is usually the one being "async" though, the controller just consumes and composes over the returned future:

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

通过调试代码来确保所有行为均符合您的预期,IDE会显示断点当前正在阻止哪个线程.

Make sure it all behaves as you expect by debugging your code, IDEs show which thread is currently being blocked by a breakpoint.

旁注:这是我对阻塞与非阻塞,MVC与WebFlux,同步与异步等了解的简化摘要.这是很肤浅的,我的某些观点可能不够具体,不能100%正确.

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.

这篇关于在控制器中使用@Async和CompletableFuture可以提高我们api的性能?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆