一:背景

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. 官方文档 | 【JVM调优体系】「GC底层调优实战」XPocket为终结性能问题而生—开发指南

    XPocket 用户文档 XPocket 是PerfMa为终结性能问题而生的开源的插件容器,它是性能领域的乐高,将定位或者解决各种性能问题的常见的Linux命令,JDK工具,知名性能工具等适配成各种X ...

  2. 超详细!手把手教你用 JaCoCo 生成单测覆盖率报告!

    我们都知道 Spock 是一个单测框架,其特点是语法简明.但当我们使用 Spock 写了一堆单元测试之后,如何生成对应的单测覆盖率报告呢?一般来说,我们会使用两个插件来一起完成单测覆盖率报告的生成,分 ...

  3. 【Diary】CSP-S 2020 游记

    一年 好快 从三百多天倒计时 一点一点掂着 又回来了 但是时间永远不会等待你. --??? CSP-J1/S1 CSP-J1/S1 Day0 请了一上午假. 这段时间都在摸鱼,作业没写( 多备赛一个上 ...

  4. Gpssworld仿真(二):并排排队系统模拟

    4.3 某一个加油站能够配给三个级别的燃油:①家庭取暖用的燃油:②轻工业用的燃油:③运输用的燃油.每一级别的燃油都有一个对应的油泵.订单中燃油的数量在3000加仑和5000加仑中变化,每次增加10加仑 ...

  5. jmeter参数化导致反斜杠(\)被转义

    前情提要:在用jmeter做接口测试时,对请求体进行参数化,执行结果报错.但在不参数化的情况下,执行结果成功,而且参数化后,请求中读取到的参数是正确的(执行失败与执行成功时的参数一致). 问题排查:参 ...

  6. Django笔记二十四之数据库函数之比较和转换函数

    本文首发于公众号:Hunter后端 原文链接:Django笔记二十四之数据库函数之比较和转换函数 这一篇笔记开始介绍几种数据库函数,以下是几种函数及其作用 Cast 转换类型 Coalesce 优先取 ...

  7. 利用机器人类Robot写出自动登录QQ的小代码

    最近写了一个小代码控制鼠标键盘使他自己登录QQ,下面给大家分享下这一小代码. 这段小程序是用Java里的Robot类实现的,控制鼠标键盘的一个机器人类. 我们想要实现自动登录QQ首先得想要做到这一步需 ...

  8. Jupyter Notebook(或vscode插件) 一个cell有多个输出

    方法一 在文件的开头加上如下代码,该方法仅对当前文件有效 from IPython.core.interativeshell import InteractiveShell InteractiveSh ...

  9. 关于PM系统以及OA系统的工作基本心态

    这个系统的目的是什么? 这个系统的初衷是好的,是一个信息化管理的数据科学系统,目的是更好的累计公司的业务数据. 但实际操作过程中,包括推广过程中,你能看到上层人员对于这个系统的态度,更像是一个个人企业 ...

  10. 金三银四好像消失了,IT行业何时复苏!

    疫情时候不敢离职,以为熬过来疫情了,行情会好一些,可是疫情结束了,反而行情更差了, 这是要哪样 我心中不由一万个 草泥 路过 我心中不惊有了很多疑惑和感叹 接着上一篇 一个28岁程序员入行自述和感受 ...