开源模型应用落地-业务优化篇(八)

一、前言

    在之前的学习中,我相信您已经学会了一些优化技巧,比如分布式锁、线程池优化、请求排队、服务实例扩容和消息解耦等等。现在,我要给您介绍最后一篇业务优化的内容了。这个优化方法是通过定时统计问题的请求频率,然后将一些经常被请求的问题缓存起来,以提高系统的响应速度。


二、术语

2.1、 任务调度框架

    是一种用于管理和执行任务的软件工具或平台。它提供了一种结构和机制,使用户能够以自动化的方式安排、调度和执行任务,以满足特定的需求和要求。

2.2、分布式任务调度框架

    是一种用于管理和调度分布式环境中任务的软件工具或平台。它专注于在分布式系统中协调和执行任务,以提高整体性能、可伸缩性和容错性。

    分布式任务调度框架通常用于处理大规模任务和作业,并利用集群、云计算或容器化环境中的多个计算节点来并行执行任务。它们提供了一种分布式任务调度器,可以协调和分配任务到可用的计算节点,并监控任务的执行状态和进度。

2.3、XXL-JOB

    是一个开源的分布式任务调度平台,用于解决大规模任务调度和分布式定时任务管理的需求。它提供了一个可视化的任务调度中心,可以集中管理和调度各种类型的任务,包括定时任务、流程任务和API任务等。

2.4、Milvus

    是一个开源的向量数据库引擎,专门用于存储和处理大规模高维向量数据。它提供了高效的向量索引和相似性搜索功能,使用户能够快速地进行向量数据的存储、查询和分析。

    Milvus的设计目标是为了满足现代应用中对大规模向量数据的需求,例如人脸识别、图像搜索、推荐系统等。它采用了向量空间模型和多种索引算法,包括倒排索引、近似最近邻(Approximate Nearest Neighbor,ANN)等,以支持高效的相似性搜索。

    Milvus提供了易于使用的编程接口和丰富的功能,使用户可以方便地插入、查询和分析向量数据。它支持多种数据类型的向量,包括浮点型、整型等,也支持多种距离度量方法,如欧氏距离、余弦相似度等。

    Milvus还提供了分布式部署和横向扩展的能力,可以在多台机器上构建高可用性和高性能的向量数据库集群。它支持数据的分片和负载均衡,可以处理大规模数据集和高并发查询。
 


三、前置条件

3.1、已经根据前面的“开源模型应用落地”的学习搭建起完整AI流程

    1) 如何部署AI服务

    2) 如何使用向量数据库

    3) 如何使用RocketMQ

    ......

    本篇将通过定时任务周期性的统计问题请求的频次,并从向量数据库中,将热点问题同步至Redis,实现缓存前置,提升访问性能。


四、技术实现

4.1、新增定时任务处理类

import cn.hutool.core.map.MapUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import java.util.Map;@Slf4j
@Component
public class HotTopicStatistics {private static final Long DEFAULT_HOTSPOT_THRESHOLD = 10L;@Autowiredprivate RedisUtils redisUtils;@Autowiredprivate ContentCacheUtils contentCacheUtils;@Scheduled(cron = "*/30 * * * * ?")public void statistics() {RMap<String, String> rmap = redisUtils.hincget("CONTENT_COUNTER");if (MapUtil.isNotEmpty(rmap)) {for (Map.Entry<String, String> entry : rmap.entrySet()) {String keyword = entry.getKey();Long count = Long.parseLong(entry.getValue());// 计数器统计数值 > 热度阈值if(count.compareTo(DEFAULT_HOTSPOT_THRESHOLD) > 0){// 从向量数据库中拉取数据log.info("从向量数据库中拉取数据");String cacheContent = contentCacheUtils.cacheFromMilvus(keyword);if(StringUtils.isNotEmpty(cacheContent) && StringUtils.isNotBlank(cacheContent)){log.info("将热点内容缓存至redis中,过期时间设置为3600秒,内容为:{}",cacheContent);// 将热点内容缓存至redis中,过期时间设置为3600秒redisUtils.buckSet(keyword,cacheContent,60*60);}}}}}}

4.2、新增内容缓存公共类

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Console;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.List;
import java.util.Map;@Slf4j
@Component
public class ContentCacheUtils {private static final int DIM = 256;@Autowiredprivate AIChatUtils aiChatUtils;@Autowiredprivate MilvusUtils milvusUtils;public String cacheFromMilvus(String keyWord){if (StringUtils.isEmpty(keyWord) || StringUtils.isBlank(keyWord)){return null;}float[] vector = aiChatUtils.getVector("", keyWord);double[] double_arr = milvusUtils.pretreatment(vector, DIM);Float[] float_arr = Convert.toFloatArray(double_arr);List<Float> vectorList = CollUtil.list(false, float_arr);List search_vectors = new ArrayList(1);
//        打印日志Console.log(search_vectors);search_vectors.add(vectorList);Map<String, String> resultMap = milvusUtils.search_data_vector("tb_content", "keyword",search_vectors, null, 1, CollUtil.list(false, "content"));String status = resultMap.get("status");String cacheContent = null;if (StringUtils.equals(status, "0")) {cacheContent = resultMap.get("content");}return cacheContent;}}

4.3、修改Redis公共类

    增加以下方法

public  RMap<String, String> hincget(String key){RMap<String, String> rmap = null;if (StringUtils.isNotEmpty(key) && StringUtils.isNoneBlank(key) ) {rmap = redissonClient.getMap(key);}return rmap;
}public void buckSet(String key, String value,long second) {if (StringUtils.isNotEmpty(key) && StringUtils.isNoneBlank(key) && StringUtils.isNotEmpty(value) && StringUtils.isNoneBlank(value)) {RBucket<String> bucket =  redissonClient.getBucket(key);bucket.set(value,second, TimeUnit.SECONDS);}
}

4.4、修改业务处理类

    使用上面内容缓存公共类替换早前未封装的代码


五、测试

5.1、启动Redis

    启动windows版本的redis服务,redis-server.exe redis.windows.conf

5.2、将CONTENT_COUNTER的值设置为11

    下面使用Redis Desktop Manager工具编辑CONTENT_COUNTER的值

5.3、启动Milvus Server,并初始化数据

5.4、启动SpringBoot项目

    (一)运行Application

    (二)Redis当前只有一个Key,热点内容未缓存

    (三)定时任务触发

    (四)Redis缓存热点内容


六、附带说明

6.1、Spring Boot开启定时任务

    启用类增加@EnableScheduling注解

    需要将具体任务类加入到Spring管理,例如:增加@Component注解

    

6.2、实际项目中,应用使用分布式任务调度平台去替换本示例中SpringBoot内置的任务调度功能

6.3、Milvus Server启动超时

  直接编辑milvus下面的__init__.py文件,将timeout设置大一些

6.4、本章BusinessHandler完整代码

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Console;
import com.alibaba.fastjson.JSON;
import io.netty.channel.ChannelHandler;
import lombok.extern.slf4j.Slf4j;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;/*** @Description: 处理消息的handler*/
@Slf4j
@ChannelHandler.Sharable
@Component
public class BusinessHandler extends AbstractBusinessLogicHandler<TextWebSocketFrame> {public static final String LINE_UP_QUEUE_NAME = "AI-REQ-QUEUE";private static final String LINE_UP_LOCK_NAME = "AI-REQ-LOCK";private static final int MAX_QUEUE_SIZE = 100;//    @Autowired
//    private TaskUtils taskExecuteUtils;
//    @Autowired
//    private AIChatUtils aiChatUtils;
//    @Autowired
//    private MilvusUtils milvusUtils;@Autowiredprivate RedisUtils redisUtils;@Autowiredprivate RedissonClient redissonClient;@Autowiredprivate NettyConfig nettyConfig;@Autowiredprivate RocketMQProducer rocketMQProducer;@Autowiredprivate ContentCacheUtils contentCacheUtils;@Overridepublic void handlerAdded(ChannelHandlerContext ctx) throws Exception {String channelId = ctx.channel().id().asShortText();log.info("add client,channelId:{}", channelId);}@Overridepublic void handlerRemoved(ChannelHandlerContext ctx) throws Exception {String channelId = ctx.channel().id().asShortText();log.info("remove client,channelId:{}", channelId);}@Overrideprotected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame)throws Exception {// 获取客户端传输过来的消息String content = textWebSocketFrame.text();// 兼容在线测试if (StringUtils.equals(content, "PING")) {buildResponse(channelHandlerContext, ApiRespMessage.builder().code(String.valueOf(StatusCode.SUCCESS.getCode())).respTime(String.valueOf(System.currentTimeMillis())).msgType(String.valueOf(MsgType.HEARTBEAT.getCode())).contents("心跳测试,很高兴收到你的心跳包").build());return;}log.info("接收到客户端发送的信息: {}", content);Long userIdForReq;String msgType = "";String contents = "";try {ApiReqMessage apiReqMessage = JSON.parseObject(content, ApiReqMessage.class);msgType = apiReqMessage.getMsgType();contents = apiReqMessage.getContents();userIdForReq = apiReqMessage.getUserId();// 用户身份标识校验if (null == userIdForReq || (long) userIdForReq <= 10000) {ApiRespMessage apiRespMessage = ApiRespMessage.builder().code(String.valueOf(StatusCode.SYSTEM_ERROR.getCode())).respTime(String.valueOf(System.currentTimeMillis())).contents("用户身份标识有误!").msgType(String.valueOf(MsgType.SYSTEM.getCode())).build();buildResponseAndClose(channelHandlerContext, apiRespMessage);return;}if (StringUtils.equals(msgType, String.valueOf(MsgType.CHAT.getCode()))) {// 对用户输入的内容进行自定义违规词检测// 对用户输入的内容进行第三方在线违规词检测// 对用户输入的内容进行组装成Prompt// 对Prompt根据业务进行增强(完善prompt的内容)// 对history进行裁剪或总结(检测history是否操作模型支持的上下文长度,例如qwen-7b支持的上下文长度为8192)// ...//                通过线程池来处理
//                String messageId = apiReqMessage.getMessageId();
//                List<ChatContext> history = apiReqMessage.getHistory();
//                AITaskReqMessage aiTaskReqMessage = AITaskReqMessage.builder().messageId(messageId).userId(userIdForReq).contents(contents).history(history).build();
//                taskExecuteUtils.execute(aiTaskReqMessage);// 违规词检测log.info("contents: {}",contents);if (WordDetection.contains_illegal_word(contents)) {log.warn("the content sent contains illegal words");ApiRespMessage apiRespMessage = ApiRespMessage.builder().code(String.valueOf(StatusCode.ILLEGAL_WORDS_FAILURE_731.getCode())).respTime(String.valueOf(System.currentTimeMillis())).contents("内容不合规!").msgType(String.valueOf(MsgType.SYSTEM.getCode())).build();buildResponseAndClose(channelHandlerContext, apiRespMessage);return;}String[] filterWords = new String[]{"一", "语文", "老师"};List<String> keyWordsList = KeyWordsUtils.extractKeywords(contents, Arrays.asList(filterWords));String keyWord = CollUtil.join(keyWordsList, "");log.info("keyWord: {}", keyWord);String cacheContent = redisUtils.buckGet(keyWord);// 返回redis中的缓存数据if (StringUtils.isNotEmpty(cacheContent) && StringUtils.isNoneBlank(cacheContent)) {buildResponse(channelHandlerContext, ApiRespMessage.builder().code(String.valueOf(StatusCode.SUCCESS.getCode())).respTime(String.valueOf(System.currentTimeMillis())).msgType(String.valueOf(MsgType.CHAT.getCode())).contents(cacheContent).build());return;} else {// 从milvus中检索数据cacheContent = contentCacheUtils.cacheFromMilvus(keyWord);if (StringUtils.isNotEmpty(cacheContent) && StringUtils.isNoneBlank(cacheContent)) {buildResponse(channelHandlerContext, ApiRespMessage.builder().code(String.valueOf(StatusCode.SUCCESS.getCode())).respTime(String.valueOf(System.currentTimeMillis())).msgType(String.valueOf(MsgType.CHAT.getCode())).contents(cacheContent).build());return;}//投递消息String msg = "{\"msg\":\""+keyWord+"\"}";rocketMQProducer.send("ai-topic",msg);}
//                通过队列来缓冲boolean flag = true;RLock lock = redissonClient.getLock(LINE_UP_LOCK_NAME);String queueName = LINE_UP_QUEUE_NAME + "-" + nettyConfig.getNode();//尝试获取锁,最多等待3秒,锁的自动释放时间为10秒if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {try {if (redisUtils.queueSize(queueName) < MAX_QUEUE_SIZE) {redisUtils.queueAdd(queueName, content);log.info("当前线程为:{}, 添加请求至redis队列", Thread.currentThread().getName());} else {flag = false;}} catch (Throwable e) {log.error("系统处理异常", e);} finally {lock.unlock();}} else {flag = false;}if (!flag) {buildResponse(channelHandlerContext, ApiRespMessage.builder().code(String.valueOf(StatusCode.SUCCESS.getCode())).respTime(String.valueOf(System.currentTimeMillis())).msgType(String.valueOf(MsgType.SYSTEM.getCode())).contents("当前排队人数较多,请稍后再重试!").build());}} else if (StringUtils.equals(msgType, String.valueOf(MsgType.INIT.getCode()))) {//一、业务黑名单检测(多次违规,永久锁定)//二、账户锁定检测(临时锁定)//三、多设备登录检测//四、剩余对话次数检测//检测通过,绑定用户与channel之间关系addChannel(channelHandlerContext, userIdForReq);String respMessage = "用户标识: " + userIdForReq + " 登录成功";buildResponse(channelHandlerContext, ApiRespMessage.builder().code(String.valueOf(StatusCode.SUCCESS.getCode())).respTime(String.valueOf(System.currentTimeMillis())).msgType(String.valueOf(MsgType.INIT.getCode())).contents(respMessage).build());} else if (StringUtils.equals(msgType, String.valueOf(MsgType.HEARTBEAT.getCode()))) {buildResponse(channelHandlerContext, ApiRespMessage.builder().code(String.valueOf(StatusCode.SUCCESS.getCode())).respTime(String.valueOf(System.currentTimeMillis())).msgType(String.valueOf(MsgType.HEARTBEAT.getCode())).contents("心跳测试,很高兴收到你的心跳包").build());} else {log.info("用户标识: {}, 消息类型有误,不支持类型: {}", userIdForReq, msgType);}} catch (Exception e) {log.warn("【BusinessHandler】接收到请求内容:{},异常信息:{}", content, e.getMessage(), e);// 异常返回return;}}}

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

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

相关文章

【进阶五】Python实现SDVRP(需求拆分)常见求解算法——蚁群算法(ACO)

基于python语言&#xff0c;采用经典遗传算法&#xff08;ACO&#xff09;对 需求拆分车辆路径规划问题&#xff08;SDVRP&#xff09; 进行求解。 目录 往期优质资源1. 适用场景2. 代码调整3. 求解结果4. 代码片段参考 往期优质资源 经过一年多的创作&#xff0c;目前已经成熟…

Go函数全景:从基础到高阶的深度探索

目录 一、Go函数基础1.1 函数定义和声明基础函数结构返回值类型和命名返回值 1.2 参数传递方式值传递引用传递 二、Go特殊函数类型2.1 变参函数定义和使用变参变参的限制 2.2 匿名函数与Lambda表达式何为匿名函数Lambda表达式的使用场景 2.3 延迟调用函数&#xff08;defer&…

Arcgis新建位置分配求解最佳商店位置

背景 借用Arcgis帮助文档中的说明:在本练习中,您将为连锁零售店选择可以获得最大业务量的商店位置。主要目标是要将商店定位在人口集中地区附近,因为这种区域对商店的需求量较大。设立这一目标的前提是假设人们往往更多光顾附近的商店,而对于距离较远的商店则较少光顾。您…

禁止ie自动跳转edge

因为微软对ie已经彻底停止维护了&#xff0c;对于没有升级系统的用户来说&#xff0c;会自动更新edge然后将ie给禁止使用。下面方法有效的解决windows10下&#xff0c;禁止ie自动跳转edge。 方法一&#xff1a;对于2023年10月份前的更新可用 打开控制面板&#xff0c;点击网络…

STM32使用常见错误合集(正在更新版)

本文章记录一些学习STM32的一些错误问题 一、编译、烧录类问题 1、烧录不成功&#xff0c;Keil提示RDDI-DAP Error【场景&#xff1a;PWM驱动直流电机】 解决方案&#xff1a;将电机断开再进行烧录&#xff0c;断开后就可以美美烧录不报错啦~ 二、Keil使用问题 1、打开一个…

2 .Gen<I>Cam模块介绍

模块组成&#xff1a;GenApi&#xff0c;SFNC&#xff0c;GenTL&#xff0c;GenDC&#xff0c;GenCP。 首先让我来看下 GenTL (Transport Layer) GenApi( sometimes simply called the GenICam Standard) 传统相机应用程序二次开发&#xff0c;是基于相机厂家提供的sdk。使用…

IBM:《2024年消费者调研:无处不在的人工智能彻底变革零售业》

1月17日&#xff0c;IBM商业价值研究院最近发布了第三份两年一度的消费者调研报告。 这项名为《无处不在的人工智能彻底改变零售业&#xff1a;客户不会等待》的报告&#xff0c;对包含中国在内的全球近20000名消费者进行了调研&#xff0c;相关结果反映了消费者对零售体验的普…

Java中 常见的开源树库介绍

阅读本文之前请参阅------Java中 树的基础知识介绍 在 Java 中&#xff0c;有几种流行的开源树库&#xff0c;它们提供了丰富的树算法和高级操作&#xff0c;可以帮助开发者更高效地处理树相关的问题。以下是几种常见的 Java 树库及其特点和区别&#xff1a; JTree 特点…

使用SpaceDesk实现iPad成为电脑拓展屏(保姆级教程)

使用SpaceDesk实现iPad成为电脑拓展屏 SpaceDesk是一个开源的软件, 所以说对学生和平民用户非常的友好, 连接后的画质也非常不错, 而且具有无线和有线两种连接方式. 接下来就开始教程: 1. 安装SpaceDesk电脑版 首先我们要下载SpaceDesk电脑版安装好: SpaceDesk官网 注意: …

探索数据结构:双向链表的灵活优势

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;数据结构与算法 贝蒂的主页&#xff1a;Betty’s blog 1. 前言 前面我们学习了单链表&#xff0c;它解决了顺序表中插入删除需…

day-20 括号生成

思路:用dfs遍历所有的可能的括号组合&#xff0c;再通过istrue&#xff08;&#xff09;方法判断是否合法 code: class Solution {List<String> ansnew ArrayList<>();public List<String> generateParenthesis(int n) {int ln,rn;String s"";de…

在线考试系统,答题小程序 毕业设计作品

在线考试系统 介绍 在线考试系统&#xff0c;答题小程序&#xff0c;包含web版和小程序版&#xff0c; 支持全平台使用&#xff01;&#xff01;&#xff01; 这是一款 java vue 的前后端分离的考试系统。主要优点是开发、部署简单快捷、界面设计友好、代码结构清晰。支持we…

将Linux curl命令转换为windows平台的Python代码

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

3款国产良心软件,免费又实用,内存满了都舍不得卸载

以下三款软件质量卓越&#xff0c;失之交臂&#xff0c;实为遗憾。 1、文电通PDF 曾经一直依赖adobe reader来浏览PDF&#xff0c;但自从遇见文电通PDF&#xff0c;它的界面与Word如出一辙&#xff0c;让我这个习惯使用office的用户感到分外亲切。它不仅完全免费&#xff0c;…

Rust 深度学习库 Burn

一、概述 Burn 它是一个新的综合动态深度学习框架&#xff0c;使用 Rust 构建的&#xff0c;以极高的灵活性、计算效率和可移植性作为其主要目标。 Rust Burn 是一个以灵活性、高性能和易用性为核心设计原则工具&#xff0c;主打就是灵活性 、高性能 及易用性。 二、Rust B…

基于Python+django影片数据爬取与数据分析设计与实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

视觉系统对透明胶水的检测都有哪些方案?

透明胶水的检测在工业生产中是一个挑战&#xff0c;因为传统的基于RGB相机的视觉系统通常难以检测透明物体。然而&#xff0c;随着技术的发展&#xff0c;现在有多种方法可以有效地检测透明胶水。 1. 高光谱相机&#xff1a;高光谱相机可以提供不同于传统RGB相机的解决方案。例…

如何实现固定公网地址远程SSH连接Linux Deepin系统

文章目录 前言1. 开启SSH服务2. Deppin安装Cpolar3. 配置ssh公网地址4. 公网远程SSH连接5. 固定连接SSH公网地址6. SSH固定地址连接测试 前言 Deepin操作系统是一个基于Debian的Linux操作系统&#xff0c;专注于使用者对日常办公、学习、生活和娱乐的操作体验的极致&#xff0…

Text-to-SQL 工具Vanna | 查看训练数据、删除训练数据

1.查看训练数据vn.get_training_data vn.get_training_data 源码如下&#xff0c;可以看到返回的是df格式的数据 abstractmethoddef get_training_data(self, **kwargs) -> pd.DataFrame:"""Example:pythonvn.get_training_data()This method is used to ge…

几大常用的排序算法

文章目录 一、插入排序二、希尔排序&#xff08;缩小增量排序&#xff09;三、选择排序四、堆排序五、冒泡排序六、快速排序6.1 Hoare法6.2挖坑法快排的优化快排的非递归实现 七、归并排序归并的非递归实现 八、计数排序 一、插入排序 直接插入排序是一种简单的插入排序法&…