JAVA和Nginx 教程大全

网站首页 > 精选教程 正文

@Async引发线上服务内存溢出如何处理

wys521 2025-03-14 21:46:02 精选教程 17 ℃ 0 评论

前言

周日在家午休,刚醒,突然接到同事电话,说线上有个kafka消费者服务崩了,一直在重启,你去看看。一顿排查后,发现进程在启动后的一分钟内必定会占用大量内存,而且进程内的线程瞬间1万+。最后定位到某个方法是用了@Async注解在使用时未指定自定义的线程池,而且kafka。那它是何方神圣

@Async介绍

在Spring中,使用@Async直接的方法,称之为异步方法;这些方法执行的时候,将会在独立的线程中被执行,调用者无需等待它的完成,可继续其他的操作。

Spring默认配置

在spring boot项目中我们只需要添加@EnableAsync来开启异步调用,@Async异步方法默认使用Spring创建ThreadPoolTaskExecutor。我们也可以自己定义线程池(推荐)。下面是spring源码中默认的参数

从源码我们可以看出线程池的部分配置:

  • 默认核心线程数:1
  • 最大线程数:2147483647
  • 队列容量是:2147483647
  • 空闲线程保留时间:60s

奇怪,没有看到队列使用的是什么以及线程池拒绝策略,于是继续看源码,发现在queueCapacity大于0的时候用的是LinkedBlockingQueue队列,否者用的SynchronousQueue

那线程池拒绝策略呢?查看源码这个地方使用了策略模式,默认是AbortPolicy。

spring默认给的这些参数在并发不高的场景下其实问题不大。但是倘若并发上来这个这个默认的线程池隐患就体现出来了。为什么呢?风险点体现在最大线程数以及默认队列LinkedBlockingQueue的大小上面。

正常情况下,我们生产环境的会设置初始堆-Xms,最大堆的-Xmx等jvm参数,当接口并发远远大于自定义线程池的任务处理速度,那线程池的队列中会堆积很多任务,直接表现是JVM内存突然飙升。这种情况下。如果你直接在使用Spring默认的线程池,那这个LinkedBlockingQueue队列的默认容量就是你生产环境OOM的引爆点。哈哈

如何正确使用@Async

1、使用@EnableAsync注解开启

@EnableAsync
@SpringBootApplication
public class ApiClientApplication {

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

}

2.自定义线程池参数

//@EnableAsync 也可以放此处
@Configuration
public class ThreadPoolConfig {
   
   @Bean("taskExecutor")
   public Executor taskExecutor() {
       ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
       executor.setCorePoolSize(10);
       executor.setMaxPoolSize(20);
       executor.setQueueCapacity(200);
       executor.setKeepAliveSeconds(60);
       //线程池名的前缀
       executor.setThreadNamePrefix("taskExecutor-");
       executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
       return executor;
  }
}

3.使用

@Async("taskExecutor")
public void methodA(){
 
}

使用

1.我们使用单元测试测试一下,首先定义一个任务类

@Slf4j
@Component
public class Task {
   @Async("taskExecutor")
   public void doTask(int i) throws InterruptedException {
       log.info("开始做任务:{}",i);
       Random random = new Random();
       Thread.sleep(random.nextInt(10000));
       log.info("任务结束:{}",i);
  }
}

2.单元测试类

@SpringBootTest
@Slf4j
class OrderServiceApiClientApplicationTests {

   @Autowired
   private Task task;
   @Test
   public void test() throws Exception {
       for (int i = 0; i < 50; i++) {
           task.doTask(i);
      }
  }
 

3.测试结果

原创不易,点个关注呗

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表