springboot优雅停机无法关闭进程,kill无法停止springboot必须kill -9,springboot线程池使用

背景最近项目在jenkins部署的时候发现部署很慢,查看部署日志发现kill命令执行后应用pid还存在,导致必须在60秒等待期后kill -9杀死springboot进程

应用环境

  • springboot
xml 复制代码
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
  <version>2.6.3</version>
</dependency>
  • springcloud
xml 复制代码
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2021.0.1.0</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>
  • 监控
xml 复制代码
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
  <version>2.6.3</version>
</dependency>

原因分析

  • 通过将全部日志调整为debug级别,观察到有个定时任务线程在不断执行,例子如下
java 复制代码
@SpringBootApplication
@MapperScan("com.test.test.mapper")
public class TestApplication implements CommandLineRunner {

	static ScheduledExecutorService executor;

	public static void main(String[] args) {
		executor = Executors.newScheduledThreadPool(1);
		SpringApplication.run(TestApplication.class, args);
	}

	private static void run(ScheduledExecutorService executor) {
		executor.scheduleAtFixedRate(() -> {
			System.out.println("run");
		}, 0, 1, TimeUnit.SECONDS);

	@Override
	public void run(String... args) throws Exception {
		run(executor);
	}
}

上述代码中,由于线程定义默认是非守护线程,执行优雅停机后,在用户线程停止后,非守护线程不会自动停止

解决办法

  1. 定义为守护线程
    对于非业务逻辑,例如监控数据上传,日志记录,这样做非常方便,但对于系统业务,这么做会导致未执行完成任务被丢弃。
  2. 将线程池定义为springbean,交予spring容器管理其生命周期
java 复制代码
@SpringBootApplication
@MapperScan("com.test.test.mapper")
public class TestApplication implements CommandLineRunner {

	public static void main(String[] args) {
		SpringApplication.run(TestApplication.class, args);
	}

	private static void run(ScheduledExecutorService executor) {
		executor.scheduleAtFixedRate(() -> {
			System.out.println("run");
		}, 0, 1, TimeUnit.SECONDS);
	}

	@Bean
	public ScheduledExecutorService executor() {
		return Executors.newScheduledThreadPool(1);
	}

	@Override
	public void run(String... args) throws Exception {
		ScheduledExecutorService executor = SpringUtil.getBean(ScheduledExecutorService.class);
		run(executor);
	}
}

效果
弊端:此类方式中,由于线程池的工作线程属于非守护线程,应用会等待所有任务执行完成后才关闭。由于容器已经关闭,数据库连接池已经释放,这时候任务再获取spring容器内容会报错,因此这种方案只适用于用户日志记录,监控等非业务功能,效果如下:

java 复制代码
@SpringBootApplication
@MapperScan("com.test.test.mapper")
@Slf4j
public class TestApplication implements CommandLineRunner {
	public static void main(String[] args) {
		SpringApplication.run(TestApplication.class, args);
	}

	private static void run(ExecutorService executor) {
		executor.execute(() -> {
			log.info("=====start");
			try {
				TimeUnit.SECONDS.sleep(25);
				User user = SpringUtil.getBean(IUserService.class).findById(10L);
				log.info("用户信息:" + user);
			} catch (Exception ex) {
				ex.printStackTrace();
			}
			log.info("=========end");
		});
	}

	@Bean
	public ExecutorService executor() {
		return new ThreadPoolExecutor(
			10, 10, 10, TimeUnit.SECONDS,
			new ArrayBlockingQueue<>(1),
			r -> {
				Thread thread =new Thread(r);
				return thread;
			},
			new ThreadPoolExecutor.DiscardOldestPolicy());
	}

	@Override
	public void run(String... args) throws Exception {
		ExecutorService executor = SpringUtil.getBean(ExecutorService.class);
		run(executor);
	}
}

3.使用spring提供的ThreadPoolTaskExecutor线程池

java 复制代码
@SpringBootApplication
@MapperScan("com.test.test.mapper")
@Slf4j
public class TestApplication implements CommandLineRunner {

	public static void main(String[] args) {
		SpringApplication.run(TestApplication.class, args);
	}

	private static void run(ThreadPoolTaskExecutor executor) {
		executor.execute(() -> {
			log.info("=====start");
			try {
				TimeUnit.SECONDS.sleep(25);
				User user = SpringUtil.getBean(IUserService.class).findById(10L);
				log.info("用户信息:" + user);
			} catch (Exception ex) {
				ex.printStackTrace();
			}
			log.info("=========end");
		});
	}

	@Bean
	public ThreadPoolTaskExecutor executor() {
		int core = Runtime.getRuntime().availableProcessors();
		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.setCorePoolSize(core > 3 ? core >> 1 : core);
		int maxSize = core + 2;
		executor.setMaxPoolSize(maxSize);
		//使用同步队列,避免任务进入等待队列排队导致耗时过长
		executor.setQueueCapacity(0);
		executor.setKeepAliveSeconds(30);
		executor.setWaitForTasksToCompleteOnShutdown(true);
		executor.setAwaitTerminationSeconds(25);
		executor.setThreadNamePrefix("async-");
		executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

		executor.initialize();

		return executor;
	}

	@Override
	public void run(String... args) throws Exception {
		ThreadPoolTaskExecutor executor = SpringUtil.getBean(ThreadPoolTaskExecutor.class);
		run(executor);
	}
}


从上图可以看到,应用会等待线程池任务执行完毕后才选择优雅关闭,因此对于异步业务任务,ThreadPoolTaskExecutor才是首选。

spring已经内置了ThreadPoolTaskExecutor 线程池实例,我们可以通过修改其配置参数,简化代码,例如:

bash 复制代码
spring:
  task:
    execution:
      pool:
        queue-capacity: 0
        core-size: 2
        max-size: 16
        keep-alive: 30s
      thread-name-prefix: 'async-'
      shutdown:
        await-termination: true
        await-termination-period: 25s
java 复制代码
@SpringBootApplication
@MapperScan("com.test.test.mapper")
@Slf4j
public class TestApplication implements CommandLineRunner {

	public static void main(String[] args) {
		SpringApplication.run(TestApplication.class, args);
	}

	private static void run(ThreadPoolTaskExecutor executor) {
		executor.execute(() -> {
			log.info("=====start");
			try {
				TimeUnit.SECONDS.sleep(25);
				User user = SpringUtil.getBean(IUserService.class).findById(10L);
				log.info("用户信息:" + user);
			} catch (Exception ex) {
				ex.printStackTrace();
			}
			log.info("=========end");
		});
	}

	@Override
	public void run(String... args) throws Exception {
		ThreadPoolTaskExecutor executor = SpringUtil.getBean(ThreadPoolTaskExecutor.class);
		run(executor);
	}
}

效果与上述手动创建效果一样

相关推荐
陈随易2 小时前
Postman 被曝自动往 Claude Code 和 Codex 装插件
前端·后端·程序员
AI你一生一世4 小时前
不止于时序存储:DolphinDB 如何成为 AI 时代的工业智能底座
java·人工智能·struts·时序数据库·工业智能·dolphindb·ai基础设施
云-缩影4 小时前
拆解 AI 图像生成应用的后端架构:ECS、OSS 和大模型 API 怎么配合
java·spring boot·后端
瑞克-桑切斯4 小时前
7、MySQL 慢查询排查与优化实战指南
java·服务器·数据库
考虑考虑5 小时前
Posrgresql恢复.backup文件
数据库·后端·postgresql
无奈何杨6 小时前
风控决策系统决策流,策略模式,权重、顺序、投票、最坏
后端
AI开源软件6 小时前
SpringBoot3+Vue3 财务管理系统如何设计:从业务单据到总账的模块划分
spring boot·vue3·领域建模·会计凭证·ruoyi office·业财协同·财务系统设计
Patrick_Wilson7 小时前
Go 为什么需要 defer?从函数怎么收尾说起
后端·go·编程语言
苏三说技术7 小时前
DeepSeek Harness入驻B站,评论区被干成了大型插件集市!
后端
橙序员小站7 小时前
我一句话让 AI 把公众号工作台搭完了:部署实测记录
后端