一:背景

1. 讲故事

前几天有位朋友wx求助,它的程序CPU经常飙满,没找到原因,希望帮忙看一下。



这些天连续接到几个cpu爆高的dump,都看烦了,希望后面再来几个其他方面的dump,从沟通上看,这位朋友表现的好惨,可能实际更惨,那既然找到我了,我就尽最大能力帮他找到幕后真凶,话不多说,上 windbg。

二: windbg 分析

1. 查看托管线程

因为线程都是靠cpu养着,所以从线程上入手也是一个很好的思路,要想查看程序的所有托管线程,可以使用 !t 命令。


0:000> !t
ThreadCount: 38
UnstartedThread: 0
BackgroundThread: 34
PendingThread: 0
DeadThread: 3
Hosted Runtime: no
Lock
ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 1 105c 000000000067f600 2a020 Preemptive 0000000000000000:0000000000000000 0000000000671ec0 0 MTA
2 2 13d0 00000000192c4f40 2b220 Preemptive 0000000000000000:0000000000000000 0000000000671ec0 0 MTA (Finalizer)
...
XXXX 15 0 000000001bc64970 8039820 Preemptive 0000000000000000:0000000000000000 0000000000671ec0 0 MTA (Threadpool Completion Port)
24 23 1380 000000001bc660e0 8029220 Preemptive 0000000000000000:0000000000000000 0000000000671ec0 0 MTA (Threadpool Completion Port)
XXXX 53 0 000000001bc63200 8039820 Preemptive 0000000000000000:0000000000000000 0000000000671ec0 0 MTA (Threadpool Completion Port)
XXXX 27 10dc 000000001bd0dbf0 1029220 Preemptive 0000000002CB40F8:0000000002CB4108 0000000000671ec0 1 MTA (GC) (Threadpool Worker)

在卦象上看:程序有38个线程,死了3个,我去,有一个亮点,最后一行出现了一个熟悉的 MTA (GC) 字样,这什么意思呢? 这表示当前线程触发了GC,但奇怪的是,这个触发GC的线程死了,你肯定要问怎么看出来的,看行头的 XXXX,先不管了,死马当活马医,调出线程的所有托管和非托管栈,看看有没有 WaitUntilGCCompletetry_allocate_more_space 字样。

2. 查看线程栈

要想查看所有线程的线程栈,可以使用 ~*e !dumpstack 命令。

  • 搜索 WaitUntilGCComplete 关键字。

从图中看,嘿嘿,真的有18个线程在等待,而且还看到了 System.String.Concat ,是不是和我上上篇发的 his cpu爆高是一个套路?

  • 搜索 try_allocate_more_space 关键字。

我去,竟然没有 try_allocate_more_space 关键词,这就和 his 不是一个套路了, 有可能这个dump踩的不是特别好的时机,有可能程序正处于某些怪异行为中。

看样子这段路走到头了,不过还是那句话,线程是靠cpu养着的,那就硬着头皮看看各个线程都在做什么吧,为了让结果更清晰一点,换一个命令 ~*e !clrstack

从图中可以看出当前有 25 个线程正卡在 FindEntry(System.__Canon) 处,而且从调用堆栈上看,貌似是 aliyun 封装的dll,为什么有这么多的线程卡在这里呢?这就给人一个很大的问号? 接下来我就把阿里云的这段代码给导出来看看到底发生了什么。

3. 查看问题代码

要想导出问题代码,还是用经典的 !ip2md + !savemodule 组合命令。


0:000> !ip2md 000007fe9a1a0641
MethodDesc: 000007fe9a5678e0
Method Name: Aliyun.Acs.Core.Utils.CacheTimeHelper.AddLastClearTimePerProduct(System.String, System.String, System.DateTime)
Class: 000007fe9a595a08
MethodTable: 000007fe9a567900
mdToken: 00000000060000a6
Module: 000007fe9a561f58
IsJitted: yes
CodeAddr: 000007fe9a1a0610
Transparency: Critical
0:000> !savemodule 000007fe9a561f58 E:\dumps\AddLastClearTimePerProduct.dll
3 sections in file
section 0 - VA=2000, VASize=14148, FileAddr=200, FileSize=14200
section 1 - VA=18000, VASize=3fc, FileAddr=14400, FileSize=400
section 2 - VA=1a000, VASize=c, FileAddr=14800, FileSize=200

然后用 ILSpy 反编译一下这个dll,因为是阿里云的代码,我就可以放心大胆的放出来啦。


// Aliyun.Acs.Core.Utils.CacheTimeHelper
using System;
using System.Collections.Generic; public class CacheTimeHelper
{
private static Dictionary<string, DateTime> lastClearTimePerProduct = new Dictionary<string, DateTime>(); private const int ENDPOINT_CACHE_TIME = 3600; public static bool CheckCacheIsExpire(string product, string regionId)
{
string key = product + "_" + regionId;
DateTime dateTime;
if (lastClearTimePerProduct.ContainsKey(key))
{
dateTime = lastClearTimePerProduct[key];
}
else
{
dateTime = DateTime.Now;
lastClearTimePerProduct.Add(key, dateTime);
}
if (3600.0 < (DateTime.Now - dateTime).TotalSeconds)
{
return true;
}
return false;
} public static void AddLastClearTimePerProduct(string product, string regionId, DateTime lastClearTime)
{
string key = product + "_" + regionId;
if (lastClearTimePerProduct.ContainsKey(key))
{
lastClearTimePerProduct.Remove(key);
}
lastClearTimePerProduct.Add(key, lastClearTime);
}
}

可以看出,上面这段代码在 if (lastClearTimePerProduct.ContainsKey(key)) 处走不下去了,如果往下追,可参考 Dictionary 的源码。


public class Dictionary<TKey, TValue>
{
// System.Collections.Generic.Dictionary<TKey,TValue>
public bool ContainsKey(TKey key)
{
return FindEntry(key) >= 0;
}
}

到这里,有没有看出这个 CacheTimeHelper 有什么问题吗? 对,竟然在多线程环境下用的是非线程安全的 Dictionary<string, DateTime>,这就很有问题了。

4. 用 Dictionary 到底会有什么问题

在多线程环境下用 Dictionary 肯定会导致数据错乱,这个毫无疑问,而且还会遇到一些 迭代时异常,但如果说这个误用会导致 CPU 爆高,在我的视野范围内还没看到过。。。为了确保起见,到 bing 上搜搜这样的 天涯沦落人

嘿嘿,还真的有这样的案例: High CPU in .NET app using a static Generic.Dictionary ,再截个图。

从文章描述看,简直是一摸一样,这也就断定在多线程环境下操作 Dictionary ,可能会导致 FindEntry(key) 时出现死循环,然后 25 个死循环一起把cpu抬起来了,补充一下当前爆满的CPU利用率。。。


0:000> !tp
CPU utilization: 100%
Worker Thread: Total: 27 Running: 27 Idle: 0 MaxLimit: 32767 MinLimit: 4
Work Request in Queue: 0
--------------------------------------
Number of Timers: 1
--------------------------------------
Completion Port Thread:Total: 4 Free: 3 MaxFree: 8 CurrentLimit: 3 MaxLimit: 1000 MinLimit: 4

三:总结

既然是阿里云的sdk出的bug,这问题就麻烦了。。。改也改不得,然后告诉朋友去提工单解决。

本以为事情就这样结束了,但我想一想,几年前用的阿里云其他 sdk 也遇到了类似CPU爆高的问题,后来通过升级sdk就搞定了,这次也赌赌看,先看一下程序集信息。


[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("Alibaba Cloud")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("2009-2018 Alibaba Cloud")]
[assembly: AssemblyDescription("Alibaba Cloud SDK for C#")]
[assembly: AssemblyFileVersion("1.1.12.0")]
[assembly: AssemblyInformationalVersion("1.1.12")]
[assembly: AssemblyProduct("aliyun-net-sdk-core")]
[assembly: AssemblyTitle("aliyun-net-sdk-core")]
[assembly: AssemblyVersion("1.1.12.0")]

可以看到朋友当前用的是 1.1.12.0 版本,那就把 aliyun-net-sdk-core 升级到最新再看看这个 CacheTimeHelper 有没有被修复 ?



果然不出所料,在新版本中给修复好了,所以经验告诉我,用阿里云的sdk,要记得经常升级,不然各种大坑等着你。。。

更多高质量干货:参见我的 GitHub: dotnetfly

记一次 .NET 车联网云端服务 CPU爆高分析的更多相关文章

  1. 记一次 .NET 某智能交通后台服务 CPU爆高分析

    一:背景 1. 讲故事 前天有位朋友加微信求助他的程序出现了CPU爆高的问题,开局就是一个红包,把我吓懵了! 由于是南方小年,我在老家张罗处理起来不方便,没有第一时间帮他处理,朋友在第二天上午已经找出 ...

  2. 记一次 .NET 差旅管理后台 CPU 爆高分析

    一:背景 1. 讲故事 前段时间有位朋友在微信上找到我,说他的 web 系统 cpu 运行一段时候后就爆高了,让我帮忙看一下是怎么回事,那就看吧,声明一下,我看 dump 是免费的,主要是锤炼自己技术 ...

  3. 记一次 .NET 某电子病历 CPU 爆高分析

    一:背景 1.讲故事 前段时间有位朋友微信找到我,说他的程序出现了 CPU 爆高,帮忙看下程序到底出了什么情况?图就不上了,我们直接进入主题. 二:WinDbg 分析 1. CPU 真的爆高吗? 要确 ...

  4. 记一次 .NET 某资讯论坛 CPU爆高分析

    大概有11天没发文了,真的不是因为懒,本想前几天抽空写,不知道为啥最近求助的朋友比较多,一天都能拿到2-3个求助dump,晚上回来就是一顿分析,有点意思的是大多朋友自己都分析了几遍或者公司多年的牛皮藓 ...

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

    一:背景 1. 讲故事 已经连续写了几篇关于内存暴涨的真实案例,有点麻木了,这篇换个口味,分享一个 CPU爆高 的案例,前段时间有位朋友在 wx 上找到我,说他的一个老项目经常收到 CPU > ...

  6. 记一次 .NET 某机械臂智能机器人控制系统MRS CPU爆高分析

    一:背景 1. 讲故事 这是6月中旬一位朋友加wx求助dump的故事,他的程序 cpu爆高UI卡死,问如何解决,截图如下: 在拿到这个dump后,我发现这是一个关于机械臂的MRS程序,哈哈,在机械臂这 ...

  7. 记一次 .NET游戏站程序的 CPU 爆高分析

    一:背景 1. 讲故事 上个月有个老朋友找到我,说他的站点晚高峰 CPU 会突然爆高,发了两份 dump 文件过来,如下图: 又是经典的 CPU 爆高问题,到目前为止,对这种我还是有一些经验可循的. ...

  8. 记一次 .NET 某医院HIS系统 CPU爆高分析

    一:背景 1. 讲故事 前几天有位朋友加 wx 抱怨他的程序在高峰期总是莫名其妙的cpu爆高,求助如何分析? 和这位朋友沟通下来,据说这问题困扰了他们几年,还请了微软的工程师过来解决,无疾而终,应该还 ...

  9. 记一次 .NET 某旅行社Web站 CPU爆高分析

    一:背景 1. 讲故事 前几天有位朋友wx求助,它的程序内存经常飙升,cpu 偶尔飙升,没找到原因,希望帮忙看一下. 可惜发过来的 dump 只有区区2G,能在这里面找到内存泄漏那真有两把刷子..., ...

随机推荐

  1. 对用pyinstaller打包的exe程序进行反编译,获得源码

    参考文章: 1.https://www.cnblogs.com/DirWang/p/12018949.html#PyInstallerExtractor 2.https://msd.misuland. ...

  2. Oment++ 初学者教程 第4节-将其转变为真实网络

    4.1两个以上的节点 现在,我们将迈出一大步:创建几个tic模块并将它们连接到网络中.现在,我们将使它们的工作变得简单:一个节点生成一条消息,其他节点继续沿随机方向扔消息,直到它到达预定的目标节点为止 ...

  3. 全网最详细的新手入门Mysql命令和基础,小白必看!

    MySQL简介 什么是数据库 ? 数据库(Database)是按照数据结构来组织.存储和管理数据的仓库,它产生于距今六十多年前,随着信息技术和市场的发展,特别是二十世纪九十年代以后,数据管理不再仅仅是 ...

  4. [源码解析] 并行分布式任务队列 Celery 之 消费动态流程

    [源码解析] 并行分布式任务队列 Celery 之 消费动态流程 目录 [源码解析] 并行分布式任务队列 Celery 之 消费动态流程 0x00 摘要 0x01 来由 0x02 逻辑 in komb ...

  5. OO 第二单元

    前言 ​ 第二单元 OO 作业的主题是多线程,课程组通过了电梯调度这个经典问题考察了多线程的调度. ​ 从第五次作业到第七次作业的迭代为,单部多线程可捎带电梯,多部多线程可捎带调度电梯(电梯属性相同) ...

  6. 几十行代码实现ASP.NET Core自动依赖注入

    在开发.NET Core web服务的时候,我们习惯使用自带的依赖注入容器来进行注入. 于是就会经常进行一个很频繁的的重复动作:定义一个接口->写实现类->注入 有时候会忘了写Add这一步 ...

  7. java面试一日一题:讲对mysql的MVCC的理解

    问题:请讲下对mysql中MVCC的理解 分析:这个问题要回答的是对MVCC的理解,以及MVCC解决了什么问题这几个方面入手. 回答要点: 主要从以下几点去考虑, 1.什么是MVCC? 2.MVCC用 ...

  8. 三、python学习-常用模块

    一.常用模块 1.math数学模块 在计算机中,所有数值在计算机底层都是约等于机制,并不是精确地 import math #ceil() 向上取整操作 math.ceil(3.1)=>4 #fl ...

  9. 集群部署时的分布式session如何实现?

    session是啥?浏览器有个cookie,在一段时间内这个cookie都存在,然后每次发请求过来都带上一个特殊的jsessionid cookie,就根据这个东西,在服务端可以维护一个对应的sess ...

  10. 一个Bug,让我发现了 Java 界的.AJ(锥)!

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 话我放这,踩过的坑越多头发越少! 说来也是奇怪,只要是学编程的,从初次接触的 Jav ...