共计 2573 个字符,预计需要花费 7 分钟才能阅读完成。
本文来自: PerfMa 技术社区
PerfMa(笨马网络)官网
一、问题
模型服务平台的排序申请呈现较多超时状况,且不定时随同空指针异样。
二、问题产生前后的改变
召回引擎扩充了召回量,导致排序申请的 item 数量减少了。
三、出问题的模型
基于 XGBoost 预测的全排序模型。
四、我的项目介绍
web-rec-model:模型服务平台。用于治理排序模型:XGBoost、TensorFlow、pmml…. 召回模型:item2item,key2item,vec2item…. 等模型的高低线、测试模型一致性、模型服务等。
五、一次排序申请流程
1、如下图所示,一次排序申请流程蕴含:特色获取、向量获取、数据处理及预测。以上提到的三个步骤均采纳多线程并行处理,均以子工作模式执行。每个阶段两头夹杂这数据处理的流程,由主线程进行解决,且每个阶段的执行工作均为超时返回,主线程期待子线程工作时,也采纳超时期待的策略。(共事实现的一个树形工作执行,超时期待的线程框架)
2、特色数据闭环:该步骤为异步执行,将排序计算应用到的特色及分数,模型版本等信息记录。后续作为模型的训练样本,达到特色闭环。
3、一次排序申请中,特色获取及向量获取 为网络 IO(IO 密集型工作),超时可间接响应中断,线程可疾速返回。数据处理及模型 为计算步骤(CPU 密集型工作)。
4、以后申请耗时状况:特色与向量的获取阶段耗时均为 5 -8ms,数据处理及模型预测阶段耗时均匀在 10ms 左右。
六、问题产生景象
1、首先是调用方:举荐策略平台,监控报警排序申请的超时数量变多(调用方超时工夫为 300ms),且从监控上看发现排序服务的耗时显著变长:50ms+。失常高峰期的期望值为 50ms 以下。
2、其次排序服务告警呈现大量超时谬误。
3、第三依据错误信息定位到该错误信息来自于 数据处理及模型预测 阶段。
4、除了超时变多以外,服务中会呈现偶发性的空指针异样。
七、问题排查
1、首先解决空指针这类低级谬误。
2、依据谬误提醒找到对应的代码,此处就不粘贴代码了,做一个简略的代码解释。代码逻辑为:从 Map<String,Object> 中依据特色 key 获取特征值进行计算。
3、纳闷点呈现,首先该 Map<String,Object> 用于寄存特色及向量键值对,且 key 均做了空值计算兼容。特色或者向量在查问到空值时,会在 Map<String,Object> 中放入一个对应的默认值。通过重复的代码确认,报错信息对应的代码不可能呈现漏放默认值的状况。
4、借助 Arthas 的 watch 命令,监控空指针异样的入参。不便前面做模仿申请还原现场。
5、依据报错时的信息进行模仿申请。尝试 N 次,且应用不同的报错数据进行尝试,均未重现事变。
6、此时狐疑是多线程并发进行 数据处理及预测 时,产生对 Map<String,Object> 进行批改的动作,导致局部键值对失落。
7、重复查看代码,确定 数据处理及预测 均为只读动作,不会对 Map<String,Object> 进行任何键值对的删改。
8、线索中断,排查一度搁置。
八、恍然大悟
1、借用 Arthas 进行报错察看:应用 watch 命令,依附 - e 参数(指定报错触发打印)以及 -x n 参数(打印办法入参及返回值数据层数)
2、依据察看,发现 Map<String,Object> 中失落的均为 向量 键值对。
3、找到问题:在排序申请流程图中,在主线程进行 分数归一化 时,会 fork 子线程异步做特色数据进行压缩写入 kafka。因为 Map<String,Object> 中存在大量的向量数据,导致保留数据过冗余的状况。此处的做法是先去除所有的 向量 数据,再进行保留。
4、然而该动作是产生在 数据处理及模型预测 后的,为何还会因为 Map<String,Object> 中删除键值对导致空指针异样呢。
5、此时狐疑是 数据处理及模型预测 阶段,多线程工作还没实现时,主线程曾经等待超时返回了。
九、验证想法
1、还是察看超时日志。
2、发现申请曾经返回后,才呈现空指针异样。那根本就能够验证以上的想法了。
十、问题解决
1、翻看应用的多线程框架(共事实现),主线程超时期待子线程工作。主线程超时返回后,没有告诉子线程工作勾销。所以才产生申请已返回,特色数据异步落地后,偶发性呈现晚到的空指针异样的状况。如下图,主线程超时返回后,只勾销主线程工作。
2、解决思路:主线程超时返回后,中断子工作(勾销子工作)。因为 java 的中断机制为软中断,个别是通过中断标记位进行线程中断合作的。当然 IO 或者 sleep 的中断由零碎帮咱们做了中断能够疾速返回。对于 CPU 密集型的工作,是须要使用者在 适合 的计算点上做标记位判断,确定是否已中断结束任务。以这种合作的形式达到中断。(此处可能有局部了解不当)
3、批改多线程框架,在主线程超时返回后,批改子线程中断标记位。
4、在计算流程中退出线程中断查看,如果被中断则提前结束计算。
十一、成果查看
1、批改发版后,空指针没再呈现。(其实该空指针是不影响排序后果,因为后果曾经是错的,该异样只是附带的虫子而已)
2、超时申请缩小,高峰期的超时数据缩小三分之一,50ms+ 的排序申请有显著缩小。
十二、复盘
1、主线程期待子工作的场景下,如果主线程超时返回了。需告诉子线程完结执行的工作。首先,主线程返回了,示意子工作已被抛弃。继续执行都是在做无用的计算,占用计算机资源。也不是说占着茅坑不拉屎,而是拉了没人要。应该尽量减少服务器资源用在没必要的耗费上。
2、该服务在 数据处理及预测 阶段应用的线程池队列为 SynchronousQueue,如果不理解 SynchronousQueue 的话能够简略了解为一个 0 长度的队列。工作进池子时必须要有线程进行对接。与惯例的 BlockingQueue 不同的是,工作在池子中不会沉积,对于工作的疾速响应比拟敌对。然而也因为如果没有闲暇的线程,则会不停创立线程直到 最高线程数 限度而触发抛弃策略。在该我的项目问题中,因为局部子工作在主线程返回后依然在执行。新的申请进来后,会呈现没有闲暇线程的状况,导致池子创立新线程接工作。对于 CPU 密集型工作来说,过多的线程数对服务来说是另一种累赘,毕竟线程切换的代价还是比拟大的。这就套入死循环了。(集体了解,如表述有误,还望斧正)
欢送关注 PerfMa 社区,举荐浏览
内存问题探微
JAVA 线上故障排查套路