成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

SpringBoot接口卡?一招異步化,吞吐量飆升10倍!

開發 前端
在實際開發中,我們需要根據任務類型(IO 密集型 vs CPU 密集型)選擇合適的異步處理方式,合理配置線程池參數,處理好線程安全、事務管理和異常處理等問題。只有這樣,才能充分發揮異步化的優勢,讓系統在高并發場景下依然保持高性能。

兄弟們,當你開著車來到一個收費站,結果發現所有車道都被堵得水泄不通。每個收費窗口都排著幾百米的長隊,后面的車只能干瞪眼。這時候你肯定想:要是收費站能同時處理更多車輛就好了!

其實咱們寫的 SpringBoot 接口,有時候就跟這個收費站一樣。當請求量突然暴增時,接口就會像被堵住的收費站一樣 “卡殼”,響應速度越來越慢,甚至直接崩潰。這時候,異步化就像是給收費站開通了 “ETC 專用車道”,能讓系統的吞吐量瞬間飆升 10 倍!

一、同步接口的三大罪狀

在聊異步化之前,咱們先來批斗一下傳統的同步接口。同步接口就像一個死板的收費站工作人員,必須把當前這輛車的費用收完、欄桿抬起來,才能去處理下一輛車。這種模式在高并發場景下,會犯下三大罪狀:

1. 線程阻塞:CPU 在摸魚

假設你的接口里有個耗時操作,比如調用第三方接口或者讀寫數據庫。這時候,處理這個請求的線程就會被死死卡住,只能眼巴巴地等著耗時操作完成。這就好比收費站工作人員收完錢后,還要等司機慢慢找零,后面的車只能排長隊。

在 Java 里,每個 Tomcat 線程默認只能處理一個請求。如果有 100 個這樣的請求同時進來,Tomcat 就需要 100 個線程來處理。要是線程池被占滿了,新的請求就只能在隊列里排隊,甚至直接被拒絕。

2. 資源浪費:服務器在哭泣

想象一下,你的服務器有 8 核 CPU,但每個線程都被阻塞在 IO 操作上,CPU 就只能在旁邊 “摸魚”。這就好比收費站有 8 個窗口,但每個窗口的工作人員都在玩手機,導致車輛越堵越多。

更嚴重的是,當線程被阻塞時,它們仍然占用著內存、網絡連接等資源。如果并發量很高,這些資源很快就會被耗盡,導致整個系統崩潰。

3. 吞吐量低下:老板在罵人

吞吐量是指系統在單位時間內處理的請求數量。同步接口的吞吐量往往很低,因為每個請求都要獨占一個線程直到處理完成。這就好比收費站只能一個一個地處理車輛,效率自然高不起來。

舉個栗子:假設你的接口處理一個請求需要 1 秒,Tomcat 線程池的最大線程數是 100。那么理論上,你的系統每秒最多只能處理 100 個請求。要是實際請求量超過這個數,系統就會直接 “罷工”。

二、異步化:給接口裝上渦輪增壓

既然同步接口這么拉胯,那異步化到底是怎么解決這些問題的呢?咱們先來看一個簡單的例子:

@RestController
public class AsyncController {
    @GetMapping("/sync")
    public String sync() throws InterruptedException {
        Thread.sleep(1000); // 模擬耗時操作
        return "Hello, World!";
    }
    @GetMapping("/async")
    public Callable<String> async() {
        return () -> {
            Thread.sleep(1000);
            return "Hello, World!";
        };
    }
}

在這個例子中,/sync接口是同步的,而/async接口使用了Callable實現異步處理。當調用/async接口時,Tomcat 線程會立即返回,把耗時操作交給另一個線程池處理。這樣,Tomcat 線程就可以去處理其他請求了。異步化的核心思想就是將耗時操作從 Tomcat 線程中剝離出來,讓 Tomcat 線程只負責接收和返回響應,而耗時操作則由專門的線程池處理。這樣一來,Tomcat 線程就可以被高效復用,系統的吞吐量自然就上去了。

三、SpringBoot 異步化的四種姿勢

SpringBoot 為我們提供了四種實現異步接口的方式,每種方式都有自己的適用場景。咱們一個一個來盤:

1. Callable:簡單粗暴的異步

Callable是 Spring 最早支持的異步處理方式,它的用法非常簡單:

@GetMapping("/async-callable")
public Callable<String> asyncCallable() {
    return () -> {
        // 模擬耗時操作
        Thread.sleep(1000);
        return "Hello, Callable!";
    };
}

當 SpringMVC 接收到這個請求時,會立即返回一個Callable對象,Tomcat 線程會被釋放。然后,Callable中的代碼會被提交到一個AsyncTaskExecutor線程池中執行。當任務完成后,SpringMVC 會再次調用 Tomcat 線程來返回結果。不過,Callable有個小缺點:它默認使用的SimpleAsyncTaskExecutor線程池不會重用線程,每次都會創建新線程。這在高并發場景下可能會導致性能問題,所以咱們需要自定義線程池:

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(25);
        executor.setThreadNamePrefix("Async-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

這樣配置后,Callable就會使用我們自定義的線程池,性能會得到顯著提升。

2. WebAsyncTask:帶超時控制的異步

WebAsyncTask是對Callable的封裝,它提供了更多的功能,比如超時控制和回調函數:

@GetMapping("/async-web-task")
public WebAsyncTask<String> asyncWebTask() {
    Callable<String> callable = () -> {
        Thread.sleep(2000);
        return "Hello, WebAsyncTask!";
    };
    return new WebAsyncTask<>(1500, callable); // 設置超時時間1.5秒
}

在這個例子中,如果耗時操作超過 1.5 秒還沒完成,就會觸發超時回調:

@GetMapping("/async-web-task")
public WebAsyncTask<String> asyncWebTask() {
    Callable<String> callable = () -> {
        Thread.sleep(2000);
        return "Hello, WebAsyncTask!";
    };
    WebAsyncTask<String> webAsyncTask = new WebAsyncTask<>(1500, callable);
    webAsyncTask.onTimeout(() -> "Timeout!"); // 超時回調
    webAsyncTask.onCompletion(() -> System.out.println("Task completed")); // 完成回調
    return webAsyncTask;
}

WebAsyncTask還支持設置優先級更高的超時時間,覆蓋全局配置。這在某些特定場景下非常有用。

3. DeferredResult:靈活的異步響應

DeferredResult允許我們在另一個線程中設置響應結果,這在需要異步處理復雜業務邏輯時非常有用:

@GetMapping("/async-deferred")
public DeferredResult<String> asyncDeferred() {
    DeferredResult<String> deferredResult = new DeferredResult<>();
    // 將DeferredResult保存到一個Map中,以便后續設置結果
    deferredResultMap.put("key", deferredResult);
    return deferredResult;
}
// 另一個線程中設置結果
public void setResult() {
    DeferredResult<String> deferredResult = deferredResultMap.get("key");
    deferredResult.setResult("Hello, DeferredResult!");
}

當調用asyncDeferred接口時,客戶端會一直處于 pending 狀態,直到另一個線程調用setResult方法。這種方式可以實現長輪詢等高級功能,但需要注意內存泄漏問題,及時清理無效的DeferredResult對象。

4. @Async 注解:方法級別的異步

@Async注解是 Spring 提供的另一種異步處理方式,它可以標記在方法上,讓方法在異步線程池中執行:

@Service
public class AsyncService {
    @Async("taskExecutor") // 指定線程池
    public CompletableFuture<String> asyncMethod() throws InterruptedException {
        Thread.sleep(1000);
        return CompletableFuture.completedFuture("Hello, @Async!");
    }
}
@RestController
public class AsyncController {
    @Autowired
    private AsyncService asyncService;
    @GetMapping("/async-annotation")
    public CompletableFuture<String> asyncAnnotation() {
        return asyncService.asyncMethod();
    }
}

@Async注解默認使用全局的TaskExecutor,但我們也可以通過@Async("taskExecutor")指定特定的線程池。這種方式適合將耗時操作封裝在 Service 層,讓 Controller 層保持簡潔。

四、線程池配置:異步化的心臟

不管是哪種異步處理方式,線程池的配置都是至關重要的。一個好的線程池配置可以讓系統的性能大幅提升,反之則可能導致系統崩潰。

1. 線程池參數詳解

線程池的核心參數包括:

  • corePoolSize:核心線程數,線程池啟動時創建的線程數。
  • maxPoolSize:最大線程數,線程池允許創建的最大線程數。
  • queueCapacity:隊列容量,當線程池已滿時,新任務會被放入隊列中等待。
  • keepAliveSeconds:線程空閑時間,超過這個時間的空閑線程會被銷毀。
  • rejectedExecutionHandler:拒絕策略,當線程池和隊列都滿時,如何處理新任務。

2. 如何選擇參數?

  • IO 密集型任務:線程數可以設置為 CPU 核心數的 2-4 倍,因為 IO 操作會讓線程大部分時間處于等待狀態。
  • CPU 密集型任務:線程數應該設置為 CPU 核心數 + 1,避免過多線程導致上下文切換開銷。
  • 隊列容量:根據實際業務場景設置,一般建議設置為幾百到幾千。
  • 拒絕策略:常用的有CallerRunsPolicy(讓調用者線程執行任務)和AbortPolicy(直接拋出異常)。

3. 多線程池配置

在復雜應用中,不同類型的任務可能需要不同的線程池配置。例如,IO 密集型任務和 CPU 密集型任務的線程數設置就應該不同:

@Configuration
@EnableAsync
public class MultiThreadPoolConfig {
    @Bean("ioTaskExecutor")
    public Executor ioTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        int processors = Runtime.getRuntime().availableProcessors();
        executor.setCorePoolSize(processors * 2);
        executor.setMaxPoolSize(processors * 4);
        executor.setQueueCapacity(50);
        executor.setThreadNamePrefix("IO-Task-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
    @Bean("cpuTaskExecutor")
    public Executor cpuTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        int processors = Runtime.getRuntime().availableProcessors();
        executor.setCorePoolSize(processors + 1);
        executor.setMaxPoolSize(processors + 1);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("CPU-Task-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

然后,在@Async注解中指定線程池名稱即可:

@Async("ioTaskExecutor")
public CompletableFuture<String> ioIntensiveTask() {
    // 執行IO密集型任務
}

五、異步化的三大挑戰與解決方案

雖然異步化能帶來巨大的性能提升,但也會引入一些新的問題。咱們來看看如何應對這些挑戰:

1. 線程安全:避免共享變量的坑

異步化意味著多個線程可能同時訪問共享資源,這就容易引發線程安全問題。例如:

@Service
public class AsyncService {

    private int count = 0;

    @Async
    public void increment() {
        count++; // 非線程安全操作
    }
}

在這個例子中,多個線程同時調用increment方法,會導致count的值不準確。解決方案是使用線程安全的類,如AtomicInteger:

private AtomicInteger count = new AtomicInteger(0);

@Async
public void increment() {
    count.incrementAndGet(); // 線程安全操作
}

2. 事務管理:異步化與事務的愛恨情仇

在異步方法中使用事務時,需要注意事務的傳播行為。默認情況下,異步方法不會共享主線程的事務上下文。例如:

@Service
publicclass AsyncService {

    @Autowired
    private UserRepository userRepository;

    @Transactional
    public void saveUser(User user) {
        userRepository.save(user);
        asyncService.asyncSaveOrder(user.getId()); // 異步方法
    }

    @Async
    @Transactional
    public void asyncSaveOrder(Long userId) {
        Order order = new Order();
        order.setUserId(userId);
        orderRepository.save(order);
    }
}

在這個例子中,saveUser方法和asyncSaveOrder方法會在不同的事務中執行。如果asyncSaveOrder方法拋出異常,不會回滾saveUser方法的事務。如果需要在異步方法中共享事務,可以使用TransactionSynchronizationManager手動綁定事務:

@Async
public void asyncSaveOrder(Long userId) {
    TransactionSynchronizationManager.bindResource(dataSource, new DataSourceTransactionObject(dataSource.getConnection()));
    // 執行數據庫操作
}

不過,這種方法比較復雜,一般建議在異步方法中避免使用事務,或者將事務邊界調整到同步方法中。

3. 異常處理:別讓異步任務 “跑路”

異步任務的異常處理需要特別注意,否則異常會被吞掉,導致問題難以排查。例如:

@Async
public void asyncTask() {
    try {
        // 執行耗時操作
    } catch (Exception e) {
        // 這里的異常不會被捕獲
    }
}

正確的做法是使用CompletableFuture的異常處理方法,或者為@Async方法配置全局異常處理器:

@Async
public CompletableFuture<String> asyncTask() {
    return CompletableFuture.supplyAsync(() -> {
        throw new RuntimeException("Async task failed");
    }).exceptionally(ex -> {
        log.error("Async task error: ", ex);
        return "Error handled";
    });
}

對于@Async方法,可以實現AsyncConfigurer接口,配置全局異常處理器:

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) -> {
            log.error("Async method error: ", ex);
        };
    }
}

六、異步化實戰:壓測數據說話

光說不練假把式,咱們來做個壓測看看異步化的效果。假設我們有一個接口,里面有一個 1 秒的耗時操作。我們分別用同步和異步方式實現,然后用 JMeter 進行壓測。

1. 同步接口壓測結果

  • 線程數:100
  • 循環次數:1000
  • 平均響應時間:1005ms
  • 吞吐量:約 100 QPS

2. 異步接口壓測結果

  • 線程數:100
  • 循環次數:1000
  • 平均響應時間:1020ms
  • 吞吐量:約 1000 QPS

從數據可以看出,異步化后的吞吐量提升了 10 倍!雖然平均響應時間略有增加,但這是因為異步處理引入了一些線程切換開銷。不過,這點開銷在高并發場景下幾乎可以忽略不計。

七、異步化的適用場景與注意事項

1. 適用場景

  • IO 密集型任務:如調用第三方接口、讀寫數據庫、文件上傳下載等。
  • 耗時操作:如發送郵件、生成報表、大數據處理等。
  • 高并發場景:需要處理大量并發請求的系統。

2. 注意事項

  • 避免過度使用:CPU 密集型任務使用異步化可能效果不佳,因為線程切換會增加開銷。
  • 合理配置線程池:根據業務場景調整線程池參數,避免資源浪費。
  • 監控線程池狀態:通過 Micrometer 等工具監控線程池隊列堆積、拒絕次數等指標。
  • 事務與異步的權衡:在異步方法中使用事務需要謹慎,盡量將事務邊界調整到同步方法中。

八、總結:異步化是銀彈,但別濫用

異步化就像是給 SpringBoot 接口裝上了渦輪增壓,能讓系統的吞吐量飆升 10 倍。但它并不是銀彈,需要根據具體業務場景合理使用。

在實際開發中,我們需要根據任務類型(IO 密集型 vs CPU 密集型)選擇合適的異步處理方式,合理配置線程池參數,處理好線程安全、事務管理和異常處理等問題。只有這樣,才能充分發揮異步化的優勢,讓系統在高并發場景下依然保持高性能。

責任編輯:武曉燕 來源: 石杉的架構筆記
相關推薦

2025-06-05 03:00:00

Spring異步接口

2024-09-09 14:12:38

2024-09-12 15:24:29

2024-12-13 13:58:53

2025-05-09 02:00:00

代碼接口吞吐量

2021-11-22 11:30:37

JavaScript代碼瀏覽器

2024-05-11 14:45:23

MAX基礎服務C端

2024-01-19 13:42:00

模型訓練

2024-06-28 09:39:58

2024-11-01 13:30:56

2010-02-06 09:47:42

以太網交換機

2013-04-19 09:45:20

AMPLabHadoopHDFS

2024-05-23 16:41:40

2024-11-02 10:28:03

2013-04-25 10:38:40

思科存儲交換機

2021-12-26 00:03:27

響應式編程異步

2025-04-16 08:25:00

2023-02-09 08:57:11

Callable異步java

2023-08-03 14:18:29

Rust阻塞函數

2023-11-07 15:11:46

Kafka技巧
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 91在线看网站 | 久久久久中文字幕 | 亚洲在线 | 久久躁日日躁aaaaxxxx | 最新91在线 | 欧美激情在线一区二区三区 | 午夜免费福利影院 | 午夜影视大全 | 欧美一级久久 | 色婷婷久久久亚洲一区二区三区 | h视频在线观看免费 | 精品在线看 | av网站免费在线观看 | 午夜影院操 | 亚洲免费人成在线视频观看 | 亚洲精品一区二区三区丝袜 | 免费亚洲成人 | 国产精品成人一区 | 久久逼逼 | 亚洲超碰在线观看 | 国产欧美久久一区二区三区 | 久久久久久国产精品 | 在线视频国产一区 | 欧美一级在线视频 | 一区二区免费视频 | 国产精品视频一区二区三区不卡 | 国产黄色麻豆视频 | 国产伦精品一区二区三毛 | 中文字幕国产精品 | 亚洲成人av在线播放 | 日本高清视频在线播放 | 国产视频久久 | 天天天天操 | 网站黄色在线 | 黄色在线网站 | 国产乱码精品1区2区3区 | 国产精品3区 | www.久久国产精品 | 国产精品日本一区二区在线播放 | 国产成人免费视频 | 特黄特黄a级毛片免费专区 av网站免费在线观看 |