异步化、线程池、反向压力参数详解

异步化

问题场景:调用的服务处理能力有限,或者接口的处理(或返回)时长较长时,就要考虑异步化。

异步化

异步:不用等一件事做完,就可以做另外一件事,等第一件事完成时,可以收到一个通知,通知你这件事做好了,就可以再进行后续处理。

业务流程分析

标准异步化的业务流程:

  1. 当用户要进行耗时很长的操作时,不需要再界面傻等,可以把任务保存到数据库中记录下来,可以给用户一个类似于进度条的任务处理进度,提高用户体验

  2. 当用户提交新的请求:

    a. 任务提交成功:

    i. 如果我们的程序还有多余的空闲线程,就可以立即处理这个请求

    ii. 如果我们的程序的线程都在繁忙,无法继续处理,就把请求放入等待队列里

    b. 任务提交失败:

    i. 拒绝这个请求,再也不执行

    ii. 通过保存到数据库中的记录来看到提交失败的任务,并且再程序空闲的时候,把请求从数据库中 取出来执行

  3. 我们的程序(线程)从任务队列中取出任务依次执行,每完成一件事就修改一下任务的状态

  4. 用户可以查询任务的执行状态,或者在任务执行成功或失败时能得到通知(发邮件、系统消息提示、短信)从而优化体验

  5. 如果我们要执行的任务非常复杂,包含很多环节,在每个小任务完成时,要在程序(数据库)记录任务的执行状态

 

问题:

  1. 任务队列的最大容量应该设置为多少?

  2. 程序怎么从任务队列中取出任务区执行?这个任务队列的流程怎么实现?怎么保证程序最多同时执行多少个任务

线程池

为什么需要线程池?

  1. 线程管理比较复杂(什么时候新增线程、什么时候减少空闲线程)

  2. 任务存取比较困难(什么时候接受任务、什么时候拒绝任务、怎么保证大家不抢到同一个任务)

线程池:帮助你轻松管理线程、协调任务的执行过程

线程池的实现

无需自己实现,如果是使用 Spring 框架,可以用 ThreadPoolTaskExecutor 配合 @Async 注解来实现(不推荐)

可以使用 Java 中 JUC 并发编程包中的 ThreadPoolExecutor 来实现非常灵活自定义线程池

ThreadPoolExecutor 线程池参数:

 ThreadPoolExecutor(int corePoolSize, 
                    int maximumPoolSize, 
                    long keepAliveTime, 
                    TimeUnit unit, 
                    BlockingQueue<Runnable> workQueue,
                    ThreadFactory threaFactory,
                    RejectedExecutionHandler handler
                   )

线程池参数

如何确定线程池参数呢? => 结合实际的业务场景和系统资源来测试调整,不断优化

回归到业务中,考虑业务中最脆弱的环节(系统的瓶颈)在哪里?

corePoolSize:核心线程数(正式员工) => 正常情况下,我们的系统应该能同时工作的线程数(随时就绪的状态)

maximumPoolSize:最大线程数(哪怕任务再多,你也只能招这么多人) => 极限情况下,我们的线程池最多有多少个线程?

keepAliveTime:空闲线程存活时间 => 非核心线程在没有任务的情况下,过多久要删除(开除临时工)=> 释放无用的线程资源

TimeUnit:空闲线程存活时间单位,分钟、秒

WorkQueue:工作队列 => 用于存放给线程执行的任务,存一个队列的长度(一定要设置,不要说队列长度无限,因为也会占用资源)

ThreadFactory:线程工厂 => 控制每个线程的生成、线程的属性

RejectedExecutionHandler:拒绝策略 => 任务队列和可用线程数都满的情况下,我们采取什么措施,比如抛异常、自定义策略

资源隔离策略:比如主要任务(VIP 任务)一个对列,普通任务一个队列,保证这两个队列互不干扰

线程的工作机制

刚开始没有线程,也没有任何任务

 

 

来了一个线程,核心线程池有空闲进程(正式员工)=> 直接处理任务

 

又来一个任务,发现核心线程数还没用完,直接处理这个任务

 

又来两个任务,但是核心线程数已经用完了,任务被放到队列(最大长度workQueue 是 2)里等待,而不是新加线程

 

再来一个任务,但是任务队列已经满了(当前线程数 > corePoolSize = 2),已有任务数 = workQueue.size = 2,新增线程(maximumPoolSize = 4)来处理新任务,而不是丢弃任务

 

到任务 7,但是我们的任务队列已经满了、临时工也满了(当前线程数 = maximumPoolSize = 4),则调用 RejectedExecutionHandler 拒绝策略来处理多余的任务

 

如果当前线程数超过 corePoolSize,又没有新的任务给非核心线程来执行,那么等 KeepAliveTime 时间达到后,就会把这个线程释放。

一般情况下,任务分为 IO 密集型和计算密集型

计算密集型:吃 CPU,比如音视频处理、图像处理、数学计算等,一般是设置 corePoolSize 为 CPU 的核数 + 1,可以让每个线程都能利用好 CPU 的每个核,而且线程时间不用频繁切换(减少打架、减少开销)

IO 密集型:吃带宽/内存/硬盘的读写资源,corePoolSize 可以设置大一点,一般以 IO 的能力为主

开发实现流程

  1. 自定义线程池

 /**
  * @author zzt
  *
  * 自定义线程池
  */
 @Configuration
 public class ThreadPoolExecutorConfig {
     
     @Bean
     public ThreadPoolExecutor threadPoolExecutor() {
         ThreadFactory threadFactory = new ThreadFactory() {
             private int count = 1;
             @Override
             public Thread newThread(@NotNull Runnable r) {
                 Thread thread = new Thread(r);
                 thread.setName("线程" + count);
                 count++;
                 return thread;
             }
         };
         ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4, 100, TimeUnit.SECONDS,
                 new ArrayBlockingQueue<>(4), threadFactory);
         return threadPoolExecutor;
     }
     
 }
  1. 提交任务到线程池

 
public class QueueController {
     
     @Resource
     private ThreadPoolExecutor threadPoolExecutor;
     
     public void add(String name) {
         CompletableFuture.runAsync(() -> {
             System.out.println("任务执行中:" + name + ",执行人:" + Thread.currentThread().getName());
             try {
                 Thread.sleep(1000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }, threadPoolExecutor);
     }
     
     public String get() {
         Map<String, Object> map = new HashMap<>();
         int size = threadPoolExecutor.getQueue().size();
         map.put("队列长度", size);
         long taskCount = threadPoolExecutor.getTaskCount();
         map.put("任务总数", taskCount);
         long completedTaskCount = threadPoolExecutor.getCompletedTaskCount();
         map.put("已完成的任务数", completedTaskCount);
         int activeCount = threadPoolExecutor.getActiveCount();
         map.put("正在工作的线程数", activeCount);
         return JSONUtil.toJsonStr(map);
     }
     
 }

反向压力

通过调用的服务状态来选择当前系统的策略(比如根据 AI 服务的当前任务队列数来控制我们系统的核心线程数),从而最大化利用系统资源

反向压力实际上是流量控制的一种解决方案,使调用方和处理方的能力相匹配,保护系统各个节点处于持续的正常工作状态

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值