[開源專案]可觀測、易使用的SpringBoot執行緒池

2022-08-08 15:01:09

在開發spring boot應用服務的時候,難免會使用到非同步任務及執行緒池。spring boot的執行緒池是可以自定義的,所以我們經常會在專案裡面看到類似於下面這樣的程式碼

@Bean
public Executor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(config.getCorePoolSize());
    executor.setMaxPoolSize(config.getMaxPoolSize());
    executor.setQueueCapacity(config.getQueueCapacity());
    executor.setThreadNamePrefix("TaskExecutePool-");
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.initialize();
    return executor;
}

使用起來很方便,但是這樣做有幾個問題:

  • 開發人員在程式碼裡面隨意定義執行緒池,開發人員A自定義一個執行緒池,開發人員B自定義一個執行緒池。執行緒池的資源的使用沒有規劃與合理的安排,後期維護的成本升高。
  • 一旦發現執行緒池數量不足或資源滿載,很難調整設定,只能調整程式碼,重新部署。
  • 多人編寫程式碼,很難統計那個服務裡面存線上程池,有幾個執行緒池。
  • 如果不去跟蹤程式碼,你很難知道它的使用情況。定義了50個執行緒,存不存在資源浪費?存不存在資源等待?

為了解決上述的問題,我開發了一個Spring Boot Starter(開源專案地址:https://gitee.com/hanxt/zimug-monitor-threadpool ),方便整合到Spring Boot專案裡面去。目標是:在不改變SpringBoot執行緒池的核心實現的基礎上,使其視覺化、易觀測、易設定、易使用

需要說明的是:zimug-monitor-threadpool並未改變SpringBoot執行緒池的實現,只是在其基礎上新增了初始化階段的設定自動化載入,執行時的狀態監控。所以任何有關Spring Boot執行緒池執行時效能的討論,都與本文及其實現無關。

一、易整合、易設定

通過上文的專案地址獲取原始碼,然後maven編譯install本地m2倉庫。然後通過下面的maven座標引入

<dependency>
    <groupId>com.zimug</groupId>
    <artifactId>zimug-monitor-threadpool</artifactId>
    <version>1.0</version>
</dependency>

如下設定spring boot YAML(application.yml)所示,設定了兩個執行緒池,分別是test、test2。當thread-pool.enable=true的時候執行緒池設定生效。

thread-pool:
  enable: true
  poolLists:
    - poolId: test    #執行緒池唯一標識
      poolName: 測試1   #執行緒池的中文描述,比如執行緒池給誰用?
      coreSize: 5  #執行緒池初始化核心執行緒數量
      maxSize: 10  #執行緒池最大執行緒容量
      queueCapacity: 10  #執行緒池等待佇列的容量
    - poolId: test2
      poolName: 測試2
      coreSize: 5
      maxSize: 10
      queueCapacity: 10

通過下面的這張圖理解上面的設定資訊

  • 當執行緒任務數量core_size被活躍任務執行緒佔滿之後,執行緒任務會被放入等待佇列(queueCapacity=10)
  • 當等待佇列queueCapacity也被佔滿之後,才會擴大執行緒池的容量
  • 執行緒池的容量最大擴充套件到maxSize。如果maxSize和queueCapacity都滿了,任務就阻塞了。

二、易使用

使用方式和SpringBoot 程式碼方式自定義執行緒池的使用方式是一樣的。使用@Async註解的值是test,呼叫該註解標識的函數就會放入上文中設定的test執行緒池裡面去執行。

@Component
public class TestTask {
    @Async("test")   //注意這裡,test是執行緒池設定的poolId
    public Future<String> test() throws Exception {
        System.out.println("當前執行緒:" + Thread.currentThread().getName());
        return new AsyncResult<>("測試任務");
    }
}

三、視覺化易觀測

在專案中引入zimug-monitor-threadpool之後,進行執行緒池設定,使用執行緒池。存取服務的/pool.html即可獲取當前SpringBoot服務的執行緒池設定資訊,以及執行時狀態資訊。

  • 執行緒池ID、描述、初始化執行緒數、最大執行緒數、任務等待佇列的容量是上文中yaml靜態設定
  • 當前執行緒池的容量,即:執行緒池當前的執行緒數量(活躍+非活躍執行緒數總和)
  • 當前活躍執行緒數,即:正在執行程式任務的執行緒數量
  • 執行緒池活躍執行緒的最大峰值,如果該值等於初始化執行緒數,說明曾經出現了任務等待,即:任務放入等待佇列,效率較低。如果該值大於初始化執行緒數,說明任務等待佇列曾經滿載,需要擴容。如果該值接近等於最大執行緒數,就需要擴大最大執行緒數的值。
  • 當前任務等待佇列剩餘的容量,剩的越少,說明正在等待執行的任務就越多。

四、實現原理

zimug-monitor-threadpool的實現原理也非常簡單,簡單說一下原理,具體實現參考原始碼。

  • 首先通過SpringBoot載入yaml設定資訊,設定載入完成之後自定義實現設定自動化載入。這個實現原理及實現方法網上到處都是,我就不寫了。
  • 將設定資訊載入之後new 一個ThreadPoolTaskExecutor 物件,並通過Spring的ConfigurableBeanFactory將執行緒池物件的bean註冊到Spring上下文環境中,bean的id是poolId設定。就可以提供給執行時任務使用了。
configurableBeanFactory.registerSingleton(pool.getPoolId(), taskExecutor);
  • 待需要監測執行緒池執行時狀態的時候,再把執行緒池物件通過getBean方法獲取到,從而獲取執行時資訊返回給前臺請求。
ThreadPoolTaskExecutor memThreadPool = (ThreadPoolTaskExecutor) applicationContext.getBean(poolModel.getPoolId());

字母哥部落格:zimug.com