记一次 .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,一经查实,立即删除!

相关文章

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 网站的创办人之一。这个网站现在已经是全美最大的交友网站。就象这网站上大多数其他人一样,我…

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

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

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

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

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

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

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

源宝导读&#xff1a;本文旨在对手机推送原理进行剖析和阐述&#xff0c;对业务开发做一些方向性的解惑。一、手机推送的基本概念 ——什么是手机推送&#xff1f;百度词条&#xff1a;手机推送服务是指服务器 定向将信息实时送达手机的服务词条中有2组概念&#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;失去了提升自己编程能力的机会。还没读上篇文章的同学&#…

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

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

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

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

android动画送礼物,Android仿直播类app赠送礼物功能

直播界面实现的是播放本地的视频文件&#xff1a;/*** 直播界面&#xff0c;用于对接直播功能*/public class LiveFrag extends Fragment {private ImageView img_thumb;private VideoView video_view;NullableOverridepublic View onCreateView(NonNull LayoutInflater inflat…

一张纸一幅图,竟然提高了10倍的学习和工作效率!?

人类大脑的容量远远超出一般人的想象&#xff0c;时到21世纪的今天&#xff0c;我们对大脑的运用远远不够。大脑机能的使用率基于我们的思维模式&#xff0c;而思维导图正是开发大脑中最有效的利器&#xff01;之前小木给大家推荐了一套基于少儿大脑思维开发的书籍——东尼博赞…

C# 消息队列之MSMQ

首先说一下&#xff0c;消息队列 (MSMQ Microsoft Message Queuing)是MS提供的服务&#xff0c;也就是Windows操作系统的功能&#xff0c;并不是.Net提供的。消息队列&#xff08;MSMQ&#xff09;技术使得运行于不同时间的应用程序能够在各种各样的网络和可能暂时脱机的系统之…

惊呆了!竟然还有这样的操作!

2018已成为过去&#xff0c;2019的序幕已经悄然打开&#xff01;2019开年钜惠盛大来袭&#xff01;&#xff08;福利在最后&#xff01;&#xff01;&#xff01;&#xff09;Python专场Python快速入门实战&#xff08;59.9&#xff09;人工智能的浪潮下&#xff0c;Python因其…

使用 .NET 升级助手将.NET Framework应用迁移到.NET 5

从.NET Framework 迁移到.NET 5 犹如搬家&#xff0c;我们都知道搬家是很痛苦的&#xff0c;我们请求搬家公司来减轻我们的压力&#xff0c;.NET 升级助手 的作用就类似我们聘请的搬家公司&#xff0c;帮助我们处理繁重乏味的迁移工作。.NET 升级助手是一个全局命令行工具&…

java 逐行写入csv_go|使用go读写CSV文件

生成CSV文件package mainimport ("encoding/csv""log""os")var data [][]string{{"tom", "18", "beijing"}, {"jon", "19", "shanghai"}}func main() {file, err : os.Create(&qu…

用android ndk编译ffmpeg,AndroidNDK交叉编译FFMPEG

1. 简介在进行安卓音视频开发时&#xff0c;利用安卓NDK工具交叉编译FFmpeg&#xff0c;生成可供安卓平台调用的FFmpeg库是最基础的工作。本篇文章介绍了利用NDK进行FFmpeg编译的具体过程&#xff0c;以及如何在Linux和windows系统下交叉编译出适用于不同安卓平台的FFmpeg库文件…