一:背景

1. 讲故事

前段时间有位朋友找到我,说他们在查询报表的时候发现程序的稳定性会受到影响,但服务器的内存,CPU都是正常的,让我帮忙看下怎么回事,问了下程序的稳定性指的是什么?指的是卡死,那既然是卡死,就抓一个卡死的dump吧。

二:Windbg 分析

1. 当前是什么程序

不同的程序类型分析卡死的思路是不一样的,如果是 WKS模式 可以看下主线程,如果是 SRV模式 就要看其他线程了,接下来用 !eeversion 验证下。


0:000> !eeversion
5.0.521.16609 free
5,0,521,16609 @Commit: 2f740adc1457e8a28c1c072993b66f515977eb51
Server mode with 16 gc heaps
SOS Version: 6.0.5.7301 retail build

从卦中看当前是 .NET5 写的 Web网站,那就需要看其他线程都在做什么了。

2. 不稳定因素在哪里

朋友前面也说了查询报表的时候导致程序不稳定,因为这个所以要找SQL查询的 ,大概率问题在数据库端,接下来使用 ~*e !clrstack 观察所有线程栈,输出如下:


OS Thread Id: 0x24e8 (60)
Child SP IP Call Site
000000C2E3BFC700 00007ffd72955d04 [InlinedCallFrame: 000000c2e3bfc700] Microsoft.Data.SqlClient.SNINativeMethodWrapper.SNIReadSyncOverAsync(Microsoft.Data.SqlClient.SNIHandle, IntPtr ByRef, Int32)
000000C2E3BFC700 00007ffce5e00264 [InlinedCallFrame: 000000c2e3bfc700] Microsoft.Data.SqlClient.SNINativeMethodWrapper.SNIReadSyncOverAsync(Microsoft.Data.SqlClient.SNIHandle, IntPtr ByRef, Int32)
000000C2E3BFC6D0 00007ffce5e00264 ILStubClass.IL_STUB_PInvoke(Microsoft.Data.SqlClient.SNIHandle, IntPtr ByRef, Int32)
000000C2E3BFC7A0 00007ffce7a3ca1f Microsoft.Data.SqlClient.TdsParserStateObjectNative.ReadSyncOverAsync(Int32, UInt32 ByRef)
000000C2E3BFC7F0 00007ffce7a3c72c Microsoft.Data.SqlClient.TdsParserStateObject.ReadSniSyncOverAsync()
000000C2E3BFC880 00007ffce7a3c60b Microsoft.Data.SqlClient.TdsParserStateObject.TryReadNetworkPacket()
000000C2E3BFC8C0 00007ffce7a3c53e Microsoft.Data.SqlClient.TdsParserStateObject.TryPrepareBuffer()
000000C2E3BFC900 00007ffce79de54e Microsoft.Data.SqlClient.TdsParserStateObject.TryReadByte(Byte ByRef)
000000C2E3BFC940 00007ffce5e06bce Microsoft.Data.SqlClient.TdsParser.TryRun(Microsoft.Data.SqlClient.RunBehavior, Microsoft.Data.SqlClient.SqlCommand, Microsoft.Data.SqlClient.SqlDataReader, Microsoft.Data.SqlClient.BulkCopySimpleResultSet, Microsoft.Data.SqlClient.TdsParserStateObject, Boolean ByRef)
000000C2E3BFCAE0 00007ffce5e38d51 Microsoft.Data.SqlClient.SqlDataReader.TryConsumeMetaData()
000000C2E3BFCB50 00007ffce7a94f77 Microsoft.Data.SqlClient.SqlDataReader.get_MetaData()
.... OS Thread Id: 0x854 (72)
Child SP IP Call Site
000000C2E66BBE70 00007ffd72955d04 [InlinedCallFrame: 000000c2e66bbe70] Microsoft.Data.SqlClient.SNINativeMethodWrapper.SNIReadSyncOverAsync(Microsoft.Data.SqlClient.SNIHandle, IntPtr ByRef, Int32)
000000C2E66BBE70 00007ffce5e00264 [InlinedCallFrame: 000000c2e66bbe70] Microsoft.Data.SqlClient.SNINativeMethodWrapper.SNIReadSyncOverAsync(Microsoft.Data.SqlClient.SNIHandle, IntPtr ByRef, Int32)
000000C2E66BBE40 00007ffce5e00264 ILStubClass.IL_STUB_PInvoke(Microsoft.Data.SqlClient.SNIHandle, IntPtr ByRef, Int32)
000000C2E66BBF10 00007ffce7a3ca1f Microsoft.Data.SqlClient.TdsParserStateObjectNative.ReadSyncOverAsync(Int32, UInt32 ByRef)
000000C2E66BBF60 00007ffce7a3c72c Microsoft.Data.SqlClient.TdsParserStateObject.ReadSniSyncOverAsync()
000000C2E66BBFF0 00007ffce7a3c60b Microsoft.Data.SqlClient.TdsParserStateObject.TryReadNetworkPacket()
000000C2E66BC030 00007ffce7a3c53e Microsoft.Data.SqlClient.TdsParserStateObject.TryPrepareBuffer()
000000C2E66BC070 00007ffce79de54e Microsoft.Data.SqlClient.TdsParserStateObject.TryReadByte(Byte ByRef)
000000C2E66BC0B0 00007ffce5e06bce Microsoft.Data.SqlClient.TdsParser.TryRun(Microsoft.Data.SqlClient.RunBehavior, Microsoft.Data.SqlClient.SqlCommand, Microsoft.Data.SqlClient.SqlDataReader, Microsoft.Data.SqlClient.BulkCopySimpleResultSet, Microsoft.Data.SqlClient.TdsParserStateObject, Boolean ByRef)
000000C2E66BC250 00007ffce5e38d51 Microsoft.Data.SqlClient.SqlDataReader.TryConsumeMetaData()
000000C2E66BC2C0 00007ffce7a94f77 Microsoft.Data.SqlClient.SqlDataReader.get_MetaData()
...

从卦中看当前有两个线程在请求SQLSERVER,并在等待返回结果,接下来问题就来了,既然说卡死肯定花费了不少时间,所以对当前是什么 SQL 产生了好奇,我们可以提取下 72 号线程的 SqlCommand 对象,看看是什么sql?


0:072> !dso
OS Thread Id: 0x854 (72)
RSP/REG Object Name
000000C2E66BBDC8 00000216eafe2520 Microsoft.Data.SqlClient.SqlCommand 0:072> !DumpObj /d 00000216eafe2520
Name: Microsoft.Data.SqlClient.SqlCommand
00007ffce4027a90 400046d 18 System.String 0 instance 00000216eafe1a60 _commandText

然后 do 一下 _commandText 就可以了,去敏之后的SQL如下;


SELECT xxx
FROM [xxxxxx] AS [d]
LEFT JOIN [xxxDrugs] AS [n]
ON [d].[DrugId] = [n].[xxx]
WHERE [d].[xxxId] = '4f1be71f-2edb-4a5c-87dc-9ab3b981cbca'

上面的sql也就一个简单的表关联,但我的职业敏感性告诉我,这条sql应该就是没加 nolock 导致在 SQLSERVER 层面一直获取不到 S 锁,获取不到 S 锁的原因应该就是 报表 间接导致很多的意向U和意向X在相关表上成了 U 和 X 锁。

3. 到底卡了多久

怀疑是 SQLSERVER 层面的问题后,接下来还要找证据验证一下,也就是当前这条SQL到底阻塞了多久? 那怎么在 Dump 中观察 SQL 的请求时间呢?这就需要考察你对 SQLConnection 类的理解了,其中有一个字段 SqlConnection._innerConnection._createTime._dateData 就记录了 SQL 的开始时间。


0:072> !DumpObj /d 00000216eaf85fa0
Name: Microsoft.Data.SqlClient.SqlConnection
00007ffce61bb9f8 40004c6 90 ...onnectionInternal 0 instance 00000215cad017c0 _innerConnection 0:072> !DumpObj /d 00000215cad017c0
Name: Microsoft.Data.SqlClient.SqlInternalConnectionTds
00007ffce42c7670 4000174 48 System.DateTime 1 instance 00000215cad01808 _createTime 0:072> !DumpVC /d 00007ffce42c7670 00000215cad01808
Name: System.DateTime
Fields:
MT Field Offset Type VT Attr Value Name
00007ffce3f6cf98 4000258 0 System.UInt64 1 instance 5249873640594058230 _dateData

卦中的 0n5249873640594058230 怎么转化为具体时间呢?这又是考验你的基础知识了,这个数的前两位bit记录的是 时区信息 ,比如本地还是UTC,参考代码如下:


internal DateTime(long ticks, DateTimeKind kind, bool isAmbiguousDst)
{
if (ticks < 0 || ticks > 3155378975999999999L)
{
throw new ArgumentOutOfRangeException("ticks", SR.ArgumentOutOfRange_DateTimeBadTicks);
}
_dateData = (ulong)ticks | (isAmbiguousDst ? 13835058055282163712uL : 9223372036854775808uL);
}

上面的两个超大十进制数转化为二进制即高位的 11 和 10 开头,也就是十六进制开头的 c 和 8 。


0:072> ? 0n13835058055282163712
Evaluate expression: -4611686018427387904 = c0000000`00000000 0:072> ? 0n9223372036854775808
Evaluate expression: -9223372036854775808 = 80000000`00000000

那怎么将 _dateData 转成 ticks 呢?在源码中也有说明,即抹掉二进制中的高二位。


internal long InternalTicks => (long)(_dateData & 0x3FFFFFFFFFFFFFFFL);

有了这些前置基础,接下来就可以用 windbg 转换了。


0:072> ? 0n5249873640594058230 & 0x3FFFFFFFFFFFFFFF
Evaluate expression: 638187622166670326 = 08db4c42`d74babf6 0:072> .formats 08db4c42`d74babf6
Evaluate expression:
Hex: 08db4c42`d74babf6
Decimal: 638187622166670326
Decimal (unsigned) : 638187622166670326
Octal: 0043332304132722725766
Binary: 00001000 11011011 01001100 01000010 11010111 01001011 10101011 11110110
Chars: ..LB.K..
Time: Thu May 4 09:56:56.667 3623 (UTC + 8:00)
Float: low -2.23939e+014 high 1.31985e-033
Double: 5.29119e-266 0:072> .time
Debug session time: Thu May 4 09:58:39.000 2023 (UTC + 8:00)
System Uptime: 40 days 10:15:42.713
Process Uptime: 0 days 16:55:48.000
Kernel time: 0 days 0:01:54.000
User time: 0 days 0:39:40.000

从卦中看,SQL 的请求时间是 09:56:56 ,Dump抓取时间为 09:58:39,也就表示当前这条 SQL 已经等待了 1分43秒,这确实是不可容忍的。

从 dump 中挖到这些信息后,让朋友重点观察下 SQLSERVER 端,比如在卡住的时候查下锁相关的 DMV视图,是不是出现了锁等待,也可以加上 nolock 尝试一下。

最终朋友在 SQLSERVER 层面修改了 max degree of parallelism 来提高并发度,说情况缓解了很多,这其中细节,熟悉 SQLSERVER 的朋友可以留言解答一下哈。

三:总结

这次不稳定的事故从直观上就能猜到可能是 SQLSERVER 层面导致的问题,但需要证据,所以我们需要用 windbg 在这小小的花园里,挖呀挖呀挖! 让朋友对外有数据层面的说服力。

记一次 .NET 某药材管理系统 卡死分析的更多相关文章

  1. 记一次 .NET 某药品仓储管理系统 卡死分析

    一:背景 1. 讲故事 这个月初,有位朋友wx上找到我,说他的api过一段时间后,就会出现只有请求,没有响应的情况,截图如下: 从朋友的描述中看样子程序是被什么东西卡住了,这种卡死的问题解决起来相对简 ...

  2. 记一次 .NET 某化妆品 webapi 卡死分析

    一:背景 1. 讲故事 10月份星球里的一位老朋友找到我,说他们公司的程序在一个网红直播带货下给弄得无响应了,无响应期间有大量的 RabbitMQ 超时,寻求如何找到根源,聊天截图我就不发了. 既然无 ...

  3. 记一次 .NET某汽车零件采集系统 卡死分析

    一:背景 1. 讲故事 前段时间有位朋友在微信上找到我,说他的程序会出现一些偶发卡死的情况,让我帮忙看下是怎么回事,刚好朋友也抓到了dump,就让朋友把 dump 丢给我,接下来用 windbg 探究 ...

  4. 记一次 .NET 某物管后台服务 卡死分析

    一:背景 1. 讲故事 这几个月经常被朋友问,为什么不更新这个系列了,哈哈,确实停了好久,主要还是打基础去了,分析 dump 的能力不在于会灵活使用 windbg,而是对底层知识有一个深厚的理解,比如 ...

  5. 基于UML的中职班主任工作管理系统的分析与设计--文献随笔(二)

    一.基本信息 标题:基于UML的中职班主任工作管理系统的分析与设计 时间:2016 出版源:遵义航天工业学校 关键字:中职学校; 班主任工作管理; UML建模 二.研究背景 问题定义:班主任是一项特殊 ...

  6. 基于UML的毕业设计管理系统的分析与设计

    基于UML的毕业设计管理系统的分析与设计 <本段与标题无关,自行略过 最近各种忙,天气不错,导师心情不错:“我们要写一个关于UML的专著”,一句话:“一个完整的系统贯穿整个UML的知识”:我:“ ...

  7. 记C++课程设计--学生信息管理系统

                                                                                  C++课程设计--学生信息管理系统     ...

  8. 记一次 .NET 某风控管理系统 内存泄漏分析

    一:背景 1. 讲故事 上个月中旬,星球里的一位朋友在微信找我,说他的程序跑着跑着内存会不断的缓慢增长并无法释放,寻求如何解决 ? 得,看样子星球还得好好弄!!! 不管怎么说,先上 windbg 说话 ...

  9. 记一次 .NET 某工控自动化控制系统 卡死分析

    一:背景 1. 讲故事 前段时间遇到了好几起关于窗体程序的 进程加载锁 引发的 程序卡死 和 线程暴涨 问题,这种 dump 分析难度较大,主要涉及到 Windows操作系统 和 C++ 的基础知识, ...

  10. 记一次 .NET 某金融企业 WPF 程序卡死分析

    一:背景 1. 讲故事 前段时间遇到了一个难度比较高的 dump,经过几个小时的探索,终于给找出来了,在这里做一下整理,希望对大家有所帮助,对自己也是一个总结,好了,老规矩,上 WinDBG 说话. ...

随机推荐

  1. [CTF学习笔记]net-pack(WinShark)

    题目:shark on wire 1 Description We found this packet capture. Recover the flag. 思路 这里懂得了winshark的一些基本 ...

  2. NEFUOJ P903字符串去星问题

    Description 有一个字符串(长度小于100),要统计其中有多少个,并输出该字符串去掉后的新字符串. Input 输入数据有多组,每组1个连续的字符串; Output 在1行内输出该串内有多少 ...

  3. XXL-Job与Elastic-Job详细对比

    1. 失败处理策略 失败处理策略 XXL-Job Elastic-Job 失败重试 支持,最多重试三次.重试时间间隔可配置. 支持,最多重试十次.重试时间间隔可配置. 失败告警 支持,可配置告警接收人 ...

  4. 存储论——经济订货批量的R实现

    存储论又称库存理论,是运筹学中发展较早的分支.早在 1915 年,哈李斯(F.Harris)针对银行货币的储备问题进行了详细的研究,建立了一个确定性的存贮费用模型,并求得了最佳批量公式.1934 年威 ...

  5. JavaScript的引入方式

    外部JS文件 deno.js alert('你好!JavaScript'); JS引入方式.html <!--方式一:内部脚本--> <!--标签不能自闭和--> <sc ...

  6. SSM整合的所有配置(配置类)

    导入依赖坐标pom.xml <dependencies> <dependency> <groupId>junit</groupId> <artif ...

  7. day91:luffy:基于vue+drf的路飞学城项目后端部署

    目录 1.安装mysql镜像 2.把本地的数据导入到容器的mysql数据库中 3.安装redis容器 4.把后端项目部署前的处理 5.修改项目的配置文件:prod.py 6.从后端项目中收集静态文件 ...

  8. 全平台数据(数据库)管理工具 DataCap 管理 Rainbond 上的所有数据库

    DataCap是用于数据转换.集成和可视化的集成软件,支持多种数据源.文件类型.大数据相关数据库.关系数据库.NoSQL数据库等.通过该 DataCap 可以实现对多个数据源的管理,对数据源下的数据进 ...

  9. React 富文本编辑 braft-editor

    推荐一种react-富文本编辑器,braft-editor braft-editor的github:https://github.com/margox/braft-editor braft-edito ...

  10. html5和css3基础学习笔记

    网页简介 一个页面包括结构.表现.行为三个部分. 结构:HTML用于描述页面的结构. 表现:CSS用于控制页面中元素的样式. 行为:JavaScript用于响应用户操作. 第一部分 HTML 5(Hy ...