智能BI(后端)-- 系统异步化

文章目录

  • 系统问题分析
  • 什么是异步化?
  • 业务流程分析
    • 标准异步化的业务流程
    • 系统业务流程
  • 线程池
    • 为什么需要线程池?
    • 线程池两种实现方式
    • 线程池的参数
    • 线程池的开发
  • 项目异步化改造

系统问题分析

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

什么是异步化?

不用等一件事做完,就可以做另外一件事,等第一件事完成时,可以收到一个通知

业务流程分析

标准异步化的业务流程

  1. 当用户要进行耗时很长的操作时,点击提交后,不需要在界面空等,而是应该把这个任务保存到数据库中记录下来
  2. 用户要执行新任务时:
    a. 任务提交成功:
    ⅰ. 若程序存在空闲线程,可以立即执行此任务
    ⅱ. 若所有线程均繁忙,任务将入队列等待处理
    b. 任务提交失败:比如所有线程都在忙碌且任务队列满了
    ⅰ.选择拒绝此任务,不再执行
    ⅱ.通过查阅数据库记录,发现提交失败的任务,并在程序空闲时将这些任务取出执行
  3. 程序(线程)从任务队列中取出任务依次执行,每完成一项任务,就更新任务状态。
  4. 用户可以查询任务的执行状态,或者在任务执行成功或失败时接收通知(例如:发邮件、系统消息提示或短信),从而优化体验
  5. 对于复杂且包含多个环节的任务,在每个小任务完成时,要在程序(数据库中))记录任务的执行状态(进度)。

系统业务流程

  1. 用户点击智能分析页提交按钮时,先把图表立刻保存到数据库中(作为一个任务)
  2. 用户可以在图表管理查看所有图表(已生成的,生成中的,生成失败的)的信息和状态
  3. 用户可以修改生成失败的图表信息,点击重新生成,以尝试再次创建图表
    在这里插入图片描述

问题分析?

  1. 任务队列的最大容量应该设置为多少
  2. 程序怎么从任务队列中取出任务去执行?这个任务队列的流程怎么实现?怎么保证程序最多同时执行多少个任务?

线程池实现

线程池

为什么需要线程池?

  1. 线程的管理比较复杂
  2. 任务存取比较复杂
  3. 线程池可以帮你轻松管理线程,协调任务的执行过程

线程池两种实现方式

  1. Spring中,可以用ThreadPoolTaskExrcutor配合@Async注解来实现(不推荐)
  2. 在Java中,可以使用JUC并发编程包中的ThreadPoolExecutor来实现非常灵活地自定义线程池

线程池的参数

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

现状:AI生成能力的并发是只允许4个任务同时去执行,AI能力允许20个任务排队
corePoolSize(核心线程数):正常情况下,我们的系统应该能同时工作的线程数
maximumPoolSize(最大线程数):极限情况下,我们的线程池所拥有的线程
keepAliveTime(空闲线程存活时间):非核心线程在没有任务的情况下,过多久要删除,从而释放无用的线程资源
unit(空闲线程存活时间的单位):分钟,秒
workQueue(工作队列):用于存放给线程执行的任务,存在一个队列的长度(一定要设置)
threadFactory(线程工厂):控制每个线程的生成,线程的属性
RejectedExecutionHandler(拒绝策略):任务队列满的时候,我们采取什么措施

线程池的开发

自定义线程池配置

package com.yupi.springbootinit.config;import org.jetbrains.annotations.NotNull;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;@Configuration
public class ThreadPoolExecutorConfig {@Beanpublic ThreadPoolExecutor threadPoolExecutor(){// 创建一个线程工厂ThreadFactory threadFactory = new ThreadFactory(){// 初始化线程数为 1private int count = 1;// 创建一个新的线程@Override// 每当线程池需要创建新线程时,就会调用newThread方法// @NotNull Runnable r 表示方法参数 r 应该永远不为null,public Thread newThread(@NotNull Runnable r) {Thread thread = new Thread(r);thread.setName("线程" + count ++);return thread;}};// 创建一个新的线程池,线程池核心大小为2,最大线程数为4,// 非核心线程空闲时间为100秒,任务队列为阻塞队列,长度为4,使用自定义的线程工厂创建线ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,4,100, TimeUnit.SECONDS,new ArrayBlockingQueue<>(4),threadFactory);return threadPoolExecutor;}
}

测试controller层(注意线上环境不要暴露出去)

package com.yupi.springbootinit.controller;import cn.hutool.json.JSONUtil;
import io.netty.handler.codec.serialization.ObjectEncoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Profile;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;/*** 队列测试controller*/
@RestController
@RequestMapping("/queue")
@Slf4j
@Profile({"dev","local"}) // 只在开发环境和本地环境生效
public class QueueController {@Resourceprivate ThreadPoolExecutor threadPoolExecutor;@GetMapping("/add")// 接收一个参数name,然后将任务添加到线程池中public void add(String name){// 使用CompletableFuture运行一个异步任务CompletableFuture.runAsync(()->{log.info("任务执行中:" + name + "执行人:" + Thread.currentThread().getName());try {// 让线程休眠10分钟,模拟长时间运行的任务Thread.sleep(600000);} catch (InterruptedException e) {throw new RuntimeException(e);} 异步任务在threadPoolExecutor中执行},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);}
}

项目异步化改造

/*** 智能分析(异步)** @param multipartFile* @param genChartByAiRequest* @param request* @return*/
@PostMapping("/gen/async")
public BaseResponse<BiResponse> genChartByAiAsync(@RequestPart("file") MultipartFile multipartFile,GenChartByAiRequest genChartByAiRequest, HttpServletRequest request) {String name = genChartByAiRequest.getName();String goal = genChartByAiRequest.getGoal();String chartType = genChartByAiRequest.getChartType();// 校验ThrowUtils.throwIf(StringUtils.isBlank(goal), ErrorCode.PARAMS_ERROR, "目标为空");ThrowUtils.throwIf(StringUtils.isNotBlank(name) && name.length() > 100, ErrorCode.PARAMS_ERROR, "名称过长");// 校验文件long size = multipartFile.getSize();String originalFilename = multipartFile.getOriginalFilename();// 校验文件大小final long ONE_MB = 1024 * 1024L;ThrowUtils.throwIf(size > ONE_MB, ErrorCode.PARAMS_ERROR, "文件超过 1M");// 校验文件大小缀 aaa.pngString suffix = FileUtil.getSuffix(originalFilename);final List<String> validFileSuffixList = Arrays.asList("xlsx", "xls");ThrowUtils.throwIf(!validFileSuffixList.contains(suffix), ErrorCode.PARAMS_ERROR, "文件后缀非法");User loginUser = userService.getLoginUser(request);// 限流判断,每个用户一个限流器redisLimiterManager.doRateLimit("genChartByAi_" + loginUser.getId());// 指定一个模型id(把id写死,也可以定义成一个常量)long biModelId = 1659171950288818178L;// 分析需求:// 分析网站用户的增长情况// 原始数据:// 日期,用户数// 1号,10// 2号,20// 3号,30// 构造用户输入StringBuilder userInput = new StringBuilder();userInput.append("分析需求:").append("\n");// 拼接分析目标String userGoal = goal;if (StringUtils.isNotBlank(chartType)) {userGoal += ",请使用" + chartType;}userInput.append(userGoal).append("\n");userInput.append("原始数据:").append("\n");// 压缩后的数据String csvData = ExcelUtils.excelToCsv(multipartFile);userInput.append(csvData).append("\n");// 先把图表保存到数据库中Chart chart = new Chart();chart.setName(name);chart.setGoal(goal);chart.setChartData(csvData);chart.setChartType(chartType);// 插入数据库时,还没生成结束,把生成结果都去掉
//        chart.setGenChart(genChart);
//        chart.setGenResult(genResult);// 设置任务状态为排队中chart.setStatus("wait");chart.setUserId(loginUser.getId());boolean saveResult = chartService.save(chart);ThrowUtils.throwIf(!saveResult, ErrorCode.SYSTEM_ERROR, "图表保存失败");// 在最终的返回结果前提交一个任务// todo 建议处理任务队列满了后,抛异常的情况(因为提交任务报错了,前端会返回异常)CompletableFuture.runAsync(() -> {// 先修改图表任务状态为 “执行中”。等执行成功后,修改为 “已完成”、保存执行结果;执行失败后,状态修改为 “失败”,记录任务失败信息。(为了防止同一个任务被多次执行)Chart updateChart = new Chart();updateChart.setId(chart.getId());// 把任务状态改为执行中updateChart.setStatus("running");boolean b = chartService.updateById(updateChart);// 如果提交失败(一般情况下,更新失败可能意味着你的数据库出问题了)if (!b) {handleChartUpdateError(chart.getId(), "更新图表执行中状态失败");return;}// 调用 AIString result = aiManager.doChat(biModelId, userInput.toString());String[] splits = result.split("【【【【【");if (splits.length < 3) {handleChartUpdateError(chart.getId(), "AI 生成错误");return;}String genChart = splits[1].trim();String genResult = splits[2].trim();// 调用AI得到结果之后,再更新一次Chart updateChartResult = new Chart();updateChartResult.setId(chart.getId());updateChartResult.setGenChart(genChart);updateChartResult.setGenResult(genResult);updateChartResult.setStatus("succeed");boolean updateResult = chartService.updateById(updateChartResult);if (!updateResult) {handleChartUpdateError(chart.getId(), "更新图表成功状态失败");}},threadPoolExecutor);BiResponse biResponse = new BiResponse();
//        biResponse.setGenChart(genChart);
//        biResponse.setGenResult(genResult);biResponse.setChartId(chart.getId());return ResultUtils.success(biResponse);
}
// 上面的接口很多用到异常,直接定义一个工具类
private void handleChartUpdateError(long chartId, String execMessage) {Chart updateChartResult = new Chart();updateChartResult.setId(chartId);updateChartResult.setStatus("failed");updateChartResult.setExecMessage(execMessage);boolean updateResult = chartService.updateById(updateChartResult);if (!updateResult) {log.error("更新图表失败状态失败" + chartId + "," + execMessage);
}

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

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

相关文章

【文档理解】TextMonkey:一种OCR-Free的用于文档理解的多模态大模型

背景 传统的信息提取&#xff0c;通常是从文本中提取信息&#xff0c;相关技术也比较成熟。然而对于复杂领域&#xff0c;例如图片&#xff0c;文档等形式的数据&#xff0c;想要提取出高质量的、可信的数据难度就比较大了&#xff0c;这种任务也常称为&#xff1a;视觉文档理…

CTF网络安全大赛web题目:just_sqli

这道题目是bugku的web题目 题目的 描  述: KosenCTF{} 原文链接&#xff1a; CTF网络安全大赛web题目&#xff1a;just_sqli - 红客网-网络安全与渗透技术 题目Web源代码&#xff1a; <?php$user NULL; $is_admin 0;if (isset($_GET["source"])) {highlig…

齐护K210系列教程(二十七)_语音识别

语音识别 1.烧录固件和模型2.语音识别程序2.1训练并识别2.2使用本地文件语音识别 3.课程资源联系我们 1.烧录固件和模型 注&#xff1a;本应用只适用于有麦克风功能的型号&#xff1a;AIstart_pro、AIstart_掌机、AIstart_Mini, 其它型号不支持&#xff01; 机器码生成以及模…

linux中远程服务器上传输文件的10个sftp命令示例

目录 1. 如何连接到 SFTP 2. 帮助 3.检查当前工作目录 4. 使用 sftp 列出文件 远程 本地 5. 使用 sftp 上传文件 6. 使用 sftp 上传多个文件 7. 使用 sftp 下载文件 8. 在 sftp 中切换目录 远程 本地 9. 使用 sftp 创建目录 10. 使用 sftp 删除目录 11. 退出 sf…

(001)apidoc 的安装

安装 1.确定 node 和 npm 的匹配版本 node -vv10.14.1# 切换node 版本 nvm list nvm use 20.12.22.安装 apidoc。 npm install -g apidoc3.生成文档&#xff1a; apidoc -i ../ -o document/ -f ".java$"-i &#xff1a;指定扫描路径。-o&#xff1a;输出目录。…

【Linux:环境变量】

环境变量一般是指在操作系统中用来指定操作系统环境的一些参数 常见的环境变量&#xff1a; PATH 指定可执行程序的搜索路径 系统级的文件&#xff1a;/etc/bashrc 用户级文件&#xff1a;~/.bashrc ~/.bash_profile HOME 指定用户的主要工作目录&#xff08;当前用…

kettle从入门到精通 第六十一课 ETL之kettle 任务调度器,轻松使用xxl-job调用kettle中的job和trans

想真正学习或者提升自己的ETL领域知识的朋友欢迎进群&#xff0c;一起学习&#xff0c;共同进步。若二维码失效&#xff0c;公众号后台加我微信入群&#xff0c;备注kettle。 1、大家都知道kettle设计的job流程文件有个缺点&#xff1a;只能设置简单的定时任务&#xff0c;无法…

数据库-索引(高级篇)

文章目录 索引概念&#xff1f;索引演示&#xff1f;索引的优劣&#xff1f;为什么使用索引就快&#xff1f;本篇小结 更多相关内容可查看 索引概念&#xff1f; 索引&#xff08;index&#xff09;是帮助MySQL高效获取数据的数据结构(有序)。在数据之外&#xff0c;数据库系统…

语法分析-文法

如果对于一部文法中&#xff0c;存在至少一个句子有两个或者两个以上的语法树则该文法是二义性的。 我们可以以上面的例子进行解释&#xff0c;对于第棵个语法树&#xff0c;我们可以看到是先进行了加法运算再进行的乘法运算&#xff0c;因为需要先把EE作为整体运算完后再成为E…

上海亚商投顾:沪指低开低走 两市成交额跌破8000亿

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 市场全天震荡走低&#xff0c;三大股指尾盘均跌近1%。地产股逆势走强&#xff0c;光大嘉宝、天地源、云南城投…

幻兽帕鲁Palworld服务器手动+docker部署方法+备份迁移

目录 帕鲁部署官方文档帕鲁手动安装法手动安装steamcmd通过steamcmd安装帕鲁后端 docker容器一键部署幻兽帕鲁绿联云NAS机器部署幻兽帕鲁客户端连接附录1&#xff1a;PalServer.sh的启动项附录2&#xff1a;配置文件游戏存档保存和迁移 关于阿里云计算巢 帕鲁部署官方文档 htt…

​学者观察 | 从区块链应用创新看长安链发展——CCF区块链专委会荣誉主任斯雪明

导语 2024年1月27日&#xff0c;斯雪明教授在长安链发布三周年庆暨生态年会上发表演讲&#xff0c;认为在区块链发展过程中&#xff0c;不仅需要技术创新&#xff0c;同时需要有价值、有特色、有示范意义的应用创新。斯雪明教授介绍了国内区块链技术与应用发展的现状、趋势与挑…

【数据结构】排序(直接插入排序,希尔排序)

目录 一、排序的概念 二、常见的排序算法 三、插入排序 1.直接插入排序 1.直接插入排序实现 2.直接插入排序特性及复杂度 2.希尔排序 1.排序思路 2.希尔排序实现 3.希尔排序的特性及复杂度 一、排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#x…

python手写数字识别(PaddlePaddle框架、MNIST数据集)

python手写数字识别&#xff08;PaddlePaddle框架、MNIST数据集&#xff09; import paddle import paddle.nn.functional as F from paddle.vision.transforms import Compose, Normalizetransform Compose([Normalize(mean[127.5],std[127.5],data_formatCHW)]) # 使用tran…

[Java基础揉碎]多线程基础

多线程基础 什么是程序, 进程 什么是线程 什么是单线程,多线程 并发, 并行的概念 单核cpu来回切换, 造成貌似同时执行多个任务, 就是并发; 在我们的电脑中可能同时存在并发和并行; 怎么查看自己电脑的cpu有几核 1.资源监视器查看 2.此电脑图标右键管理- 设备管理器- 处理器…

k8s 二进制安装 详细安装步骤

目录 一 实验环境 二 操作系统初始化配置&#xff08;所有机器&#xff09; 1&#xff0c;关闭防火墙 2&#xff0c;关闭selinux 3&#xff0c;关闭swap 4, 根据规划设置主机名 5, 做域名映射 6&#xff0c;调整内核参数 7&#xff0c; 时间同步 三 部署 dock…

原生IP介绍

原生IP&#xff0c;顾名思义&#xff0c;即初始真实IP地址&#xff0c;是指从互联网服务提供商获得的IP地址&#xff0c;IP地址在互联网与用户之间直接建立联系&#xff0c;不需要经过代理服务器代理转发。 原生IP具备以下特点。 1.直接性 原生IP可以直接连接互联网&#xff…

代码随想录算法训练营第五十四天

第二题我看了很久还是没太明白&#xff0c;我发现理解动规有一点点吃力了啊&#xff0c;努努力。 392.判断子序列 总感觉在不等于的时候&#xff0c;应该是dp[i][j] dp[i-1][j-2]; 这里其实按他那个图会更好理解一点。 class Solution { public:bool isSubsequence(string s, …

CentOs安装

安装 开发工具 &#xff1a;GCC、 JDK、mysql 如果出现蓝屏&#xff0c;要在BIOS开启虚拟化支持&#xff0c;或者移除打印机。

Google:站长移除无效网址

当您的网址不需要呈现在Google站长中时&#xff0c;您可以在站长工具中移除网址 操作步骤&#xff1a;登录Google站长&#xff0c;绑定网站完成后&#xff0c;点击左侧删除 >> 输入网址 如果遇到一些网址&#xff0c;可以找寻网址间的规律&#xff0c;比如说&#xff0…