记一次 .NET 某电商交易平台Web站 CPU爆高分析

一:背景

1. 讲故事

已经连续写了几篇关于内存暴涨的真实案例,有点麻木了,这篇换个口味,分享一个 CPU爆高 的案例,前段时间有位朋友在 wx 上找到我,说他的一个老项目经常收到 CPU > 90% 的告警信息,挺尴尬的。

既然找到我,那就用 windbg 分析呗,还能怎么办。

二:windbg 分析

1. 勘探现场

既然说 CPU > 90%,那我就来验证一下是否真的如此?


0:359> !tp
CPU utilization: 100%
Worker Thread: Total: 514 Running: 514 Idle: 0 MaxLimit: 2400 MinLimit: 32
Work Request in Queue: 1Unknown Function: 00007ff874d623fc  Context: 0000003261e06e40
--------------------------------------
Number of Timers: 2
--------------------------------------
Completion Port Thread:Total: 2 Free: 2 MaxFree: 48 CurrentLimit: 2 MaxLimit: 2400 MinLimit: 32

从卦象看,真壮观,CPU直接被打满,线程池里 514 个线程也正在满负荷奔跑,那到底都奔跑个啥呢?首先我得怀疑一下这些线程是不是被什么锁给定住了。

2. 查看同步块表

观察锁情况,优先查看同步块表,毕竟大家都喜欢用 lock 玩多线程同步,可以用 !syncblk 命令查看。


0:359> !syncblk
Index SyncBlock MonitorHeld Recursion Owning Thread Info  SyncBlock Owner53 000000324cafdf68          498         0 0000000000000000     none    0000002e1a2949b0 System.Object
-----------------------------
Total           1025
CCW             3
RCW             4
ComClassFactory 0
Free            620

我去,这卦看起来很奇怪, MonitorHeld=498 是什么鬼???教科书上都说: owner + 1 , waiter + 2,所以你肉眼看到的总会是一个奇数,那偶数又是个啥意思?查了下神奇的 StackOverflow,大概总结成如下两种情况:

  • 内存损坏

这种情况比中彩还难,我也坚信不会走这种天罗运。。。

  • lock convoy (锁护送)

前段时间我分享了一篇真实案例:记一次 .NET 某旅行社Web站 CPU爆高分析 ,它就是因为 lock convoy 造成的 CPU 爆高,果然世界真小,又遇到了。。。为了方便大家理解,我还是把那张图贴上吧。

看完这张图你应该就明白了,一个线程在时间片内频繁的争抢锁和上下文切换,所以就很容易的出现一个持有锁的线程刚退出,那些等待锁的线程此时还没有一个真正的持有锁,刚好抓到的dump就是这么一个时间差,换句话说,当前的 498 全部是 waiter 线程的计数,也就是 249 个 waiter 线程,接下来就可以去验证了,把所有线程的线程栈调出来,再检索下 Monitor.Enter 关键词。

从图中可以看出当前有 220 个线程正卡在 Monitor.Enter 处,貌似丢了29个,不管了,反正大量线程卡住就对了,从堆栈上看貌似是在 xxx.Global.PreProcess方法中设置上下文后卡住了,为了满足好奇心,我就把问题代码给导出来。

3. 查看问题代码

还是用老命令 !ip2md + !savemodule


0:359> !ip2md 00007ff81ae98854
MethodDesc:   00007ff819649fa0
Method Name:  xxx.Global.PreProcess(xxx.JsonRequest, System.Object)
Class:        00007ff81966bdf8
MethodTable:  00007ff81964a078
mdToken:      0000000006000051
Module:       00007ff819649768
IsJitted:     yes
CodeAddr:     00007ff81ae98430
Transparency: Critical
0:359> !savemodule 00007ff819649768 E:\dumps\PreProcess.dll
3 ps in file
p 0 - VA=2000, VASize=b6dc, FileAddr=200, FileSize=b800
p 1 - VA=e000, VASize=3d0, FileAddr=ba00, FileSize=400
p 2 - VA=10000, VASize=c, FileAddr=be00, FileSize=200

然后用 ILSpy 打开问题代码,截图如下:

尼玛,果然每个 DataContext.SetContextItem() 方法中都有一个 lock 锁,完美命中 lock convoy

4. 真的就这样结束了吗?

本来准备汇报了,但想着500多个线程栈都调出来了,闲着也是闲着,干脆扫扫看吧,结果我去,意外发现有 134 个线程卡在 ReaderWriterLockSlim.TryEnterReadLockCore 处,如下图所示:

从名字上可以看出,这是一个优化版的读写锁:ReaderWriterLockSlim, 真的很好奇,再次导出问题。

internal class LocalMemoryCache : ICache
{private string CACHE_LOCKER_PREFIX = "xx_xx_";private static readonly NamedReaderWriterLocker _namedRwlocker = new NamedReaderWriterLocker();public T GetWithCache<T>(string cacheKey, Func<T> getter, int cacheTimeSecond, bool absoluteExpiration = true) where T : class{T val = null;ReaderWriterLockSlim @lock = _namedRwlocker.GetLock(cacheKey);try{@lock.EnterReadLock();val = (MemoryCache.Default.Get(cacheKey) as T);if (val != null){return val;}}finally{@lock.ExitReadLock();}try{@lock.EnterWriteLock();val = (MemoryCache.Default.Get(cacheKey) as T);if (val != null){return val;}val = getter();CacheItemPolicy cacheItemPolicy = new CacheItemPolicy();if (absoluteExpiration){cacheItemPolicy.AbsoluteExpiration = new DateTimeOffset(DateTime.Now.AddSeconds(cacheTimeSecond));}else{cacheItemPolicy.SlidingExpiration = TimeSpan.FromSeconds(cacheTimeSecond);}if (val != null){MemoryCache.Default.Set(cacheKey, val, cacheItemPolicy);}return val;}finally{@lock.ExitWriteLock();}}

看了下上面的代码大概想实现一个对 MemoryCache 的 GetOrAdd 操作,而且貌似为了安全起见,每一个 cachekey 都配了一把 ReaderWriterLockSlim,这逻辑就有点奇葩了,毕竟 MemoryCache 本身就带了实现此逻辑的线程安全方法,比如:


public class MemoryCache : ObjectCache, IEnumerable, IDisposable
{public override object AddOrGetExisting(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null){if (regionName != null){throw new NotSupportedException(R.RegionName_not_supported);}CacheItemPolicy cacheItemPolicy = new CacheItemPolicy();cacheItemPolicy.AbsoluteExpiration = absoluteExpiration;return AddOrGetExistingInternal(key, value, cacheItemPolicy);}
}

5. 用 ReaderWriterLockSlim 有什么问题吗?

哈哈,肯定有很多朋友这么问?????????????,确实,这有什么问题呢?首先看一下 _namedRwlocker 集合中目前到底有多少个 ReaderWriterLockSlim ? 想验证很简单,上托管堆搜一下即可。


0:359> !dumpheap -type System.Threading.ReaderWriterLockSlim -stat
Statistics:MT    Count    TotalSize Class Name
00007ff8741631e8    70234      6742464 System.Threading.ReaderWriterLockSlim

可以看到当前托管堆有 7w+ 的 ReaderWriterLockSlim,这又能怎么样呢???不要忘啦, ReaderWriterLockSlim 之所以带一个 Slim ,是因为它可以实现一段时间内的用户态 自旋,那 自旋 就得吃一点CPU,如果再放大几百倍?CPU能不被抬起来吗?

三:总结

总的来说,这个 Dump 所反应出来的 CPU打满 有两个原因。

  • lock convoy 造成的频繁争抢和上下文切换给了 CPU 一顿暴击。

  • ReaderWriterLockSlim 的百倍 用户态自旋 又给了 CPU 一顿暴击。

知道原因后,应对方案也就简单了。

  • 批量操作,降低串行化的 lock 个数,不要玩锁内卷。

  • 去掉 ReaderWriterLockSlim,使用 MemoryCache 自带的线程安全方法。

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

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

相关文章

QOMO Linux 4.0 正式版发布

首 先感谢大家长期以来对Qomo发行版和国产Linux操作系统的关心和爱护。由于一些客观原因&#xff0c;Qomo发行版的发行和维护工作一度减缓&#xff0c;社区也进入停滞 状态。但是从4.0开始&#xff0c;Qomo的开发和维护工作将恢复正常运转&#xff0c;本次发布的4.0版本就是我们…

c语言输入n个数按大小输出,输入n个整数并输出,用c语言表达

FOSS//这个是用静态数组储存整数#includeint main(void){int a[100],i0,j;//如果n小于100的话就不需要用动态数组&#xff0c;或者你可以把100改成更大的数&#xff0c;比如100000000......printf("请输入你要输入的数字&#xff0c;以ctrlz结束:");while(scanf(&quo…

ML.NET Cookbook:(5)如何查看中间过程数据?

通常&#xff0c;当我们构建实验时&#xff0c;我们希望确保“到某一时刻”的数据处理产生我们想要的结果。对于ML.NET来说&#xff0c;这不是很容易做到的&#xff1a;因为所有的ML.NET操作都是延迟执行的&#xff0c;所以我们构造的对象只是数据的“承诺”。我们需要创建游标…

在线交友背后的数学原理

全世界只有3.14 % 的人关注了数据与算法之美欣赏 TED-Ed 带字幕视频, 或者看下面编写的文字版. 01交友网站的背后是算法帝国大家好&#xff0c;我叫 Christian Rudder,我是 OKCupid 网站的创办人之一。这个网站现在已经是全美最大的交友网站。就象这网站上大多数其他人一样,我…

C语言删掉无关变量无输出,C语言变量类型与输出控制用法实例教程

本文实例讲述了C语言变量类型与输出控制用法&#xff0c;有助于读者很好的对其进行总结与归纳。该实例分享给大家供大家参考借鉴之用。具体如下&#xff1a;完整实例代码如下&#xff1a;/************************************************《Beginning C 4th Edition》Notes c…

你不得不知道的Visual Studio 2012(3)- 创建Windows应用程序

创建项目 在Visual Studio中创建一个应用程序&#xff0c;应首先创建一个项和一个解决方案。在此示例中&#xff0c;您将创建Windows presentation foundation应用程序。 创建 WPF 项目 在菜单栏上&#xff0c;依次选择 *** 文件 ***&#xff0c;新建&#xff0c;项目。 选择V…

关于.NET微服务最热门的问题解答

点击蓝字关注我们在我们最近让我们一起学习 .NET的微服务专场活动中&#xff0c;我们收到了一些很好的问题。我们在现场已经回答很多问题&#xff0c;但我们想继续回答一些在会议中出现的最热门的问题。如果你错过了现场直播&#xff0c;不要担心&#xff0c;因为你可以按需观看…

超级智能玩具《小小机器人》|全新50种玩法,创造力之源

致砖《小小机器人》全新套装电动机械的完美结合先来看看视频过过眼瘾吧来自美国STEAM教育让孩子跨学科学知识积木向来是STEAM教育很重要的一部分&#xff0c;因为它涉及到了多种学科&#xff1a;要搭建得稳固——这是工程学&#xff1b;要精准搭建——这是数学&#xff1b;要外…

链表归并成递减c语言,将两个递增的单链表合并为一个递减的单链表

问题描述&#xff1a;假设有两个按元素值递增次序排列的线性表&#xff0c;均以单链表形式存储。请编写算法将这两个单链表归并为一个按元素值递减次序排列的单链表&#xff0c;并要求利用原来两个单链表的结点存放归并后的单链表。问题解答&#xff1a;算法思想&#xff1a;两…

强烈推荐!孩子的科普从这套全球畅销250万册的最酷科学书起步

在马斯的学生时代的记忆中&#xff0c;数学定义定理、化学方程式、物理公式……这些科学知识点总是冷冰冰的&#xff0c;枯燥、深奥也总是科学的代名词。如今教育局明确规定科学课是小学必修课&#xff0c;孩子也逐步接受科学知识的熏陶。但科学课上冷冰冰的&#xff0c;枯燥、…

技术分享|手机推送原理剖析指南

源宝导读&#xff1a;本文旨在对手机推送原理进行剖析和阐述&#xff0c;对业务开发做一些方向性的解惑。一、手机推送的基本概念 ——什么是手机推送&#xff1f;百度词条&#xff1a;手机推送服务是指服务器 定向将信息实时送达手机的服务词条中有2组概念&#xff1a;第一组是…

IO控制命令

ioctl 控制套接字IO行为&#xff0c;获取套接字未决IO的信息。 向套接字发送ioctl命令的函数有两个 一个是源于winsock1 的 ioctlsocket 另一个是 winsock2 新引进的 WSAIoctl ioctl的常用命令&#xff1a; FIONBIO:将套接字设置于非阻塞模式 FIONREAD&#xff1a;返回套接字上…

闽高校计算机二级c语言模拟器,闽高校计算机二级C语言模拟卷及答案.doc

闽高校计算机二级C语言模拟卷及答案.doc (54页)本资源提供全文预览&#xff0c;点击全文预览即可全文预览,如果喜欢文档就下载吧&#xff0c;查找使用更方便哦&#xff01;9.90 积分&#xfeff;一、单项选择题(每小题2分&#xff0c;共20分)1.以下4组用户定义标识符中&#xf…

重磅!中国最赚钱的公司,要上市了!

全世界只有3.14 % 的人关注了数据与算法之美12月31日&#xff0c;中国烟草子公司中烟国际&#xff08;香港&#xff09;神秘地向香港交易所递交了IPO的材料&#xff0c;赶上了2018年的末班车。嫡子上市&#xff0c;中国烟草自己向众人掀起了裙裾一角。过去三年&#xff0c;中烟…

浅谈VS2012单元测试

1、先建一个工程此工程带有待测试的方法 2、在解决方案中建立单元测试 3、在测试项目中添加测试项目的引用 4、写测试用例 namespace UnitTestProject1 {[TestClass]public class UnitTest1{[TestMethod]public void TestMethod1(){var restClient (IRestClient)new JsonServi…

我写代码时的小倔强

分享自己写代码时的好习惯&#xff0c;让你的编程能力突飞猛进&#xff01;大家好&#xff0c;我是鱼皮&#xff0c;上回说到&#xff0c;很多同学在学编程时不注重代码质量&#xff0c;养成坏习惯的同时&#xff0c;失去了提升自己编程能力的机会。还没读上篇文章的同学&#…

c语言管理系统信息以文件保存,求大神给一份能用的c语言的学籍管理系统:且能进行文件保存...

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼#include#include#pragma comment(lib,"libmysql.lib")void showmenu(){system("cls");prinft("\n\n\n\n\n");prinft("\t ┏━━━━━━━━━━━━━━━━━━━━━━━┓ \n");prin…

10个舍不得删的高质量公号

全世界有3.14 % 的人已经关注了数据与算法之美在信息爆炸的时代快节奏的生活里 你是否曾有一瞬间觉得忙碌而空虚&#xff1f;以下10个优质公众号能让你在闲暇的时候不断的提升自我&#xff0c;拓宽视野愿以书卷气&#xff0c;行我路千里历史学资讯ID&#xff1a;tongbanlishi▲…

数据初始化

成员初始化&#xff1a;java保证所有变量在使用前必须初始化。对于方法的局部变量&#xff0c;java以编译时错误的形式来保证&#xff0c;所以如果写成&#xff1a;void f(){int i;i; //Error.. i not initialized}会得到一条错误信息&#xff0c;告诉你i 可能尚未初始化。当然…

ML.NET Cookbook:(7)如何训练回归模型?

通常&#xff0c;为了在ML.NET中训练任何模型&#xff0c;您将经历三个步骤&#xff1a;弄清楚训练数据如何以IDataView形式进入ML.NET。将“学习管道”构建为一系列基本的“运算符”&#xff08;估计器&#xff09;。在管道上调用Fit以获得经过训练的模型。示例文件[1]:featur…