Loading

池化思想

Async

使用方式

  • 启动类里面使用@EnableAsync注解开启功能,自动扫描
  • 定义异步任务类并使用@Component标记组件被容器扫描,异步方法加上@Async

注意:@Async失效情况

  • 注解@Async的方法不是public方法

  • 注解@Async的返回值只能为void或者Future

  • 注解@Async方法使用static修饰也会失效

  • spring无法扫描到异步类,没加注解@Async 或 @EnableAsync注解

  • 调用方与被调方不能在同一个类

    • Spring 在扫描bean的时候会扫描方法上是否包含@Async注解,动态地生成一个子类(即proxy代理类),当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用时增加异步作用
    • 如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个 bean,所以就失效了
    • 所以调用方与被调方不能在同一个类,主要是使用了动态代理,同一个类的时候直接调用,不是通过生成的动态代理类调用
    • 一般将要异步执行的方法单独抽取成一个类
  • 类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象

  • 在Async 方法上标注@Transactional是没用的,但在Async 方法调用的方法上标注@Transactional 是有效的

问题现象

现象:压测后很快跑完全部内容,但是后台还是执行异步方法,是因为都在线程池内部的阻塞队列里面

  • 极容易出现OOM(阻塞队列被堆满),或者消息丢失(前端显示任务执行完成,后端还在持续执行异步方法,宕机则消息丢失)

  • 默认8个核心线程数占用满了之后, 新的调用就会进入队列, 最大值是Integer.MAX_VALUE,表现为没有执行

    • task-XXX 日志里面会出现递增

复现:设置下idea启动进程的jvm参数: -Xms50M -Xmx50M

image-20230206201956197

代码位置

  • TaskExecutionProperties

    • 核心线程数:8
      private int coreSize = 8;
      
      队列容量:Integer.MAX_VALUE  ( 21亿多)
      private int queueCapacity = Integer.MAX_VALUE;
      
      最大线程数:Integer.MAX_VALUE  ( 21亿多)
      private int maxSize = Integer.MAX_VALUE;
      
  • TaskExecutionAutoConfiguration

说明:

  • 直接使用 @Async 注解没指定线程池的话,即未设置TaskExecutor时
  • 默认使用Spring创建ThreadPoolTaskExecutor
  • 核心线程数:8
  • 最大线程数:Integer.MAX_VALUE ( 21亿多)
  • 队列使用LinkedBlockingQueue
  • 容量是:Integer.MAX_VALUE
  • 空闲线程保留时间:60s
  • 线程池拒绝策略:AbortPolicy

自定义线程池

Springboot线程池文章

ThreadPoolTaskExecutor和ThreadPoolExecutor

  • ThreadPoolExecutor,这个类是JDK中的线程池类,继承自Executor,里面有一个execute()方法,用来执行线程,线程池主要提供一个线程队列,队列中保存着所有等待状态的线程,避免了创建与销毁的额外开销

  • ThreadPoolTaskExecutor,是spring包下的,是Spring为我们提供的线程池类

    • Spring异步线程池的接口类是TaskExecutor,本质还是java.util.concurrent.Executor

解决方式

  • spring会先搜索TaskExecutor类型的bean或者名字为taskExecutor的Executor类型的bean,
  • 所以我们最好来自定义一个线程池,加入Spring IOC容器里面,即可覆盖
// 使用:
// 启动类可以不加@EnableAsync,在ThreadPoolTaskConfig上面加
// 异步方法指定线程池 @Async("threadPoolTaskExecutor")

@Configuration
@EnableAsync
public class ThreadPoolTaskConfig {

    @Bean("threadPoolTaskExecutor")
    public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        //线程池创建的核心线程数,线程池维护线程的最少数量,即使没有任务需要执行,也会一直存活
        //如果设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
        threadPoolTaskExecutor.setCorePoolSize(8);

        //缓存队列(阻塞队列)当核心线程数达到最大时,新任务会放在队列中排队等待执行
        threadPoolTaskExecutor.setQueueCapacity(124);

        //最大线程池数量,当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
        //当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
        threadPoolTaskExecutor.setMaxPoolSize(64);

        //当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
        //允许线程空闲时间60秒,当maxPoolSize的线程在空闲时间到达的时候销毁
        //如果allowCoreThreadTimeout=true,则会直到线程数量=0
        threadPoolTaskExecutor.setKeepAliveSeconds(30);

        //spring 提供的 ThreadPoolTaskExecutor 线程池,是有setThreadNamePrefix() 方法的。
        //jdk 提供的ThreadPoolExecutor 线程池是没有 setThreadNamePrefix() 方法的
        threadPoolTaskExecutor.setThreadNamePrefix("ccl自定义线程池:");
        
        // 设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,这样这些异步任务的销毁就会先于Redis线程池的销毁。
        threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);

        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CallerRunsPolicy():交由调用方线程运行,比如 main 线程;如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行
        // AbortPolicy():该策略是线程池的默认策略,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。
        // DiscardPolicy():如果线程池队列满了,会直接丢掉这个任务并且不会有任何异常
        // DiscardOldestPolicy():丢弃队列中最老的任务,队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

        threadPoolTaskExecutor.initialize();

        return threadPoolTaskExecutor;
    }
}

image-20230206210551657

总结【方便记忆】

  • 先是CorePoolSize是否满足,然后是Queue阻塞队列是否满,最后才是MaxPoolSize是否满足

RestTemplate

RestTemplate连接池文章

  • 重新认识RestTemplate

    • RestTemplate是Spring提供的用于访问Rest服务的客户端
    • 底层通过使用java.net包下的实现创建HTTP 请求
    • 通过使用ClientHttpRequestFactory指定不同的HTTP请求方式,主要提供了两种实现方式
      • SimpleClientHttpRequestFactory(默认)
        • 底层使用J2SE提供的方式,既java.net包提供的方式,创建底层的Http请求连接
        • 主要createRequest 方法( 断点调试),每次都会创建一个新的连接,每次都创建连接会造成极大的资源浪费,而且若连接不能及时释放,会因为无法建立新的连接导致后面的请求阻塞
      • HttpComponentsClientHttpRequestFactory
        • 底层使用HttpClient访问远程的Http服务
  • 问题解决

    • 客户端每次请求都要和服务端建立新的连接,即三次握手将会非常耗时
    • 通过http连接池可以减少连接建立与释放的时间,提升http请求的性能
    • Spring的restTemplate是对httpclient进行了封装, 而httpclient是支持池化机制
@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
        return new RestTemplate(factory);
    }

    @Bean
    public ClientHttpRequestFactory httpRequestFactory(){
        return new HttpComponentsClientHttpRequestFactory(httpClient());
    }

    @Bean
    public HttpClient httpClient(){

        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", SSLConnectionSocketFactory.getSocketFactory())
                .build();

        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);

        //设置连接池最大是500个连接
        connectionManager.setMaxTotal(500);
        //MaxPerRoute是对maxtotal的细分,每个主机的并发最大是300,route是指域名
        connectionManager.setDefaultMaxPerRoute(300);
        /**
         * 只请求 xdclass.net,最大并发300
         *
         * 请求 xdclass.net,最大并发300
         * 请求 open1024.com,最大并发200
         */

        RequestConfig requestConfig = RequestConfig.custom()
                //返回数据的超时时间
                .setSocketTimeout(20000)
                //连接上服务器的超时时间
                .setConnectTimeout(10000)
                //从连接池中获取连接的超时时间
                .setConnectionRequestTimeout(1000)
                .build();

        CloseableHttpClient closeableHttpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig)
                .setConnectionManager(connectionManager)
                .build();

        return closeableHttpClient;
    }
}
posted @ 2023-02-06 21:46  yonugleesin  阅读(61)  评论(0编辑  收藏  举报