记一次 .NET 某上市工业智造 CPU+内存+挂死 三高分析

一:背景

1. 讲故事

上个月有位朋友加wx告知他的程序有挂死现象,询问如何进一步分析,截图如下:

87e86a8711994c0983c5bd8870d6701e.png

看这位朋友还是有一定的分析基础,可能玩的少,缺乏一定的分析经验,当我简单分析之后,我发现这个dump挺有意思的, CPU,内存,挂死 三样全占,程序悲惨莫过于此。。。

既然找到我,我得想办法化解他的痛苦😄😄😄,由易到难我们逐一分析这三样都是因为什么原因所致?

二:三高分析

1. 挂死原因

根据 40+ 的dump分析经验,挂死大多是由于某种情况导致线程卡死,导致后续请求堆积在 threadpool 中,要想验证,可以使用 !tp 命令查看线程池队列。

0:000> !tp
CPU utilization: 81%
Worker Thread: Total: 65 Running: 65 Idle: 0 MaxLimit: 32767 MinLimit: 64
Work Request in Queue: 2831Unknown Function: 00007ffffcba1750  Context: 0000022ab04d4a58Unknown Function: 00007ffffcba1750  Context: 0000022ab03e4ce8...Unknown Function: 00007ffffcba1750  Context: 0000022ab825ec88Unknown Function: 00007ffffcba1750  Context: 0000022ab825a458Unknown Function: 00007ffffcba1750  Context: 0000022ab8266500Unknown Function: 00007ffffcba1750  Context: 0000022ab8268198Unknown Function: 00007ffffcba1750  Context: 0000022ab826cb00Unknown Function: 00007ffffcba1750  Context: 0000022ab8281578
--------------------------------------
Number of Timers: 0
--------------------------------------
Completion Port Thread:Total: 2 Free: 2 MaxFree: 128 CurrentLimit: 2 MaxLimit: 32767 MinLimit: 64

可以很明显的看到线程池队列有 2831 个任务堆积,这就导致新进来的请求无法得到处理,所以就出现了挂死现象,接下来就来看看这些线程都干嘛去了,为啥效率那么低,可以用 ~*e !clrstack 调出所有线程栈,截图如下:

17aafd0b5586d9991e89b68977b55c3e.png

扫了一遍后,发现有很多的 System.Net.HttpWebRequest.GetResponse() 方法,有经验的朋友应该知道,这又是一个经典的同步http请求过慢导致的程序处理不及的挂死,有些朋友可能好奇,能不能把网址给我扒出来,可以是可以,用 !dso 命令即可。

000000D2FBD3B840 0000023269e85698 System.Text.UTF8Encoding
000000D2FBD3B850 00000236e9dd2cb8 System.String    application/x-www-form-urlencoded
000000D2FBD3B870 0000023269e85698 System.Text.UTF8Encoding
000000D2FBD3B9A8 00000231aa221a38 System.String    uSyncAppxxx
000000D2FBD3B9B8 00000231aa201a70 System.String    VToken={0}&Vorigin={1}&QueryJson={2}
000000D2FBD3B9C0 00000231aa202200 System.String    http://xxx.xxx.com/API/xxx/BusinessCardFolder/Connector/xxx/GetPageList

我去,这url还是一个外网地址,🐂👃了,本身同步方式就慢,这地址更是雪上加霜哈。。。难怪不卡死😄

2. cpu爆高分析

从上面的 !tp 输出中也看出来了,当前 cpu = 81% ,那为什么会这么高呢?根据经验大概就是 lock锁,GC触发,死循环等情况,可以用排除法。

  1. 是 lock 锁吗?

可以用命令 !syncblk 看一下同步块表。

0:000> !syncblk
Index SyncBlock MonitorHeld Recursion Owning Thread Info  SyncBlock Owner212 0000023ef3cdd028            3         1 0000023ef40efa40 8d70 209   000002396ad93788 System.Object
-----------------------------
Total           297
CCW             3
RCW             4
ComClassFactory 0
Free            139

从输出看,lock 锁没什么问题,接下来用 !mlocks 命令查看下其他类型的锁,看看有没有什么新发现。

0:000> !mlocks
Examining SyncBlocks...
Scanning for ReaderWriterLock(Slim) instances...
Scanning for holders of ReaderWriterLock locks...
Scanning for holders of ReaderWriterLockSlim locks...
Examining CriticalSections...ClrThread  DbgThread  OsThread    LockType    Lock              LockLevel
------------------------------------------------------------------------------
...
0x49       209        0x8d70      thinlock    000002396ad9ba90  (recursion:0)
0x49       209        0x8d70      thinlock    000002396ad9baa8  (recursion:0)
0x49       209        0x8d70      thinlock    000002396ad9bac0  (recursion:0)
0x49       209        0x8d70      thinlock    000002396ad9bad8  (recursion:0)
0x49       209        0x8d70      thinlock    000002396ad9baf0  (recursion:0)
0x49       209        0x8d70      thinlock    000002396ad9bb08  (recursion:0)
0x49       209        0x8d70      thinlock    000002396ad9bb20  (recursion:0)
0x49       209        0x8d70      thinlock    000002396ad9bb38  (recursion:0)
0x49       209        0x8d70      thinlock    000002396ad9bb50  (recursion:0)
0x49       209        0x8d70      thinlock    000002396ad9bb68  (recursion:0)
0x49       209        0x8d70      thinlock    000002396ad9bb80  (recursion:0)
0xe        152        0x8e68      thinlock    0000023669f7e428  (recursion:0)
0x41       208        0x8fb4      thinlock    00000235e9f6e8d0  (recursion:0)
0x17       161        0x9044      thinlock    00000238ea94db68  (recursion:0)
0x16       159        0x911c      thinlock    000002392a03ed40  (recursion:0)
0x47       206        0x9264      thinlock    000002322af08e28  (recursion:0)

我去,发现有大量的 thinlock,而且 DbgThread=209 线程居然有 1000 +,截图如下:

bfb73c4a40e5d5004820692c48d1057a.png

有些朋友可能不知道什么叫 thinlock,简单来说,它就是一种耗cpu的内旋锁,类似 SpinLock,接下来随便抽一个对象,查看它的 !gcroot

0:000> !gcroot 000002396ad9ba48
Thread 2580:000000d2fb0bef10 00007ff806945ab3 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)rbp-80: 000000d2fb0bef50->  0000023769dd4008 System.Threading.Thread->  0000023269e776b8 System.Runtime.Remoting.Contexts.Context->  0000023269e773b8 System.AppDomain...->  0000023269ee1e00 System.Threading.TimerCallback->  0000023269ed2d30 System.Web.Caching.CacheExpires->  0000023269ed2c78 System.Web.Caching.CacheSingle->  0000023269ed2ce0 System.Collections.Hashtable->  000002372ab91d90 System.Collections.Hashtable+bucket[]->  00000239ab32fd10 System.Web.Caching.CacheEntry->  000002396ad93748 System.Collections.Concurrent.ConcurrentDictionary`2[[System.String, mscorlib],[xxx].Application.Entity.BaseManage.UserRelationEntity, xxx.Application.Entity]]->  00000239ab2a8248 System.Collections.Concurrent.ConcurrentDictionary`2+Tables[[System.String, mscorlib],[xxx.Application.Entity.BaseManage.UserRelationEntity, xxx.Application.Entity]]->  000002396ad96b80 System.Object[]->  000002396ad9ba48 System.Object

从输出信息看,这个 thinlock 来自于 ConcurrentDictionary 字典内部,接下来我们 dump 出这个字典,使用 !mdt 命令。

0:148> !mdt 000002396ad93748
000002396ad93748 (System.Collections.Concurrent.ConcurrentDictionary`2[[System.String, mscorlib],[xxx.Application.Entity.BaseManage.UserRelationEntity, xxx.Application.Entity]])m_tables:00000239ab2a8248 (System.Collections.Concurrent.ConcurrentDictionary`2+Tables[[System.String, mscorlib],[xxx.Application.Entity.BaseManage.UserRelationEntity, xxx.Application.Entity]])m_comparer:NULL (System.Collections.Generic.IEqualityComparer`1[[System.__Canon, mscorlib]])m_growLockArray:true (System.Boolean)m_keyRehashCount:0x0 (System.Int32)m_budget:0x213 (System.Int32)m_serializationArray:NULL (System.Collections.Generic.KeyValuePair`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]][])m_serializationConcurrencyLevel:0x0 (System.Int32)m_serializationCapacity:0x0 (System.Int32)
0:148> !mdt 00000239ab2a8248
00000239ab2a8248 (System.Collections.Concurrent.ConcurrentDictionary`2+Tables[[System.String, mscorlib],[xxx.Application.Entity.BaseManage.UserRelationEntity, xxx.Application.Entity]])m_buckets:0000023e9a2477e8 (System.Collections.Concurrent.ConcurrentDictionary`2+Node[[System.String, mscorlib],[xxx.Application.Entity.BaseManage.UserRelationEntity, xxx.Application.Entity]][], Elements: 543997)m_locks:000002396ad96b80 (System.Object[], Elements: 1024)m_countPerLock:00000239aa8472c8 (System.Int32[], Elements: 1024)m_comparer:0000023269e782b8 (System.Collections.Generic.GenericEqualityComparer`1[[System.String, mscorlib]])

从上面信息看,这个字典有 54.3 w 条记录,为啥这么大,而且还有 1024 个 lock,有点意思,我们扒一下源码看看。

c9e82ade2a6bacf72aba026e9b3108c8.png

从源码看,内部确实有一个 lock[] 数组,那到底是什么操作引发了遍历 locks[],要想寻找答案,可以在所有线程栈上寻找 ConcurrentDictionary 关键词。

OS Thread Id: 0x2844 (163)Child SP               IP Call Site
000000d2fb83abb8 00007ff80a229df8 [GCFrame: 000000d2fb83abb8] 
000000d2fb83aca0 00007ff80a229df8 [GCFrame: 000000d2fb83aca0] 
000000d2fb83acd8 00007ff80a229df8 [HelperMethodFrame: 000000d2fb83acd8] System.Threading.Monitor.Enter(System.Object)
000000d2fb83add0 00007ff80693ea56 System.Collections.Concurrent.ConcurrentDictionary`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].AcquireLocks(Int32, Int32, Int32 ByRef)
000000d2fb83ae20 00007ff806918ef2 System.Collections.Concurrent.ConcurrentDictionary`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].AcquireAllLocks(Int32 ByRef)
000000d2fb83ae60 00007ff8069153f9 System.Collections.Concurrent.ConcurrentDictionary`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].GetValues()
000000d2fb83aee0 00007ff7ae17d8ec xxx.Util.DataHelper.ToEnumerable[[System.__Canon, mscorlib],[System.__Canon, mscorlib]](System.Collections.Concurrent.ConcurrentDictionary`2<System.__Canon,System.__Canon>)
000000d2fb83af20 00007ff7ad125241 xxx.Application.Code.CacheHelper.GetCaches[[System.__Canon, mscorlib],[System.__Canon, mscorlib]](System.String)
000000d2fb83afa0 00007ff7ad12513b xxx.Application.Code.CacheHelper.GetCaches[[System.__Canon, mscorlib]](System.String)
000000d2fb83b000 00007ff7b10199e5 xxx.Application.Cache.CacheHelper.GetUserRelations()

从线程栈上看,发现了有近20处如上的代码,可以看出程序在调用 GetCaches 方法的时候触发了 ConcurrentDictionary 的lock锁从而卡住,接下来我们看一下 xxx.Application.Cache.CacheHelper.GetUserRelations() 源码到底做了什么?

public static IEnumerable<UserRelationEntity> GetUserRelations()
{return xxx.Application.Code.CacheHelper.GetCaches<UserRelationEntity>("xxx.BaseManage-UserRelation");
}protected static IEnumerable<T> GetCaches<T>(string cacheKeyName)
{return GetCaches<T, string>(cacheKeyName);
}private static IEnumerable<T> GetCaches<T, TKey>(string cacheKeyName)
{return GetConcurrentDictionaryCache<T, TKey>(cacheKeyName)?.ToEnumerable();
}public static IEnumerable<T> ToEnumerable<TKey, T>(this ConcurrentDictionary<TKey, T> dics)
{return dics.Values;
}

从源码逻辑看,程序每次调用缓存最终都会调用 dics.Values , 我很好奇它的框架逻辑是什么样的?截图如下:

3d7629340d7a37e50b52880c5a2d3f24.png

大家有没有发现,每次 dict.Values 时都要执行 1024 次 Monitor.Enter(locks[i], ref lockTaken);, 也就是 1024 次的内旋锁,这就是cpu高的一个关键因素。

3. 内存爆高原因

最后一个问题是内存为啥会爆高?细心的朋友应该会发现刚才那个 GetValues 中有一个奇怪的逻辑 ,我再贴一下代码:

private ReadOnlyCollection<TValue> GetValues()
{int locksAcquired = 0;try{AcquireAllLocks(ref locksAcquired);int countInternal = GetCountInternal();if (countInternal < 0){throw new OutOfMemoryException();}List<TValue> list = new List<TValue>(countInternal);for (int i = 0; i < m_tables.m_buckets.Length; i++){for (Node node = m_tables.m_buckets[i]; node != null; node = node.m_next){list.Add(node.m_value);}}return new ReadOnlyCollection<TValue>(list);}finally{ReleaseLocks(0, locksAcquired);}
}

有没有发现,每一次 GetValues 时都会生成一个 54.3w 大小的新List,请注意这个 list 是新生成的,不是 ConcurrentDictionary 上的引用,这就很坑了,每调用一次,LOH上就会来一个这么大的List,你说内存暴增不暴增???

三:总结

总的来说,这苦逼的三高有下面两个因素造成。

  1. 使用了同步的 HttpRequest 方式并使用了外网url导致程序挂死。

优化措施:使用异步方式

  1. 巨坑的 ConcurrentDictionary.Values 导致 内存,cpu 爆高。

我想很多朋友都没想到: ConcurrentDictionary.Values 有这么大的一个坑,这就让我联想起了线程不安全的 Dictionary.Values 是怎么做的?

public ValueCollection Values
{[__DynamicallyInvokable]get{if (values == null){values = new ValueCollection(this);}return values;}
}public sealed class ValueCollection
{public ValueCollection(Dictionary<TKey, TValue> dictionary){if (dictionary == null){ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary);}this.dictionary = dictionary;}
}

可以很明显的看到它并没有生成新的list,所以优化措施如下:

  1. 拒绝使用 ConcurrentDictionary.Values,采用 lock + Dictionary

  2. 如果硬要用 ConcurrentDictionary ,请将 Query 条件送下去,而不是使用 Values 做全量拉取再查询,减少内存无畏占用。

最后上一个小彩蛋,将分析结果给了这位朋友之后,朋友想让我上门分析,第一次遇到。。。太猝不及防啦🤣🤣🤣

7ba6a475faaad39397ce3ef81a293132.png

END

工作中的你,是否已遇到 ... 

1. CPU爆高

2. 内存暴涨

3. 资源泄漏

4. 崩溃死锁

5. 程序呆滞

等紧急事件,全公司都指望着你能解决...  危难时刻才能展现你的技术价值,作为专注于.NET高级调试的技术博主,欢迎微信搜索: 一线码农聊技术,免费协助你分析Dump文件,希望我能将你的踩坑经验分享给更多的人。

b4170fdd521365631ab23e245caafa2d.png

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

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

相关文章

60佳优秀的 Photoshop 网页制作教程【下篇】

Photoshop 作为网页设计利器&#xff0c;是网页设计师必备。曾经和大分享过几篇优秀的 Photoshop 网页制作教程&#xff0c;喜欢的人非常多。今天这篇文章继续向大家分享优秀的 Photoshop 网页制作教程。其实&#xff0c;网页设计并没有你想的那么难&#xff0c;相信看完这些教…

android studio 创建.9文件,自己使用Android studio创建.9(点9)图片

本来标题想写"Mac版Android studio创建.9图片"&#xff0c;但是感觉区别应该不大&#xff0c;因为只需要拖拽一下鼠标就行了&#xff0c;键盘以及快捷键都不用不上。这篇文章其实也是自己看了其他文章后的一点补充&#xff0c;并不是所有人都会有的问题。既然是想标为…

java stream 求和_谈谈Java任务的并行处理

作者&#xff1a;ksfzhaohui前言谈到并行&#xff0c;我们可能最先想到的是线程&#xff0c;多个线程一起运行&#xff0c;来提高我们系统的整体处理速度&#xff1b;为什么使用多个线程就能提高处理速度&#xff0c;因为现在计算机普遍都是多核处理器&#xff0c;我们需要充分…

现在的男生真的太惨了

1 做男生不容易啊2 这到底是被甲方怎样虐过3 暴风螺旋般的伤口撒盐式连环补刀来源&#xff1a;知乎4 说好的雪橇三傻呢&#xff1f;5 谁都别打扰我看电视6 正弦余弦的空间展示7 这是什么原理&#xff1f;你点的每个赞&#xff0c;我都认真当成了喜欢

SharePoint 2010 - 如何导入\导出WebPart

为了保存WebPart并进行重用&#xff0c;有时我们必须导出该WebPart并在其他页面中将其导入。下面是对此过程的描述。 导出一个WebPart 导出一个WebPart可以将WebPart设置保存成计算机本地文件。并不是所有的WebPart都支持这个选项&#xff0c;该页面的所有者可能禁用了这个选项…

OC----内存管理

任何继承了NSObject的对象&#xff0c;都需要内存管理&#xff0c;但是对基本数据类型无效&#xff08;不需要释放&#xff09;原理&#xff1a;每个对象内部都保存了一个与之相关联的整数&#xff0c;称为引用计数器当使用alloc、new或者copy创建一个对象时&#xff0c;对象的…

python赋值语句格式_Python赋值语句后逗号的作用分析

Python赋值语句后逗号的作用分析 本文实例讲述了Python赋值语句后逗号的作用。分享给大家供大家参考。具体分析如下&#xff1a; IDLE 2.6.2 >>> a 1 >>> b 2, >>> print type(a)>>> print type(b)>>> c [] >>> d […

2020年高考数学试题难吗?历史上最难数学卷不是2003!

全世界只有3.14 % 的人关注了爆炸吧知识不经历风雨怎能知道明天会死得更惨今天&#xff0c;高考拉开大帷幕&#xff0c;数学考试结束的一瞬间&#xff0c;在微博上简直是一片哀嚎今年&#xff0c;延期一个月高考让许多人预测&#xff0c;难度比起非典那一年或许将有增无减&…

深入浅出Docker(三):Docker开源之路

背景 Docker从一开始的概念阶段就致力于使用开源驱动的方式来发展&#xff0c;它的成功缘于国外成熟的开源文化氛围&#xff0c;以及可借鉴的社区运营经验。通过本文详细的介绍&#xff0c;让大家可以全面了解一个项目亦或者一项技术是如何通过开源的方式发展起来的。为了更准确…

android activity解耦,Android与设计模式:用单一职责原则为Activity解耦

一、什么是单一职责原则单一职责原则(SRP&#xff1a;Single responsibility principle)又称单一功能原则&#xff0c;其定义为&#xff1a;一个类&#xff0c;应该只有一个可以导致变化的原因。光看概念一、什么是单一职责原则单一职责原则(SRP&#xff1a;Single responsibil…

利用Azure communication service实现跟Teams同样等级的沟通协作应用

大家都知道Teams是一个非常强大的沟通协作平台&#xff0c;包括聊天&#xff0c;团队协作&#xff0c;会议&#xff0c;以及应用集成等功能&#xff0c;现在在全世界拥有数以亿计的商业用户。作为Teams平台的一个延伸&#xff0c;产品组把一些核心功能变成了一个公开的服务&…

一个入门的学生选课系统

大三的时候写的一个学生选课系统&#xff0c;WinForm 的使用的SQL数据库。主要有学生&#xff0c;老师两类用户&#xff0c;老师查看选择自己课程的学生&#xff0c;学生进行选课&#xff08;只能选5门课&#xff09;&#xff0c;登陆的时候要有登陆错误次数限制超过会锁定&…

世界上最诡异的画,到底为何让无数人闻风丧胆?

全世界只有3.14 % 的人关注了爆炸吧知识也许只看到标题 &#xff0c;你一定想不到&#xff0c;传说中世界上最诡异的画是这张。《雨中女郎》这是乌克兰画家斯韦特兰娜捷列茨&#xff0c;绘画生涯中重要的一个作品。也许你会说哪里诡异呢&#xff1f;其实很多人看完这幅画以后&a…

pixel android8,谷歌Pixel 2更多信息:安卓8.1

给HTC手机团队注入11亿美元现金后&#xff0c;已经显示了谷歌要把硬件做下去的决心&#xff0c;特别是自家的Pixel系列&#xff0c;而10月4日新一代Pixel手机将正式来袭。据Android Police最新报道称&#xff0c;谷歌新的Pixel 2代手机将会提供一个新的功能名叫Always-On Song …

tftp 服务器 ip_360Stack裸金属服务器部署实践

女主宣言裸金属特性是一种将物理设备作为资源提供给租户的云计算服务&#xff0c;租户通过该服务可申请、管理和配置相应的物理设备资源&#xff0c;本文将介绍360Stack裸金属服务器的部署实践。PS&#xff1a;丰富的一线技术、多元化的表现形式&#xff0c;尽在“360云计算”&…

设计模式:状态模式

一、引子 状态模式自身结构非常简单——前面刚刚介绍了几个结构比较简单的设计模式&#xff0c;和他们 一样&#xff0c;状态模式在具体实现上留下了可变换的余地。我前面已经介绍过它的孪生兄妹策略模 式了&#xff0c;大家可以两者比较着阅读。本文将会讨论两者的区别。 二、…

Google Chrome 总提示flash插件过期,用命令行模式解决

目标那改成&#xff1a;"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --args --allow-outdated-plugins chrome老提示插件已被阻止&#xff1a; chrome://plugins/ 进入插件 选择始终允许转载于:https://www.cnblogs.com/as3lib/p/4396418.html

Windows下的gcc/gc++编译环境配置

最近有很多算法设计的网络大赛&#xff0c;其中大部分的C/C都是采用Linux下的GCC/G编译器。配置GCC编译器大概有这几种途径&#xff1a;装Linux系统、装Linux虚拟机或者在Windows环境下模拟Linux编译环境。这里谈谈有关Windows下模拟Linux编译环境的方法。 说到Windows下模拟Li…

程序员苦逼了6年,选择在街边做鸭......

1 从入门到转行可太真实了▼2 干干巴巴&#xff0c;麻麻赖赖给我盘&#xff01;▼3 这位高人一看就是本科蓝翔&#xff0c;新东方硕博连读▼4 主要还是男人比较自信▼5 逻辑清晰&#xff0c;思维缜密▼6 妈妈&#xff0c;我就想听好听的▼7 如过全网都没有广告说不定会有…

Resharper 和 Rider 的奇淫技巧,你知道多少?

.NET 开发中最令人印象深刻的生产力工具之一是ReSharper[1]。每次发布时&#xff0c;我都对它的功能感到震惊。不要误会我的意思&#xff0c;我喜欢 Visual Studio&#xff0c;而且它也变得越来越好。但每当我认为 Visual Studio 迎头赶上时&#xff0c;我就会发现一些令人惊叹…