簡體   English   中英

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

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

我想要實現的是,通過以這種簡單的方式使用多線程,在我的 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 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;

}

我只是通過 JPA 連接返回簡單的 Object,這樣我的請求性能會提高嗎? 還是我錯過了一些東西來增加它? 或者在我的 controller 上使用或不使用 CompletableFuture 和 @Async 都沒有區別嗎?

*注意:我的項目是基於 java 13

使用 CompletableFuture 不會神奇地提高服務器的性能。

如果您使用的是Spring MVC ,通常在 Jetty 或 Tomcat 之上的 Servlet API 上構建,則每個請求將有一個線程。 從中獲取這些線程的池通常非常大,因此您可以有相當數量的並發請求。 在這里,阻塞一個請求線程不是問題,因為這個線程無論如何都只處理那個請求,這意味着其他請求不會被阻塞(除非池中不再有可用線程)。 這意味着,您的 IOs可以是阻塞的,您的代碼可以是同步的。

但是,如果您使用的是Spring WebFlux ,通常在 Netty 之上,請求將作為消息/事件處理:一個線程可以處理多個請求,這樣可以減少池的大小(線程很昂貴)。 在這種情況下,阻塞線程一個問題,因為它可能/將導致其他請求等待 IO 完成。 這意味着,您的 IOs必須是非阻塞的,您的代碼必須是異步的,以便可以釋放線程並“同時”處理另一個請求,而不是僅僅等待空閑等待操作完成。 僅供參考,這個反應式堆棧看起來很吸引人,但由於代碼庫的異步性質,它還有許多其他需要注意的缺點。

JPA 是阻塞的,因為它依賴於 JDBC(在 IO 上阻塞)。 這意味着,使用 JPA 和 Spring WebFlux 沒有多大意義,應該避免,因為它違背了“不阻塞請求線程”的原則。 人們找到了解決方法(例如,從另一個線程池中運行 SQL 查詢),但這並沒有真正解決根本問題:IOs 將阻塞,競爭可能/將會發生。 人們正在為 Java 開發異步 SQL 驅動程序(例如Spring Data R2DBC和底層供應商特定驅動程序),例如可以在 WebFlux 代碼庫中使用這些驅動程序。 Oracle 也開始研究他們自己的異步驅動程序 ADBA,但他們放棄了該項目,因為他們通過Project Loom專注於程(這可能很快就會完全改變 Java 中處理並發的方式)。

您似乎正在使用 Spring MVC,這意味着依賴於 thread-per-request model。僅在代碼中刪除 CompletableFuture 不會改善情況。 假設您將所有服務層邏輯委托給另一個線程池而不是默認請求線程池:您的請求線程將可用,是的,但是現在將在您的另一個線程池上發生爭用,這意味着您只是在轉移您的問題大約。

有些情況可能仍然很有趣,可以推遲到另一個池,例如計算密集型操作(如密碼哈希),或某些會觸發大量(阻塞)IOs 等的操作,但請注意競爭仍然可能發生,這意味着請求仍然可以被阻止/等待。

如果您確實發現代碼庫存在性能問題,請先對其進行概要分析 使用像YourKit (許多其他可用的)這樣的工具,甚至像NewRelic (還有許多其他可用的)這樣的 APM。 了解瓶頸在哪里,解決最壞的問題,重復。 話雖這么說,一些常見的嫌疑人:

  • IOs 太多(尤其是 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;

這不會使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,你必須使用供應商:

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

Spring 的@Async基本上只是上面的語法糖,使用 either/or。 出於技術 AOP/代理原因,使用@Async注釋的方法確實需要返回一個 CompletableFuture,在這種情況下返回一個已經完成的未來是好的:Spring 無論如何都會讓它在執行程序中運行。 服務層通常是“異步”層,controller 只是在返回的未來消費和組合:

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

通過調試代碼確保它的所有行為都符合您的預期,IDE 會顯示當前哪個線程被斷點阻止。


旁注:這是我對阻塞與非阻塞、MVC 與 WebFlux、同步與異步等的理解的簡單總結。這很膚淺,我的一些觀點可能不夠具體,無法 100% 正確。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM