博客 / 詳情

返回

線程池導致的 shutdown失敗的完整排查過程

SpringBoot 中有一種方式可以優雅地關閉應用程序。

(優雅停機是指​關閉應用程序時,在規定的超時時間範圍內,允許進行中的請求完成,拒絕新的請求進入​。 這將使應用在請求處理方面保持一致,即沒有未處理請求,每一個請求都被處理(完成或拒絕)

配置如下

server:
  port: 8888
  shutdown: graceful
management:
  endpoint:
    shutdown:
      enabled: true  
  endpoints:
    web:
      exposure:
        include: shutdown
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

現象

直接調用 localhost:8888/actuator/shutdown 即可關閉應用程序。但是在調用某個業務後,再調用 shutdown 的 api,發現實際 shutdown
確實在執行,但是最終並沒有把 pid 給 kill 掉,應用程序依然在運行。

第一懷疑就是認為這個程序執行後,還有什麼資源沒有被關閉掉,導致 springboot 認為應用程序還在運行,從而沒有執行關閉操作。

排查過程

執行腳本

生成線程快照
jstack -l pid > threads.txt

# 查詢非守護進程(因為非守護線程會阻止 JVM 退出) -v 表示反向排除
grep -n '" ' threads2.txt | grep -v daemon
  • 所有線程信息
    allThread.png
  • 非守護線程信息
    nonDaemonThread.png

在裏面發現了一段 關於 "pool-X-thread-Y"的線程信息,這個 ThreadPoolExecutor 出來的線程,處於等待中,其他的都是額外框架的線程信息或者 jvm 的,只有"pool-X-thread-Y"屬於額外的。

"pool-4-thread-1" #230 prio=5 os_prio=31 cpu=0.21ms elapsed=252.08s tid=0x00000001642cf800 nid=0x9a07 waiting on condition  [0x000000017aaf6000]
   java.lang.Thread.State: WAITING (parking)
    at jdk.internal.misc.Unsafe.park(java.base@17.0.13/Native Method)
    - parking to wait for  <0x0000000701e8af30> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
    at java.util.concurrent.locks.LockSupport.park(java.base@17.0.13/LockSupport.java:341)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@17.0.13/AbstractQueuedSynchronizer.java:506)
    at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@17.0.13/ForkJoinPool.java:3465)
    at java.util.concurrent.ForkJoinPool.managedBlock(java.base@17.0.13/ForkJoinPool.java:3436)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@17.0.13/AbstractQueuedSynchronizer.java:1630)
    at java.util.concurrent.ArrayBlockingQueue.take(java.base@17.0.13/ArrayBlockingQueue.java:420)
    at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@17.0.13/ThreadPoolExecutor.java:1062)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@17.0.13/ThreadPoolExecutor.java:1122)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@17.0.13/ThreadPoolExecutor.java:635)
    at java.lang.Thread.run(java.base@17.0.13/Thread.java:840)

   Locked ownable synchronizers:
    - None
  • ThreadPoolExecutor 默認線程名稱源碼
    defaultThreadName.png

有了這個排查方向,去項目裏面查找關於ThreadPoolExecutor的代碼。

最終發現一句關於線程池的聲明代碼。從代碼來看,雖然 XxxConfig 類上加了 @Configuration 註解,受到 spring 管理,但是 XXX_EXECUTOR 這個線程池是靜態變量,
並沒有受到 spring 管理,所以 springboot 在執行 shutdown 的時候,並不會關閉這個線程池,導致應用程序沒有被關閉。

@Configuration
public class XxxConfig { 
    public static final ThreadPoolExecutor XXX_EXECUTOR = new ThreadPoolExecutor(20, 20, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10000), new ThreadPoolExecutor.CallerRunsPolicy());

}

最終解決方案

建議將 XXX_EXECUTOR 這個線程池改為 spring 管理的 bean,如下所示:

@Configuration
public class XxxConfig {
    @Bean("xxxExecutor")
    public ThreadPoolExecutor xxxExecutor() {
        //示例 demo
        return new ThreadPoolExecutor(20, 20, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10000), new ThreadPoolExecutor.CallerRunsPolicy());
    }
}
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.