项目优化(异步化)

项目优化(异步化)

1. 认识异步化

1.1 同步与异步

  • 同步:一件事情做完,再做另外一件事情,不能同时进行其他的任务。
  • 异步:不用等一件事故完,就可以做另外一件事情。等第一件事完成时,可以收到一个通知,通知你这件事做好了,你可以再进行后续处理。

1.2 标准异步化的业务流程 ⭐

  1. 当用户要进行耗时很长的操作时,如点击提交后,不需要在界面长时间的等待,而是应该把这个任务保存到数据库中记录下来

  2. 用户要执行新任务时:

    1. 任务提交成功:

      • 程序还有多余的空闲线程时,可以立刻去执行这个任务。

      • 程序的线程都在繁忙时,无法继续处理,那就放到等待队列里。

    2. 任务提交失败:比如程序所有线程都在忙,任务队列满了

      • 拒绝掉这个任务,再也不去执行。

      • 通过保存到数据库中的记录来看到提交失败的任务,并且在程序空闲的时候,可以把任务从数据库中回调到程序里,再次去执行此任务。3

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

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

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

2. 线程池

  1. 线程池是什么:线程池是一种并发编程技术,用于优化多线程应用程序的性能和稳定性。它可以在应用程序启动时创建一组可重用的线程,并将工作任务分配给这些线程,以避免重复地创建和销毁线程,从而提高应用程序的吞吐量、响应时间和资源利用率。

  2. 线程池优点:

    1. 减少了线程的创建和销毁开销,提高了性能和效率。

    2. 避免了线程数量过多而导致的系统资源耗尽和线程调度开销增大的问题。

    3. 允许调整线程池大小,以满足不同应用程序的需求。

    4. 可以提高代码的可维护性和可重用性,避免了线程相关的错误,使得代码更加健壮和可靠。

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

  4. 线程池的实现:(不用自己写)

    1. 如果是在Spring中,可以用ThreadPoolTaskExecutor配合@Async注解来实现。(不太建议:进行了封装)

    2. JUC线程池的实现方式(JUC并发编程包,中的ThreadPoolExecutor来实现非常灵活地自定义线程池。)

      1. 创建配置线程池类

        /*** 配置线程池类* 可以在yml文件中写配置,实现自动注入*/
        @Configuration
        public class ThreadPoolExecutorConfig {/*** 线程池的实现类* @return*/@Beanpublic ThreadPoolExecutor threadPoolExecutor(){ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor();return threadPoolExecutor;}
        }
        
      2. 参数解释

        参数根据实际场景,测试调整,不断优化

        根据BI系统来说,线程数量要配置AI的能力(AI能力是瓶颈),ai支持4个线程,允许20个任务排队(参数根据条件调整)

        资源隔离策略:不同的程度的任务,分为不同的队列,比如VIP一个队列,普通用户一个队列。

        public ThreadPoolExecutor(int corePoolSize,//核心线程数:正常情况下,系统能同时工作的数量,处于随时就绪的状态int maximumPoolSize,//最大线程数,极限情况下,线程池有多少个线程long keepAliveTime,//空闲线程存活时间,非核心线程在没有任务的情况下多久删除,从而释放线程资源TimeUnit unit,//空闲线程存活时间单位BlockingQueue<Runnable> 															workQueue, //工作队列,用于存放给线程执行的任务,存在队列最大长度(一定要设置不可以为无限)ThreadFactory threadFactory,//线程工厂,控制每个线程的生产RejectedExecutionHandler handler//(拒绝策略)线程池拒绝策略,==任务队列满的时候,我们采取什么措施,比如抛异常、不抛异常、自定义策略==。) {}
        
        1. 线程池的工作机制

          1. 开始时:没有任务的线程,也没有任何的任务。
          1. 刚开始的核心线程数、最大线程数、任务队列中分别存在的数量为:

          corePoolSize = 0 ;maximumPoolSize = 0 ,workQueue.size = 0

          在这里插入图片描述

          1. 来了第一个任务,发现我们的员工没有到达正式的员工人数,来了一个员工直接处理这个任务。
          1. 第一个任务到来时的核心线程数、最大线程数、任务队列中分别存在的数量为:

          corePoolSize = 1 ;maximumPoolSize = 1 ,workQueue.size = 0

          在这里插入图片描述

          1. 又来了一个任务,发现我们的员工还没有达到正式员工数,再来一个员工直接处理这个任务。

          3、第二个任务到来时的核心线程数、最大线程数、任务队列中分别存在的数量为:

          corePoolSize = 2 ;maximumPoolSize = 2 ,workQueue.size = 0

          (一个人处理一个任务,一线程一任务)

          在这里插入图片描述

          1. 又来了一个任务,但是我们正式员工数已经满了(当前线程数 = corePoolSize = 2),将最新来的任务放到任务队列(最大长度 workQueue.size 是 2) 里等待,而不是再加新员工。

          4、第三、四个任务到来时的核心线程数、最大线程数、任务队列中分别存在的数量为:

          corePoolSize = 2 ;maximumPoolSize = 2 ,workQueue.size = 2

          (一个人处理一个任务,一线程一任务)

          在这里插入图片描述

          1. 又来了一个任务,但是我们的任务队列已经满了(当前线程数 大于了 corePoolSize=2,己有任务数 = 最大长度 workQueue.size = 2),新增线程(maximumPoolSize = 4)来处理任务,而不是丢弃任务。
          1. 第五个任务到来时的核心线程数、最大线程数、任务队列中分别存在的数量为:

          corePoolSize = 2 ;maximumPoolSize = 3 ,workQueue.size = 2

          (找了一个临时工处理这个队新来的这个任务)

          在这里插入图片描述

          1. 当到了任务7,但是我们的任务队列已经满了、临时工也招满了(当前线程数 = maximumPoolSize = 4,已有任务数 = 最大长度 workQueue.size = 2) 调用RejectedExecutionHandler 拒绝策略来处理多余的任务。
          1. 等到第六个任务到来时的核心线程数、最大线程数、任务队列中分别存在的数量为:

          corePoolSize = 2 ;maximumPoolSize = 4 ,workQueue.size = 2

          (再找了一个临时工处理这个队列任务中最前面的任务4,然后这个第六个新来的线程就进入到任务队列中等待)

          1. 等到第七个任务到来时的核心线程数、最大线程数、任务队列中分别存在的数量为:

          corePoolSize = 2 ;maximumPoolSize = 4 ,workQueue.size = 2

          (此时在核心线程数、最大线程数以及任务队列中都占满了,以及无法接收新的任务了,所以说只能拒绝任务7)

          7、最后,如果当前线程数超过 corePoolSize (正式员工数),又没有新的任务给他,那么等 keepAliveTime 时间达到后,就可以把这个线程释放。

        2. 确定线程池的参数

          1. corePoolSize(核心线程数=>正式员工数):正常情况下,可以设置为 2 - 4
          2. maximumPoolSize(最大线程数):设置为极限情况,设置为 <= 4
          3. keepAliveTime(空闲线程存活时间):一般设置为秒级或者分钟级
          4. TimeUnit unit(空闲线程存活时间的单位):分钟、秒
          5. workQueue(工作队列):结合实际请况去设置,可以设置为20 (2n+1)
          6. threadFactory(线程工厂):控制每个线程的生成、线程的属性(比如线程名)
          7. RejectedExecutionHandler(拒绝策略):抛异常,标记数据库的任务状态为 “任务满了已拒绝”
        3. 线程设置种类

          • IO密集型:耗费带宽/内存/硬盘的读写资源,corePoolSize可以设置大一点,一般经验值是 2n 左右,但是建议以 IO 的能力为主。
          • 计算密集型:耗费cpu资源,比如音视频资源,图像处理、数学计算等一般是设置corePoolSize为cpu的核数+1(空余线程池)

          考虑导入百万数据到数据库,属于IO密集型任务、还是计算密集型任务?

          答:考虑导入百万数据到数据库是一个IO密集型任务。导入数据需要从外部读取大量数据,然后将其写入数据库,这个过程中计算的工作量不是很高,相对来说需要更多的磁盘IO和网络IO。因此,这个任务的性能瓶颈通常是在IO操作上,而非计算上。

        4. 自定义线程池

          /*** 配置线程池类* 可以在yml文件中写配置,实现自动注入*/
          @Configuration
          public class ThreadPoolExecutorConfig {@Beanpublic ThreadPoolExecutor threadPoolExecutor() {ThreadFactory threadFactory = new ThreadFactory() {private int count = 1;@Overridepublic Thread newThread(@NotNull Runnable r) {// 一定要将这个 r 放入到线程当中Thread thread = new Thread(r);thread.setName("线程:" + count);// 任务++count++;return thread;}};ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4, 100, TimeUnit.SECONDS,new ArrayBlockingQueue<>(100), threadFactory);return threadPoolExecutor;}
          }
          
        5. 提交任务到自定义线程池

          @RestController
          @RequestMapping("/queue")
          @Slf4j
          @Profile({ "dev", "local" })
          @Api(tags = "QueueController")
          @CrossOrigin(origins = "http://localhost:8000", allowCredentials = "true")
          public class QueueController {@Resourceprivate ThreadPoolExecutor threadPoolExecutor;@GetMapping("/add")public void add(String name) {CompletableFuture.runAsync(() -> {log.info("任务执行中:" + name + ",执行人:" + Thread.currentThread().getName());try {Thread.sleep(60000);} catch (InterruptedException e) {throw new RuntimeException(e);}},threadPoolExecutor);}@GetMapping("/get")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);}
          }
          

3. 实操项目使用异步化优化

  1. 系统问题分析:

    1. 用户等待时间较长
    2. 业务服务器可能会有很多请求在处理,导致系统资源紧张,严重时导致服务器宕机或者无法处理新的请求(很多用户在系统中进行同一个请求导致系统使用体验降低。)
    3. 调用的第三方服务(如AI能力)的处理能力是有限的,比如每3秒只处理1个请求,当多个请求时会导致 AI 处理不过来,严重时AI可能会对后台系统拒绝服务。
  2. 解决方法=>异步化

    • 异步化使用场景:调用的服务处理能力有限,或者接口的处理(返回)时间较长,考虑异步化
  3. 异步化优化前后对比

    • 优化前架构图

      在这里插入图片描述

    • 优化后架构图

      在这里插入图片描述

  4. 异步化(new Rhread)实现

    1. 任务队列的最大容量应该设置多少合适?
    2. 程序怎么从任务队列中取出任务去执行,这个任务队列的流程怎么实现的,怎么保证程序最多同时执行多少个任务?
      • 阻塞队列
      • 线程池
      • 增加更多的人手?
    1. 流程梳理:

      1. 给chart表新增任务状态字段(比如排队中、执行中、已完成、失败),任务执行信息字段(用于记录任务执行中、或者失败的一些信息)

        -- 图表信息表
        create table if not exists chart
        (id          bigint auto_increment comment 'id' primary key,goal        text                                   null comment '分析目标',chartName   varchar(256)                           null comment '图表名称',chartData   text                                   null comment '图表数据',chartType   varchar(256)                           null comment '图表类型',genChart    text                                   null comment '生成的图表信息',genResult   text                                   nul l comment '生成的分析结论',chartStatus varchar(128) default 'wait'            not null comment 'wait-等待,running-生成中,succeed-成功生成,failed-生成失败',execMessage text                                   null comment '执行信息',userId      bigint                                 null comment '创建图标用户 id',createTime  datetime     default CURRENT_TIMESTAMP not null comment '创建时间',updateTime  datetime     default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',isDelete    tinyint      default 0                 not null comment '是否删除'
        ) comment '图表信息表' collate = utf8mb4_unicode_ci;
        
      2. 用户点击智能分析页面的提交按钮时,先把图表立刻保存到数据库中(作为一个任务)

      3. 任务:先修改图表任务状态为"执行中"。等执行成功后,修改为"已完成"、保存执行结果;执行失败后,状态修改为"失败",记录任务失败信息。

      4. 用户可以在图表管理界面插查看所有的图表的信息和状态

        • 已生成的
        • 生成中的
        • 生成失败的
      5. 用户可以修改生成失败的图表信息,点击重新生成图表

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/259723.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RocketMQ-RocketMQ高性能核心原理(流程图)

1.NamesrvStartup 2.BrokerStartup 3. DefualtMQProducer 4.DefaultMQPushConsumer

《每天一个Linux命令》 -- (5)通过sshkey密钥登录服务器

欢迎阅读《每天一个Linux命令》系列&#xff01;在本篇文章中&#xff0c;将介绍通过密钥生成&#xff0c;使用公钥连接管理服务器。 概念 SSH 密钥是用于安全地访问远程服务器的一种方法。SSH 密钥由一对密钥组成&#xff1a;公钥和私钥。公钥存储在远程服务器上&#xff0c;…

Hanlp自然语言处理如何再Spring Boot中使用

一、HanLP HanLP (Hankcs NLP) 是一个自然语言处理工具包&#xff0c;具有功能强大、性能高效、易于使用的特点。HanLP 主要支持中文文本处理&#xff0c;包括分词、词性标注、命名实体识别、依存句法分析、关键词提取、文本分类、情感分析等多种功能。 HanLP 可以在 Java、Py…

Nginx按指定格式记录访问日志

今天突然想起来一个日志的一个东西,因为拉项目无意中看到了日志文件的一些东西,现在不经常做后端了,加上其他的一些原因吧.有时候有些问题也没想太多,马马虎虎就过了,后来想想还是要记录一下这方面的处理过程吧: 一般我们作为开发人员关注的日志只是在应用程序层面的,我们称它…

网页设计--第6次课后作业

试用Vue相关指令完成对以下json数据的显示。显示效果如下&#xff1a; 其中&#xff1a;gender1 显示为女&#xff0c;gender2显示为男。价格超过30元&#xff0c;显示“有点小贵”。价格少于等于30元&#xff0c;则显示“价格亲民”。 data: {books: [{"id": "…

hook其他调试技巧

输出堆栈信息 通过 android.util.Log 输出当前线程的堆栈跟踪信息。 function showStacks() {Java.perform(function () {console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new() )); }) } 可以在需要的…

3GPP标准查看、下载和几个UE相关系列标准

由于一直做终端侧协议。最近以UE为核心重新下载了一系列文档。 总结并举例一下分类标准。 如何查看3GPP标准列表 实际上在3GPP网站如下链接&#xff1a;Specifications by Series&#xff0c;每个系列以及分类都说的很清楚。 几个系列分类举例 和终端协议层工作比较关系密切…

阿里云国际基于CentOS系统镜像快速部署Apache服务

阿里云轻量应用服务器提供了Windows Server系统镜像和主流的Linux系统镜像&#xff0c;您可以通过该类镜像创建纯净、安全、稳定的运行环境。本文以CentOS 7.6系统镜像为例&#xff0c;介绍如何快速配置Apache服务。 背景信息 注意&#xff0c;阿里云国际通过corebyt注册并充…

Spring Boot 整合 xxl-job 保姆级教程!

文章目录 介绍使用初始化“调度数据库”配置调度中心配置“执行器项目”调度任务 介绍 首先我们介绍一下什么是xxl-job&#xff0c;根据官方定义&#xff0c;XXL-JOB是一个分布式任务调度平台&#xff0c;其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码…

LeetCode(52)最小栈【栈】【中等】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 最小栈 1.题目 设计一个支持 push &#xff0c;pop &#xff0c;top 操作&#xff0c;并能在常数时间内检索到最小元素的栈。 实现 MinStack 类: MinStack() 初始化堆栈对象。void push(int val) 将元素val推入堆栈。void…

激光打标机在智能手表上的应用:科技与时尚的完美结合

随着科技的飞速发展&#xff0c;智能手表已经成为我们日常生活中不可或缺的智能设备。而在智能手表制造中&#xff0c;激光打标机扮演着至关重要的角色。本文将详细介绍激光打标机在智能手表制造中的应用&#xff0c;以及其带来的优势和影响。 ​ 一、激光打标机在智能手表制…

UE4 材质实现Glitch效果

材质实现Glitch效果 UE4 材质实现Glitch效果预览1预览2 UE4 材质实现Glitch效果 预览1 添加材质函数&#xff1a; MF_RandomNoise 添加材质&#xff1a; 预览2 添加材质函数MF_CustomPanner&#xff1a; 添加材质函数&#xff1a;MF_Glitch 材质添加&#xff1a; 下面用…