当前位置:网站首页>记一次线上接口慢查询问题排查
记一次线上接口慢查询问题排查
2022-07-01 06:33:00 【凉茶冰】
目录
问题描述
有一个分类预测的接口,主要业务逻辑是输入一段文本,接口内部调用模型对文本进行分类预测。 模型数据是直接在内存中,所以预测的过程本身很快。预测完成之后,往预测记录表插入一条数据。后续有其他应用会对该记录进行矫正,判断是否预测成功,以便后续进行自学习。
接口上线初期好评如潮,不管是做智能派遣还是经办单位流转预测使用效果都很好,随着自学习功能加入,预期应用应该会越跑越好。但是前两天客户现场突然反馈系统很慢,做智能派遣和流转的时候要三四秒甚至五六秒才能出结果。
最开始想的是现场的分类模型太多,模型树也比较深,所以一次多级预测不可避免会比较耗时,但是核查后发现最多的模型树只有四级,单个模型直接验证预测结果都是毫秒出结果,但是直接调用接口就很慢。 如果不是算法模型的问题,也不是机器资源的问题,那一定是代码逻辑中有没有考虑到的点,而且随着数据量激增,问题越来越明显。
经过简单排查,发现问题非常低级,造成慢的主要原因是因为预测记录表的数据已经将近100w了,而且这个表没有任何索引,只有一个自增主键,程序中,因为业务逻辑需要,每次调用预测接口都会有一个查询再更新或者插入的动作,并且是同步的。那么随着数据量的激增,接口的RT注定会越来越慢。
解决方案
问题是个小问题,主要原因还在于接口设计的初期没有做深入的考虑,代码写的很漂亮,只可惜不经考验,绣花枕头一个。对于这种问题,最直观的方案就是解耦,预测和预测记录入库做成异步的。 当然如果有业务场景硬要求不得不做成同步,也可以从代码层面和数据库层面做优化增加处理速度。
1 消息中间件
(1)可以直接引入kafka或者RocketMQ进行解耦,这种比较保险,数据不会丢,而且可以做到核心功能和非核心功能解耦。是主流解决方案,并且支持横向扩展和分布式。
(2)如果不想使用MQ,也可以直接使用Redis,Redis也可以实现消息队列的功能,参考这里
2 代码及数据库优化
如果不得不同步操作,那就要从代码层面进行优化和调整,主要以下几个思路:
(1)给查询的字段添加索引,通过数据库的能力提升IO
(2)设置分区表或者天表(月表)的概念,降低单表的数据量
(3)代码优化,将查询后的更新或者插入合并为一个sql操作,参考这里
insertOrUpdate的实现是基于mysql的on duplicate key update 来实现的。
使用ON DUPLICATE KEY UPDATE,如果行作为新行插入,则每行受影响的行值为1。如果更新现有行,则每行受影响的行值为2;如果将现有行设置为其当前值,则每行受影响的行值为0(可以通过配置,使其受影响的行值为1)。
官方地址:
对于异步解耦这种方案,如果之前应用本身没有MQ、Redis,为了解决该问题安装这些中间件,从某种程度上来说这无疑增加了系统复杂度好运维集成的工作量。基于此,我们也可以使用java自身的多线程来实现异步,基本思路就是在需要进行IO操作的地方,直接开启一个新线程去处理。
但是如果请求量很大,这无疑会造成频繁的线程创建、释放资源问题,如果引入线程池,又可能会出现因为资源用完阻塞的情况,这不能根本上解决同步的问题。
可以通过java多线程模拟消息队列,在需要进行IO操作的业务代码处,将业务数据封装为Bo放到一个队列中。有一个独立的线程对队列进行消费处理即可。可以参考这里
我主要是使用了ConcurrentLinkedQueue来解决了此问题,基本思路是,创建了一个定时任务,每30秒执行一次,每次都去处理ConcurrentLinkedQueue队列中的数据,将数据入库。 而业务代码中是将业务数据封装成Bo放到队列中去了。
3 ConcurrentLinkedQueue方案
定时任务
@Component
public class TaskJob
{
private static final Logger logger = LoggerFactory.getLogger(TaskJob.class);
@Scheduled(cron = "*/10 * * * * ?") //每10秒执行一次,异步处理预测结果信息,入库
public void execuPredictResult() throws Exception
{
SyncSavePredictResultService syncSavePredictResultService = new SyncSavePredictResultService();
syncSavePredictResultService.consumeData();
}
}异步处理
public class SyncSavePredictResultService
{
private static final Logger logger = LoggerFactory.getLogger(SyncSavePredictResultService.class);
//预测记录的消息队列,异步处理入库操作
public static final ConcurrentLinkedQueue<PredictResultBo> RESULT_BO = new ConcurrentLinkedQueue<>();
public void consumeData()
{
PredictResultBo resultBo = RESULT_BO.poll();
while (resultBo != null)
{
//进行业务逻辑处理
excuSubPdResult(resultBo.getContent(),
resultBo.getBegin(),
resultBo.getEnd(),
resultBo.getmId(),
resultBo.getFlagValue(),
resultBo.getPredictResult(),
resultBo.getRootPid(),
resultBo.getD());
logger.info("异步写入预测记录,对象内容为:{}", resultBo.toString());
//更新resultBo
resultBo = RESULT_BO.poll();
}
}
private void excuSubPdResult(String pstr, long begin, long end, Integer mId, String flagValue, String result, Integer rootPid, Date d)
{
NlpSubPredictResults nResult = NlpSubPredictResults.GetInstance().findFirst("select * from nlp_sub_predict_results where flag_value=?", flagValue);
if (nResult != null)
{
nResult.set("predict_result", result)
.set("start_time", begin)
.set("end_time", end)
.set("content", pstr)
.set("updated_at", d)
.update();
}
else
{
nResult = NlpSubPredictResults.GetInstance();
nResult.set("flag_value", flagValue)
.set("root_p_id", rootPid)
.set("m_id", mId)
.set("predict_result", result)
.set("start_time", begin)
.set("end_time", end)
.set("content", pstr)
.set("created_at", d)
.set("updated_at", d)
.save();
}
}
}业务流程
private void excuSubPdResult(String pstr,long begin,long end,Integer mId,String flagValue,String typeId,String typeName,Integer rootPid){
Map<String, String> rMap = new HashMap<>();
rMap.put("name",typeName);
rMap.put("id", typeId);
String result = JSON.toJSONString(rMap);
Date d = new Date();
PredictResultBo predictResultBo = new PredictResultBo(mId,rootPid,flagValue,result,begin,end,pstr,d);
SyncSavePredictResultService.RESULT_BO.add(predictResultBo);
/*
//2022-05-30改为异步
NlpSubPredictResults nResult = NlpSubPredictResults.GetInstance().findFirst("select * from nlp_sub_predict_results where m_id=? and flag_value=? and root_p_id=?",mId,flagValue,rootPid);
if(nResult!=null){
nResult.set("predict_result", result)
.set("start_time",begin)
.set("end_time",end)
.set("content",pstr)
.set("updated_at",d)
.update();
}else{
nResult = NlpSubPredictResults.GetInstance();
nResult.set("flag_value",flagValue)
.set("root_p_id",rootPid)
.set("m_id",mId)
.set("predict_result", result)
.set("start_time",begin)
.set("end_time",end)
.set("content",pstr)
.set("created_at",d)
.set("updated_at",d)
.save();
}
*/
}其他
如上的改造方式实际上是有隐患和弊端的
(1)如果接口调用量很大,难免会有消息积压,这时候如果节点挂了,那数据就丢失了。
(2)消息如果有大量积压,有可能撑爆内存,这样是会影响应用正常使用,也会造成数据丢失。
(3)无法支持多个消费程序。
(4)消费入库的逻辑实际上和核心的预测功能没关系,但是如果因为消费数据多,势必会影响核心预测功能的使用。这从软件架构上来说是不合理的。
上述这些问题通过MQ或者Redis都能很好的解决。
但是,但是任何事情没有绝对的,很多时候需要因地制宜,需要根据实际的业务要求、数据要求、项目紧急情况、成本预算等等,考虑到底使用哪种解决方案。
比如:通过单节点多线程ConcurrentLinkedQueue的方式就能解决99%的问题,所需要的成本预算是1,开发周期1天,而通过MQ能解决99.9%的问题,所需成本预算是10,开发周期7天。客户允许的容错是5%。这时候明显没有必要使用MQ的方案,没意义。
其实我所想表达的意思是,任何事情没有绝对的,我们始终应该抱着一种开放的心态去面对问题,没必要为了0.1%的优点引入99%的额外投入。
参考文献
边栏推荐
- The code generator has eliminated the styling of xxxx js as it exceeds the max of 500kb
- 微信公众号内嵌跳转微信小程序方案总结
- JSON module
- [unity shader ablation effect _ case sharing]
- MySQL constraint learning notes
- C#如何打印输出原版数组
- [ManageEngine Zhuohao] helps Huangshi Aikang hospital realize intelligent batch network equipment configuration management
- mysql约束学习笔记
- 【#Unity Shader#Amplify Shader Editor(ASE)_第九篇】
- K8S搭建Redis集群
猜你喜欢
随机推荐
C how to print out the original array
@Propagation property of transactional requires_ New in-depth understanding
网络爬虫
Free trial of self-developed software noisecreater1.1
mysql数据类型学习笔记
[ManageEngine Zhuohao] helps Julia college, the world's top Conservatory of music, improve terminal security
C#如何打印輸出原版數組
Esp32 - ULP coprocessor reading Hall sensor in low power mode
了解ESP32睡眠模式及其功耗
H5 web page determines whether an app is installed. If it is installed, it will jump to the summary of the scheme to download if it is not installed
Application of IT service management (ITSM) in Higher Education
绕圆旋转动画组件,拿过来直接用
Gson的@JsonAdater注解的几种方式
The code generator has eliminated the styling of xxxx js as it exceeds the max of 500kb
ESP32在电池供电时用ULP监测电池电压
mysql学习
【Unity Shader 消融效果_案例分享】
PAT (Advanced Level) Practice 1057 Stack
【微信小程序】如何搭积木式开发?
C#如何打印输出原版数组





![[enterprise data security] upgrade backup strategy to ensure enterprise data security](/img/59/e44c6533aa546e8854ef434aa64113.png)
![[unity shader ablation effect _ case sharing]](/img/e3/464f1cf426e8c03ce3d538ed9cf4bc.png)


