记一次 .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: 1
Unknown 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 Owner
53 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 sections in file
section 0 - VA=2000, VASize=b6dc, FileAddr=200, FileSize=b800
section 1 - VA=e000, VASize=3d0, FileAddr=ba00, FileSize=400
section 2 - VA=10000, VASize=c, FileAddr=be00, FileSize=200
然后用 ILSpy 打开问题代码,截图如下:
尼玛,果然每个 DataContext.SetContextItem() 方法中都有一个 lock 锁,完美命中 lock convoy。
4. 真的就这样结束了吗?
本来准备汇报了,但想着500多个线程栈都调出来了,闲着也是闲着,干脆扫扫看吧,结果我去,意外发现有 134 个线程卡在 ReaderWriterLockSlim.TryEnterReadLockCore 处,如下图所示:
从名字上可以看出,这是一个优化版的读写锁: ReaderWriterLockSlim,为啥有 138 个线程都卡在这里呢? 真的很好奇,再次导出问题。
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 自带的线程安全方法。
更多高质量干货:参见我的 GitHub: dotnetfly

记一次 .NET 某电商交易平台Web站 CPU爆高分析的更多相关文章
- 记一次 .NET 某旅行社Web站 CPU爆高分析
一:背景 1. 讲故事 前几天有位朋友wx求助,它的程序内存经常飙升,cpu 偶尔飙升,没找到原因,希望帮忙看一下. 可惜发过来的 dump 只有区区2G,能在这里面找到内存泄漏那真有两把刷子..., ...
- 记一次 .NET 某电商无货源后端服务 死锁分析
一:背景 1. 讲故事 这个月初,星球里的一位朋友找到我,说他的程序出现了死锁,怀疑是自己的某些写法导致mongodb出现了如此尴尬的情况,截图如下: 说实话,看过这么多dump,还是第一次遇到真实的 ...
- 记一次 .NET 某机械臂智能机器人控制系统MRS CPU爆高分析
一:背景 1. 讲故事 这是6月中旬一位朋友加wx求助dump的故事,他的程序 cpu爆高UI卡死,问如何解决,截图如下: 在拿到这个dump后,我发现这是一个关于机械臂的MRS程序,哈哈,在机械臂这 ...
- Java生鲜电商平台-商城系统库存问题分析以及产品设计对逻辑/物理删除思考
Java生鲜电商平台-商城系统库存问题分析以及产品设计对逻辑/物理删除思考 说明:在生鲜电商的库存设计,是后台的重点,也是难点,关乎商品是否存在超卖.商品的库存增加方式倒不难,直接在后台添加即可,而扣 ...
- C2B电商三种主要模式的分析_数据分析师
C2B电商三种主要模式的分析_数据分析师 在过去的一年中电商领域血雨腥风,尤其是天猫.京东.苏宁.当当.易讯等B2C电商打得不亦乐乎.而随着B2C领域竞争进入白热化阶段,C2B模式也在天猫" ...
- 电商类Web原型制作分享-IKEA
IKEA是一个家居整合大型零售商,属于电商类官网.电商以展示商品.售后服务.购物流程为主.根据网站的图文方式排版,主导航栏使用的标签组,区域导航栏使用的是垂直选项卡,实现下拉弹出面板交互的功能. 本原 ...
- 电商类Web原型制作分享——聚美优品
这是一家化妆品限时特卖商城.作为美妆电商类网站的佼佼者,网站以用户体验为核心,画面主色调符合女性消费者审美.排版整齐,布局合理.网站用弹出面板实现点击弹出内容,鼠标悬停文字按钮颜色改变等交互效果. 本 ...
- Spark大型电商项目实战-及其改良(3) 分析sparkSQL语句的性能影响
之前的运行数据被清除了,只能再运行一次,对比一下sparkSQL语句的影响 纯SQL的时间 对应时间表 th:first-child,.table-bordered tbody:first-child ...
- 电商类web原型制作分享——美丽说【附源文件】
美丽说是国内白领女性时尚消费品牌,精选上千家优质卖家供应商,为用户提供女装.女鞋.女包.配饰.美妆等品类的优质时尚商品. 此原型模板所用到的组件有搜索框.下拉菜单.输入框.选项卡等.交互动作有切换选项 ...
随机推荐
- 《SQL必知必会》学习笔记整理
简介 本笔记目前已包含 <SQL必知必会>中的所有章节. 我在整理笔记时所考虑的是:在笔记记完后,当我需要查找某个知识点时,不需要到书中去找,只需查看笔记即可找到相关知识点.因此在整理笔记 ...
- Prime Ring Problem UVA - 524
A ring is composed of n (even number) circles as shown in diagram. Put natural numbers 1,2,...,n int ...
- kuberadm集群升级
升级kubernetes集群 注意不能跨版本升级 比如1.13.x 要先升级到1.14.x,不能直接升级到1.15.x 举例说明升级到1.15,和1.14有些参数不一样,具体看官网: https:// ...
- OOP-面向对象(三)
魔术方法的使用 # 使用 __new__ 创建单态模式 class Singleton(): __obj = None def __new__(cls, *args, **kwargs): if cl ...
- 注册中心与API网关不是这样用的!
之前在做顾问和咨询项目的时候,见到了一种非常经典的关于API网关和注册中心的错误用法.这个案例在我的星球里已经分享过,没想到最近又碰到了两个类似的使用姿势.也许这样的问题还存在不少团队的应用中,所以拿 ...
- 硬件篇-04-SLAM移动底盘机械设计
这篇比较水,发出来主要是为了呼应专栏主题,既然是实现,那各个方面都得讲一下不是. 底盘SW模型 淘的,主要是看上了它有弹簧阻尼器,适合野外,抗震,但是这种底盘结构转向起来比较吃力.是再有个全轮 ...
- hdu1815 2sat + 二分 + 建图
题意: 给你两个总部,s1 ,s2,和n个点,任意两点之间都是通过这个总部相连的,其中有一些点不能连在同一个总部上,有一些点可以连接在同一个总部上,总部和总部之间可以直接连接,就是假如a, ...
- GNU C++的符号改编机制介绍(函数的名称粉碎格式解析)
转载:http://blog.csdn.net/roland_sun/article/details/43233565 众所周知,强大的C++相较于C增添了许多功能.这其中就包括类.命名空间和重载这些 ...
- UVA10827球面上的最大和
题意: 最大子矩阵的加强版,就是给你一个n*n的矩阵,每个格子里面都有数字,然后我们在里面选择一个矩阵,使得矩阵中所有数字的和最大,而且这个题目说这个n*n的矩阵的最右边和最左边是相邻的,最 ...
- CVE-2010-3974:Windows 传真封面编辑器 FxsCover.exe 双重释放漏洞调试分析
0x01 堆空间申请后的双重释放 Windows FxsCover 程序存储封面编辑器的信息,封面编辑器是传真服务的一个组件,通过解析特定的传真封面文件(.cov)时,会调用类析构函数对同一内存中的栈 ...