记一次 .NET 某注塑模具系统 CPU爆高分析 澳五机器人

admin5个月前澳五机器人101


一:背景

1. 讲故事

上个月,一位做工业智能制造的朋友找到我,说他们车间配套的注塑模具人机交互监控系统,上线一周就多次出现偶发性CPU爆高,每次持续时间从三五分钟到十几分钟不等,车间中控大屏会出现卡顿,模具状态数据刷新延迟,已经影响到生产线的正常监测。朋友团队有一定的调试基础,折腾了一周没定位到根因,特意找过来帮忙分析。

这次出问题的系统是部署在工业控制计算机上的,硬件配置不算高,只有4核8G的配置,平时只运行监控程序和边缘计算模块,正常负载下CPU使用率一般维持在15%以内,爆高的时候直接冲到95%以上,内存倒是没什么异常。闲话不多说,直接上WinDbg分析。

二:WinDbg 分析

1. 先确认CPU是不是真的爆高

分析问题的第一步永远是用数据说话,不能只听业务方描述,我们先抓了完整的dump文件,载入WinDbg之后先用!tp命令观察线程池状态:

0:000> !tp
CPU utilization: 92%
Worker Thread: Total: 16 Running: 16 Idle: 0 MaxLimit: 2048 MinLimit: 4
Work Request in Queue: 0
--------------------------------------
Number of Timers: 3
--------------------------------------
Completion Port Thread:Total: 3 Free: 3 MaxFree: 8 CurrentLimit: 3 MaxLimit: 1000 MinLimit: 4

再用!cpuid确认一下核心数:

0:000> !cpuid
CP  F/M/S  Manufacturer     MHz
0  6,10,9  <unavailable>  2100
1  6,10,9  <unavailable>  2100
2  6,10,9  <unavailable>  2100
3  6,10,9  <unavailable>  2100

确实四个核心都被拉满了,CPU利用率92%,而且线程池里16个工作线程全部都在跑,没有空闲线程,问题定位思路一下子就清晰了:所有工作线程都被占满了,接下来看看这些线程现在都在做什么。

2. 遍历所有线程看调用栈

要找CPU爆高的根因,最直接的方式就是把所有线程的托管调用栈全部打出来,用命令~*e !clrstack,很快就发现了异常:几乎所有线程的调用栈都停在了同一个方法上,简化之后如下:

OS Thread Id: 0x2a34 (12)
Child SP       IP Call Site
00000012F843C180 00007ffd88a37215 xxx.Injection.Monitor.DataProcessor+<ProcessRealTimeData>d__18.MoveNext()
00000012F843C360 00007ffd88a2f9e2 System.Threading.ThreadPoolWorkQueue.Dispatch()
00000012F843C410 00007ffd9138e3ee System.Threading.PortableThreadPool+WorkerThread.WorkerThreadStart()

所有工作线程都卡在了实时数据处理的ProcessRealTimeData方法里,接下来我们看看这个方法的IL,再反编译出代码看看具体逻辑,很快就找到了问题:方法里有一段循环处理模腔压力曲线的代码,存在一个隐性的死循环条件。

系统每秒钟会从注塑机采集200组压力和温度数据,每次采集后都会调用数据归一化方法做预处理,归一化方法里有一段代码是用来裁剪超出边界的异常数据:

while (currentIndex < dataLength)
{
   if (data[currentIndex] > upperBound || data[currentIndex] < lowerBound)
   {
       RemoveAt(data, currentIndex);
   }
   currentIndex++;
}

问题就出在RemoveAt这里:当删除当前索引位置的元素后,原数组长度减一,但是代码还是直接给currentIndex加一,本来逻辑没问题,但是如果连续两个异常数据出现在数组末尾,删除第一个后,currentIndex增加,刚好等于新的数组长度,循环就直接退出了,看起来没错对不对?

那什么时候会死循环呢?如果数组最后一个元素本身就是异常数据,删除之后,数组长度变成currentIndex,这时候循环条件currentIndex < dataLength依然成立吗?不对,原来的dataLength是进循环前就缓存好的,删除元素后没有更新dataLength!所以不管删了多少个元素,dataLength一直是原始长度,currentIndex一直增加,循环永远满足条件,直接进入死循环占满CPU。

3. 为什么是偶发性爆高?

为什么这个问题不是每次都出现,而是偶发性的?其实也很好解释:只有当采集到的实时数据末尾刚好有异常数据的时候,才会触发这个死循环,如果没有异常数据,或者异常数据不在末尾,程序就能正常运行。而注塑机的异常数据本身就是偶发的,只有压力突变超出阈值的时候才会出现,所以对应的CPU爆高也是偶发性的,这也是朋友团队一周都没定位到根因的原因。

三:问题解决与总结

1. 问题修复

找到根因之后修复就非常简单了,两个修改方式:要么把dataLength换成data.Length,每次循环都拿最新的数组长度;要么删除元素后currentIndex不增加,因为下一个元素已经移到当前索引位置了。修改后的代码:

int currentIndex = 0;
while (currentIndex < data.Count)
{
   if (data[currentIndex] > upperBound || data[currentIndex] < lowerBound)
   {
       data.RemoveAt(currentIndex);
   }
   else
   {
       currentIndex++;
   }
}

修复之后上线观察了两周,再也没有出现过CPU爆高的问题,监控系统运行稳定。

2. 总结

这次的CPU爆高其实是一个非常典型的低级错误导致的线上问题,本质就是缓存数组长度的时候没有考虑到数组长度动态变化的场景,刚好触发了边界条件进入死循环。这里也给大家做两个总结:

第一:处理动态修改长度的集合循环,一定要特别注意循环条件和索引计数,非常容易出现边界错误引发死循环,占满CPU。

第二:工业软件的偶发性问题排查,一定要抓到爆高时刻的dump文件,通过遍历所有线程的调用栈,很快就能定位到卡死的方法,比瞎猜瞎试效率高很多。

这次问题也算给朋友团队上了一课,边界条件的测试一定要覆盖到极端场景,不然一个小小的错误就会导致整个系统不可用,影响生产线的正常运行。 


澳五机器人 澳八机器人 河内机器人 加拿大机器人 花开月下机器人 朱雀机器人 速飞机器人 名爵机器人 飞天机器人 BV机器人 涂六飞单机器人 美猴王机器人 大富豪机器人 速讯机器人 五球助手 十球助手

相关文章

澳五机器人 如何基于Koog构建生产级多模型容灾方案

如何基于Koog构建生产级多模型容灾方案基于 Koog 构建生产级多模型容灾方案依托 Koog 原生的 MultiLLMPromptExecutor 多模型执行器抽象,结合断路器、健康探测、智能路由三...

澳五机器人 JuiceFS+FoundationDB企业级统一存储方案

结合你此前关注的云原生架构、分布式存储选型的相关背景,这套基于腾讯云资源搭建的JuiceFS+FoundationDB企业级统一存储方案,核心是依托云资源的原生能力,打造高可靠、多协议兼容的分布式存储...

澳五机器人 实验性DAG流程审计Skill设计与实现

一、背景与需求分析在数据工程和工作流管理领域,有向无环图(DAG)被广泛应用于任务调度、数据流水线和复杂业务流程编排。随着DAG规模的扩大和复杂度的提升,流程审计成为保障系统可靠性、合规性和可追溯性的...

统计学WebApp实验体系:从概率直觉到AI赋能的能力进阶(二)

在统计学WebApp实验体系的进阶阶段,我们将从基础的概率认知与统计分析,迈向AI赋能的数据分析实践,实现从“理解数据”到“应用数据”的能力跨越。一、AI辅助统计建模:从手动拟合到智能选择统计建模是数...

澳五机器人 AI辅助开发相关的技术实践,把传统黄历吉时逻辑和代码合并的工程特性结合

结合你此前关注的Git协作、AI辅助开发相关的技术实践,把传统黄历吉时逻辑和代码合并的工程特性结合,就能得到适配GitHub项目的「合并代码良辰吉时」参考方案,既贴合趣味玩法也兼顾工程稳定性:1. 基...

OpenClaw imageModel配置全流程指南

一、imageModel的核心价值与配置必要性在OpenClaw的多模态交互体系中,imageModel是专门针对视觉内容处理的核心组件,与主对话模型(model)相互独立却又协同工作。主对话模型通常...