记一次 .NET 某汽贸店 CPU 爆高分析

news2024/11/25 7:06:31

一:背景

1. 讲故事

上周有位朋友在 github 上向我求助,说线程都被卡住了,让我帮忙看下,截图如下:

时隔两年 终于有人在上面提 Issue 了,看样子这块以后可以作为求助专区来使用,既然来求助,必须得免费帮忙解决,从朋友这边拿到 dump 之后,接下来就可以分析了。

二:WinDbg 分析

1. 为什么有大量线程卡住

在朋友的文案描述中可以看到有一段 !syncblk 输出,格式有点乱,再用这个命令跑一下看看。


0:000> !syncblk
Index SyncBlock MonitorHeld Recursion Owning Thread Info  SyncBlock Owner
49755 0000000013b666b8          602         0 0000000000000000     none    00000001bfc5f0b0 System.ComponentModel.PropertyDescriptorCollection
-----------------------------
Total           207053
CCW             22
RCW             4
ComClassFactory 0
Free            0

从卦中看,貌似不是一个 dump,不过都有如下几个疑点。

1) MonitorHeld 为什么是偶数?

熟悉同步块表的朋友都知道,持有+1,等待+2,而这个居然是一个偶数,也就表明那个 +1 的线程已经退出了临界区,同时等待线程此时还没有进来,正处在这么一个时间差内。

2) 持有线程信息 为什么不显示?

正因为持有线程已经退出临界区,所以看不到持有线程信息,往细处说就是 AwareLock 类下的 m_HoldingThread 字段被抹掉了,同时用 AwareLock::LockState::InterlockedUnlock 函数解锁了 LockState 中的掩码,前者在 0000000013b666b8+0x8 的位置,后者在 0000000013b666b8+0x4 的位置。


0:000> dp 0000000013b666b8
00000000`13b666b8  00000000`0000025a 00000000`00000000

以多年的治疗经验来看,这是经典的 lock convoy 现象,这个病还会因为高频的上下文切换导致 cpu 爆高。

2. CPU真的爆高吗?

要验证 cpu 是否爆高,可以用 !tp 命令验证。


0:000> !tp
CPU utilization: 100%
Worker Thread: Total: 336 Running: 336 Idle: 0 MaxLimit: 32767 MinLimit: 16
Work Request in Queue: 915
    Unknown Function: 000007fef97715cc  Context: 0000000026d95968
    Unknown Function: 000007fef97715cc  Context: 0000000026d98b78
    Unknown Function: 000007fef97715cc  Context: 0000000026d9bd88
    ...
    Unknown Function: 000007fef97715cc  Context: 000000002aab0368
    Unknown Function: 000007fef97715cc  Context: 000000001d62ed00
--------------------------------------
Number of Timers: 0
--------------------------------------
Completion Port Thread:Total: 1 Free: 1 MaxFree: 32 CurrentLimit: 1 MaxLimit: 1000 MinLimit: 16

从卦中看,果然是 cpu 爆高,而且还堆积了 915 个待处理的任务,既然有堆积,那就说明下游处理不及,接下来用 ~*e !clrstack 观察下所有的线程,整理后如下:


0:000> ~*e !clrstack

OS Thread Id: 0x4228 (404)
        Child SP               IP Call Site
000000002417b8d8 00000000771b9e3a [HelperMethodFrame: 000000002417b8d8] System.Threading.Monitor.Enter(System.Object)
000000002417b9d0 000007fe9a6c2bd9 System.ComponentModel.PropertyDescriptorCollection.Find(System.String, Boolean)
000000002417ba40 000007fe9a6c26fc System.Data.XSDSchema.SetProperties(System.Object, System.Xml.XmlAttribute[])
000000002417bab0 000007fe9a6c7b0f System.Data.XSDSchema.HandleElementColumn(System.Xml.Schema.XmlSchemaElement, System.Data.DataTable, Boolean)
000000002417bba0 000007fe9a6c71fa System.Data.XSDSchema.HandleParticle(System.Xml.Schema.XmlSchemaParticle, System.Data.DataTable, System.Collections.ArrayList, Boolean)
000000002417bc70 000007fe9a6c6bf5 System.Data.XSDSchema.HandleComplexType(System.Xml.Schema.XmlSchemaComplexType, System.Data.DataTable, System.Collections.ArrayList, Boolean)
000000002417bcf0 000007fe9a6c3edf System.Data.XSDSchema.InstantiateTable(System.Xml.Schema.XmlSchemaElement, System.Xml.Schema.XmlSchemaComplexType, Boolean)
000000002417bde0 000007fe9a6c383a System.Data.XSDSchema.HandleTable(System.Xml.Schema.XmlSchemaElement)
000000002417be60 000007fe9a6bf0d8 System.Data.XSDSchema.LoadSchema(System.Xml.Schema.XmlSchemaSet, System.Data.DataSet)
000000002417bee0 000007fe9a698c25 System.Data.DataSet.ReadXmlSchema(System.Xml.XmlReader, Boolean)
000000002417bf80 000007fe9a915b45 System.Data.DataTable.DeserializeDataTable(System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext, Boolean, System.Data.SerializationFormat)
000000002417bfe0 000007fe9a915981 System.Data.DataTable..ctor(System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext)
...
000000002417ce40 000007fe9a781af3 xxx.Cache.Utils.MemcachedProvider.GetDataTable(System.String)
...
000000002417d5a0 000007fe9a7743a9 System.Web.Mvc.ControllerActionInvoker.InvokeAction(System.Web.Mvc.ControllerContext, System.String)
000000002417d600 000007fe9a773110 System.Web.Mvc.Controller.ExecuteCore()
...
000000002417e380 000007fe9a8d5aae DomainNeutralILStubClass.IL_STUB_PInvoke(IntPtr, System.Web.RequestNotificationStatus ByRef)
000000002417e440 000007fe9a8c0231 System.Web.Hosting.PipelineRuntime.ProcessRequestNotificationHelper(IntPtr, IntPtr, IntPtr, Int32)
000000002417e5f0 000007fe9a8bf854 System.Web.Hosting.PipelineRuntime.ProcessRequestNotification(IntPtr, IntPtr, IntPtr, Int32)

从卦中信息看,当时有一个 http 请求,然后在 MemCached 中获取一条数据再将获取到的数据转成 DataTable 的过程中被锁等待。

接下来检索下 GetDataTable 方法高达 336 处,也就表示当前有 336 个线程在抢这个锁,高频的进入和退出导致的 CPU 爆高。

3. 问题代码在哪里

仔细观察代码会发现将 Byte[] 转成 DataTable 内部的逻辑不简单,会大量使用反射,然后在反射中又会大量的使用锁,接下来仔细观察调用栈,最终定位到了上层 GetxxxSite() 函数调用,简化后的代码如下:


public static DataTable GetxxxSite()
{
    string text = HttpContext.Current.Request.Url.ToStr();
    string xxxDomain = GetxxxDomain();

    DataTable dataTable = CacheHelper.GetDataTable("xxxSite_" + xxxDomain, -1, 0);

    if (dataTable.HasRecord())
    {
        return dataTable;
    }

    dataTable = GetxxxSiteInfo(xxxDomain);

    if (dataTable.HasRecord())
    {
        _xxxSite = dataTable;
        CacheHelper.AddCache("xxxSite_" + xxxDomain, dataTable, -1, 0);
    }
    return dataTable;
}

有朋友可能有疑问,这代码怎么可能会出现 lock convoy 呢? 你可以这么想一想,本来 CacheHelper.GetDataTable 函数只需 10ms 就能执行完,但需要执行 100 次 lock,然而在这 10ms 之内,又来了一个请求,也需要 100 次 lock,因为反序列成 DataTable 底层是共享的 lock,导致上一次 100 的lock 和这次 100 的 lock 要进行锁竞争,导致大家的执行时间由 10ms 延长到了 15ms。

可气的是,这 15ms 之间又来了100个线程,导致 100*100 =1000 个锁竞争,执行时间由原来的 15ms 延长到了 1min,以此类推,最终导致本该 10ms 执行的函数,结果要几分钟才能执行完。

知道了原理之后,缓解措施就简单了。

  1. 在 CacheHelper.GetDataTable 加串行锁

这个只能治标,也是最简单的方式,可以将原来的 平方锁 降成 线性锁,来缓解锁竞争,CPU爆高的问题也就迎刃而解。

  1. 不要转成 DataTable

本质的问题是转成 DataTable 的过程中有大量的锁,如果直接转成 List 自然就绕过去了,在现代编程中也是极力推荐的。

三:总结

这次 CPU 爆高事故,主要就是将 byte[] 转成 DataTable 的过程中出现的 lock convoy 现象,治标治本的方案也说了,希望给后来人少踩一些坑吧。

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

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

相关文章

gulimall-商城业务-商品上架

商城业务 前言一、商品上架1.1 商品 Mapping1.2 商品信息保存到es1.3 es数组的扁平化处理1.4 构造基本数据 前言 本文继续记录B站谷粒商城项目视频 P128-135 的内容,做到知识点的梳理和总结的作用,接口文档地址:gulimall接口文档 一、商品上…

面了个字节拿 30K 出来的测试,让我见识到了什么是测试的天花板

人人都有大厂梦,对于软件测试人员来说,BATJ 为首的一线互联网公司肯定是自己的心仪对象,毕竟能到这些大厂工作,不仅薪资高待遇好,而且能力技术都能够得到提升,最关键的是还能够给自己镀上一层金&#xff0c…

jenkins pipline 拉取git历史版本

声明,本文是基于:jenkins流水线(jenkinsfile)详解,保姆式教程_我认不到你的博客-CSDN博客,以下内容介绍通过 Commit ID 拉取 git 历史版本 Commit ID (节点号)是什么?&a…

5G配电网专用工业级路由器(电力紧凑型DTU)-智慧电力物联网

随着近年来智能电网的快速发展,它实现了电力系统的监控、数据、电能的统一化智能管理,通过与5G技术结合,助力构建高可靠、高灵活、高效率的配电网络。 5G网络技术具备低时延传输的特点,满足配电网安全、控制的苛刻要求&#xff0…

软件测试面试一定要准备的7个高频面试题(附答案,建议收藏)

收集了很多人在面试时的面试题后,我特意整理出了7个高频出现的面试题,一起来看看。 高频问题1:请自我介绍下? 高频问题2:请介绍下最近做过的项目? 高频问题3:请介绍下你印象深刻的bug&#xff1…

Android网络握手失败问题分析

问题场景 调用某功能云端接口请求,保存如下信息:Web服务通信期间握手期间远程主机关闭连接 javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake分析 由于同一份代码不同机器有的能调用成功,有的调用失…

C#通用的二进制转化为float和double方法

上一篇,我们将32位二进制【或4个字节】转化为float【Real】小数,这次我们使用通用的方法进行二进制转化。 C#中将32位二进制转换为float【Real】十进制类型_斯内科的博客-CSDN博客 二进制转化float(double)方法: //单精度浮点数对应32位 /…

挖出电商店铺详情数据-API接口分享

在今天的互联网时代,电商平台已经成为了我们生活中不可或缺的一部分。淘宝作为全国最大的电商平台之一,其商品信息也越来越丰富,但是如果你想开发一款能够帮助用户购物的应用程序,就必须获取到淘宝的API接口,才能让你的…

【Webpack】前端工程自动化 - require.context实现模块自动化导入

一、介绍 require.context 是基于 webpack 的一个的 api,主要用来实现模块的自动化导入在前端工程中,如果遇到一个文件需要引入很多模块的情况,可以使用这个apirequire.context 会遍历文件夹中的指定文件,然后自动导入&#xff0…

记一次redis主从切换导致的数据丢失与陷入只读状态故障

背景 最近一组业务redis数据不断增长需要扩容内存,而扩容内存则需要重启云主机,在按计划扩容升级执行主从切换时意外发生了数据丢失与master进入只读状态的故障,这里记录分享一下。 业务redis高可用架构 该组业务redis使用的是一主一从&am…

从“0”到“1”!低代码开发和云计算的碰撞,引领数字化转型浪潮!

随着互联网技术的飞速发展,数字化转型已经成为企业转型升级的必经之路。而在数字化转型的过程中,云计算和低代码开发逐渐成为新技术的代表,为企业提供更高效、更灵活的技术支持,让企业赢得更大的竞争优势。 云计算的发展 云计算是…

三阶段项目

DHCP分配不到冲突地址 需要重启 再分配 用这个命令 reset ip pool name vlan40 all ospf: 建立邻居表:报文:hello报文 状态:down int 2-way 选举DR 同步数据库:报文:DD-LSR-LSU-LSACK 状态&#xff…

C语言-printf打印%*s、%.*s与%-.*s的区别

一、简介 在平时的使用中,会经常使用到printf进行打印,而最长使用的方式是printf("%s",string)进行打印。但是有个问题,如果string结尾不是0。那么printf会继续打印,直到遇到0为止。这样就会有内存溢出的风险。显然&…

(三)ArcGIS空间数据的转换与处理——栅格数据变换

ArcGIS空间数据的转换与处理——栅格数据变换 目录 ArcGIS空间数据的转换与处理——栅格数据变换 1.地理配准2.平移3.扭曲4.旋转5.翻转6.重设比例尺7.镜像 数据变换是指对数据进行诸如放大、缩小、翻转、移动、扭曲等几何位置、形状和方位的改变等操作。对于 栅格数据的相应操…

类和对象 - 练习题(C++)

目录 1、求123...n 题目链接: 题目: 题目描述: 解题思路: 代码: 2 计算日期到天数转换 题目链接: 题目: 题目描述: 解题思路: 代码: 3 日期差值 题目链接&…

人工智能学习07--pytorch18--目标检测:Faster RCNN源码解析(pytorch)

参考博客: https://blog.csdn.net/weixin_46676835/article/details/130175898 VOC2012 1、代码的使用 查看pytorch中的faster-rcnn源码: 在pytorch中导入: import torchvision.models.detection.faster_rcnn即可找到faster rcnn所实现的源…

NIFI分页获取Postgresql数据到Hbase中_实际操作---大数据之Nifi工作笔记0049

首先看一下整体流程,可以看到这里用的PutHbaseJson处理器,把数据导入到 hbase中的 注意这里也可以使用PutSql导入数据,通过phoenix的jdbc驱动,然后把数据利用PutSql处理器导入到 Hbase中,但是我这里的时候报错了,然后一直没解决,所以最后用了PutHbaseJson处理器,把数据存入到…

【LinuxShell】linux防火墙之SNAT策略和DNAT策略

文章目录 前言一、SANT策略1.SNAT策略概述2.SNAT的典型应用环境3.SNAT的工作原理4.SNAT策略的应用SNAT转换前提条件SNAT策略打开方式SNAT实验过程 5.知识扩展 二、DNAT策略1.DNAT策略概述2.DNAT的典型应用环境3.DNAT的工作原理4.DNAT策略的应用DNAT转换前提条件DNAT地址转换方式…

linux管道通信原理

管道,通常指无名管道,是 UNIX 系统IPC(InterProcess Communication)最古老的形式。 1、特点: 1.它是半双工的(即数据只能在一个方向上流动) ,具有固定的读端和写端 2.它只能用于具有亲缘关系的进程之间的通信(也是子进程或者兄弟进…