和我一起来分析某药品仓储管理系统 卡死现象

一:背景

1. 讲故事

这个月初,有位朋友wx上找到我,说他的api过一段时间后,就会出现只有请求,没有响应的情况,截图如下:

3e3c6959c4e75d8c310df44bb37b57ec.png

从朋友的描述中看样子程序是被什么东西卡住了,这种卡死的问题解决起来相对简单,接下来我就用 windbg 给大家分析一下。

二:Windbg 分析

1. Request 请求正在干嘛?

既然朋友说 api 有 request 无 response,那怎么去验证朋友的话对不对呢?我们都知道 .NET 用 HttpContext 来表示一个请求,言外之意就是可以去抓 HttpContext 下的时长属性,Netext 中有一个 !whttp 命令可以帮助我们。

0:000> !whttp
HttpContext    Thread Time Out Running  Status Verb     Url
000000563bf803b0   42 00:01:50 00:01:24    200 POST     http://xxx.com:30003/Wms/xxx/xxx?xxx=xxx&xxx=x-HN
000000563bf84660   -- 00:01:50 Finished    200 GET      http://xxx.com:30003/
000000563c4a0470   51 00:01:50 00:00:12    200 POST     http://xxx.com:30003/Wms/xxx/xxx?xxx=xxx&xxx=xxx2C
00000056bbf63590   30 00:01:50 00:02:41    200 POST     http://xxx.com:30003/Wms/xxx/xxx?xxx=xxx&xxx=xxx-B2C
00000056bc82a038   -- 00:01:50 Finished    200 GET      http://localhost:30003/
00000056bc84a3e8   44 00:01:50 00:00:51    200 POST     http://xxx.com:30003/Wms/xxx/xxx?xxx=xxx&xxx=x
00000056bc8671c8   46 00:01:50 00:00:45    200 POST     http://xxx.com:30003/Wms/xxx/xxx?xxx=xxx&xxx=xxx-B2C
000000573bf44698   35 00:01:50 00:02:39    200 POST     http://xxx.com:30003/Wms/xxx/xxx?xxx=xxx&xxx=x
000000573bf483c0   33 00:01:50 00:02:41    200 POST     http://xxx.com:30003/Wms/xxx/xxx?xxx=xxx&xxx=x-HN
000000573bf97e80   40 00:01:50 00:02:32    200 POST     http://xxx.com:30003/Wms/xxx/xxx?xxx=xxx&xxx=ZJB2C
000000573c583b08   -- 00:01:50 Finished    200 GET      http://localhost:30003/
000000573c589ec8   -- 00:01:50 Finished    200 GET      http://xxx.com:30003/Wms/xxx/xxx/xxx
000000573c760e28   -- 00:01:50 Finished    200 POST     http://xxx.com:30003/Wms/xxx/xxx/xxx
000000573c95f990   48 00:01:50 00:00:31    200 POST     http://xxx.com:30003/Wms/Common/xxx?xxx=xxx&xxx=x-HN
00000057bbf4f8e8   31 00:01:50 00:02:12    200 POST     http://xxx.com:30003/Wms/xxx/xxx?xxx=xxx&xxx=x
00000057bc080340   50 00:01:50 00:00:19    200 POST     http://xxx.com:30003/Wms/xxx/xxx?xxx=xxx&xxx=x
000000583c4aee80   43 00:01:50 00:01:11    200 POST     http://xxx.com:30003/Wms/xxx/xxx?xxx=xxx&xxx=xxx2B
000000583c4d0c50   53 00:01:50 00:00:01    200 POST     http://xxx.com:30003/Wms/xxx/xxx?xxx=xxx&xxx=xxx2B
00000058bbf8f1a0   34 00:01:50 00:02:22    200 POST     http://xxx.com:30003/Wms/xxx/xxx?xxx=xxx&xxx=xxx2B
000000593bfe1758   41 00:01:50 00:01:22    200 POST     http://xxx.com:30003/Wms/xxx/xxx?xxx=xxx&xxx=xxx2C
000000593c892160   -- 00:01:50 Finished    200 GET      http://xxx.com:30003/Wms/xxx/xxx/xxxJob
000000593ca813b0   45 00:01:50 00:00:30    200 POST     http://xxx.com:30003/Wms/xxx/xxx?xxx=xxx&xxx=xxx-HN
000000593caa45d8   -- 00:01:50 Finished    200 GET      http://xxx.com:30003/
00000059bc1ad808   32 00:01:50 00:01:45    200 POST     http://xxx.com:30003/Wms/xxx/xxx?xxx=xxx&xxx=xxx-B2C
00000059bc1c3d70   36 00:01:50 00:01:29    200 POST     http://xxx.com:30003/Wms/xxx/xxx?xxx=xxx&xxx=x25 HttpContext object(s) found matching criteria

Running 列可以看到大多请求都已经达到1分钟以上,这也验证了朋友所说的卡死问题,按照经验,可以取 Running 列中最大的 httpContext 所在的线程,也就是上面的 3033 号线程, 看看它们都在干什么?

2. 探究 Running 最长线程

接下来切到 3033 号线程,看看它们的线程栈。

0:000> ~30s
ntdll!NtWaitForSingleObject+0xa:
00007ffd`b81f024a c3              ret
0:030> !clrstack 
OS Thread Id: 0x29d0 (30)Child SP               IP Call Site
0000005acc3ac590 00007ffdb81f024a [PrestubMethodFrame: 0000005acc3ac590] xxx.xxx.RedisConnectionHelp.get_Instance()
0000005acc3ac850 00007ffd4dd78911 xxx.xxx.RedisCache..ctor(Int32, System.String)
0000005acc3ac8c0 00007ffd4dd78038 xxx.xxx.CacheByRedis.HashGet[[System.__Canon, mscorlib]](System.String, System.String, Int32)
0000005acc3ac968 00007ffdabef1f7c [StubHelperFrame: 0000005acc3ac968] 
0000005acc3ac9c0 00007ffd4dd77f18 xxx.xxx.Cache.xxx.GetCacheNotAreaDataEntity[[System.__Canon, mscorlib]](System.String, System.String, System.String)...0:030> ~33s
ntdll!NtWaitForMultipleObjects+0xa:
00007ffd`b81f07ba c3              ret
0:033> !clrstack 
OS Thread Id: 0x3ad4 (33)Child SP               IP Call Site
0000005accabae90 00007ffdb81f07ba [GCFrame: 0000005accabae90] 
0000005accabafb8 00007ffdb81f07ba [HelperMethodFrame_1OBJ: 0000005accabafb8] System.Threading.Monitor.ObjWait(Boolean, Int32, System.Object)
0000005accabb0d0 00007ffdaac60d64 System.Threading.ManualResetEventSlim.Wait(Int32, System.Threading.CancellationToken)
0000005accabb160 00007ffdaac5b4bb System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken)
0000005accabb1d0 00007ffdab5a01d1 System.Threading.Tasks.Task.InternalWait(Int32, System.Threading.CancellationToken)
0000005accabb2a0 00007ffdab59cfa7 System.Threading.Tasks.Task`1[[System.__Canon, mscorlib]].GetResultxxx(Boolean)
0000005accabb2e0 00007ffd4d8d338f xxx.Config.xxx.Config`1[[System.__Canon, mscorlib]].GetConfig(xxx.Config.Model.ConfigListener, System.Func`2<xxx.Config.Request.GetConfigRequest,System.Threading.Tasks.Task`1<System.String>>)
0000005accabb340 00007ffd4d8d2f40 xxx.Config.xxx.Config`1[[System.__Canon, mscorlib]].get_Item(System.String, System.String)
0000005accabb3c0 00007ffd4dd78f7f xxx.Util.BaseConfig.get_GetRedisConn()
0000005accabb440 00007ffd4dd78e9c xxx.xxx.RedisConnectionHelp.GetConnectionString()
0000005accabb4a0 00007ffd4dd789cb xxx.xxx.RedisConnectionHelp..cctor()
0000005accabb940 00007ffdabef6953 [GCFrame: 0000005accabb940] 
0000005accabc5b0 00007ffdabef6953 [PrestubMethodFrame: 0000005accabc5b0] xxx.xxx.RedisConnectionHelp.get_Instance()
0000005accabc870 00007ffd4dd78911 xxx.xxx.RedisCache..ctor(Int32, System.String)
0000005accabc8e0 00007ffd4dd78038 xxx.xxx.CacheByRedis.HashGet[[System.__Canon, mscorlib]](System.String, System.String, Int32)
0000005accabc988 00007ffdabef1f7c [StubHelperFrame: 0000005accabc988] 
0000005accabc9e0 00007ffd4dd77f18 xxx.Core.Cache.xxx.GetCacheNotAreaDataEntity[[System.__Canon, mscorlib]](System.String, System.String, System.String)
...

上面的信息不难发现 30 号线程正卡在 RedisConnectionHelp.get_Instance() 处,33 号线已经进入了 RedisConnectionHelp.get_Instance() 方法中,最后在 GetConfig() 处等待 Result 的结果,按经验来说,30 号线程看样子正在锁等待, 33 号正在等待异步结果,接下来的突破点就是探究下 RedisConnectionHelp.Instance 处代码。

3. 寻找问题代码

接下来用反编译工具 ILSpy 找到问题代码。

public static class RedisConnectionHelp
{public static ConnectionMultiplexer Instance{get{if (_instance == null){lock (Locker){if (_instance == null || !_instance.IsConnected){_instance = GetManager();}}}return _instance;}}
}

30 号线程果然是卡在 Locker 处,接下来深挖下 33 号线程所执行的 GetManager() 方法,简化后代码如下:

public T this[string dataId, string key = ""]
{get{try{string config = GetConfig(configListener, new NacosConfigClient(Base.NacosConfiguration).DoGetConfigAsync);return JsonConvert.DeserializeObject<T>(config);}catch (Exception ex){return default(T);}}
}private string GetConfig(ConfigListener listener, Func<GetConfigRequest, Task<string>> action)
{var text2 = action(new GetConfigRequest{DataId = listener.DataId,Group = listener.Group,Tenant = text}).Result;return text2;
}internal async Task<string> DoGetConfigAsync(GetConfigRequest request)
{IRestResponse restResponse = await HttpUtil.Request(currentServerAddr, Method.GET, request.ParamValues(), xxx);return restResponse.Content;
}

可以看到代码卡在了 Result 上无限期等待,到这里我就想到了 同步上下文 ,我看他这个程序是 .NET 4.8 下的 ASP.NET MVC,记得不错上下文应该是 AspNetSynchronizationContext,具体死锁原因可参见我的这篇文章:一句 Task.Result 就死锁, 这代码还怎么写?,解决办法大概有四种。

  1. 使用 .ConfigureAwait(false)

  2. 改成全异步

  3. 用 Task 再包一层。

  4. 改成全同步

三:总结

其实本次事故主要还是因为在 同步代码 中做了 异步代码.Result 导致的死锁问题,有非常多的文章都在抨击这种现象,在 asp.net core 中已经移除了这种同步上下文的大坑,给到朋友的建议是改成全同步,死锁问题也随之消失。

e47005df16b693ee64c461f3bbbd87cb.png

哈哈,真替朋友开心!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/292622.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何定位Source Generators性能问题

前言在以前的文章中&#xff0c;我们介绍过如何调试Source Generators。但是当实现较为复杂时&#xff0c;我们需要能够快速地分析Source Generators性能的方法。默认情况下&#xff0c;使用VS 2019自带的“性能探查器”不能分析Source Generators项目&#xff0c;因为它是类库…

什么是域(domain)

在今天很多人都有意识或无意识的跟域这个东西打过交道。如果你在公司里使用电脑&#xff0c;并且你的电脑接入了公司的局域网&#xff0c;那你的电脑很可能就在一个域中。如何查看你的电脑是否连接到一个域中&#xff0c;以Windows为例&#xff0c;右击我的电脑 –>属性&…

四舍五入运算

为什么80%的码农都做不了架构师&#xff1f;>>> 请用户输入1个实数(浮点型),请编写算法对这个实数进行四舍五入到个位的运算. 例如 12.56经过四舍五入运算得到13. 而12.45经过四舍五入运算得到12 代码如下地址: http://git.oschina.net/touch1D/qf17j3eughwdp8zxi…

python 新建html_Python学习第226课——html中创建按钮

前面我们学习了一些常见的表单元素&#xff0c;表单就是用来收集用户的信息的&#xff0c;比如调查问卷、用户登录网站的页面等这类的网页&#xff0c;里面都要用到表单元素&#xff0c;当用户在前端页面上填入账号、密码、或者调查问卷的答案等等信息之后&#xff0c;就需要提…

WPF 实现图片切成九宫格控件~

WPF开发者QQ群&#xff1a; 340500857由于微信群人数太多入群请添加小编微信号yanjinhuawechat 或 W_Feng_aiQ 邀请入群需备注WPF开发者 PS&#xff1a;有更好的方式欢迎推荐。接着上一篇倒计时控件01—代码如下一、创建 CropControl.cs代码如下。&#xff08;修改RowColumn “…

GCT之数学公式(三角函数)

转载于:https://www.cnblogs.com/jyh317/p/3386598.html

Android安全与逆向之Java虚拟机和Dalvik虚拟机的区别

Google于2007年底正式发布了Android SDK, 作为 Android系统的重要特性&#xff0c;Dalvik虚拟机也第一次进入了人们的视野。它对内存的高效使用&#xff0c;和在低速CPU上表现出的高性能&#xff0c;确实令人刮目相看。 依赖于底层Posix兼容的操作系统&#xff0c;它可以简单的…

软件与硬件我该选哪个_MacBook Air 和 MacBook Pro 该选哪个……?

一个多月前&#xff0c;Apple 发布了 2019 款 MacBook Air 和 MacBook Pro&#xff0c;经过这一次的更新之后&#xff0c;MacBook 产品线总算变得更加清晰了&#xff0c;同时也减少了之前存在的价格和定位产生冲突的现象。不过从购买决策来看&#xff0c;我发现还是有些问题难倒…

只要300页!火遍全网的NET6+linux知识手册!拿走不谢!

这是微软公司基于最新的.net 6编写&#xff0c;循序渐进地对.net6/C#10进行讲解。对于零基础可以作为.net的快速入门教材&#xff0c;对于高级程序员而言&#xff0c;这也是你的进阶之路&#xff01;NO.1资料介绍该手册,全面的介绍.net6和c#的新特性&#xff0c;看完这个资料&a…

Linux0.11内核剖析--内核体系结构

一个完整可用的操作系统主要由 4 部分组成&#xff1a;硬件、操作系统内核、操作系统服务和用户应用程序&#xff0c;如下图所示&#xff1a; 用户应用程序是指那些字处理程序、 Internet 浏览器程序或用户自行编制的各种应用程序&#xff1b; 操作系统服务程序是指那些向用户所…

ubuntu上最使用jni最简单易懂的例子

第一步:爆结果照,让你有坚持下去的信心 二、NDK解释 NDK全称:Native Development Kit。 NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的。 NDK集成了交叉编译器,并提供了相应的m…

buck变换器设计matlab_一种用于Boost PFC变换器的改进关断时间控制策略

随着我国工业的发展&#xff0c;越来越多的非线性负荷接入电网&#xff0c;各种换流设备的使用&#xff0c;使电网的电压波形发生畸变&#xff0c;造成电能质量下降&#xff0c;威胁电网和各种用电设备的安全、经济运行。为了能够确保电网安全&#xff0c;研制出了各种PFC变换器…

你初吻啥时候没有的​?​

1 &#xff1f;&#xff1f;&#xff1f;&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼2 世界上最难受的三件事&#xff08;via.惨绿少年金三坨&#xff0c;侵删&#xff09;▼3 漏网之鱼▼4 有哪里不太对劲&#xff1f;&#xff08;素材来源网络&#xff0c;侵删…

乡亲们,我们创建了 Dapr 中文交流频道

我们创建了 Dapr 中文交流 QQ 频道&#xff0c;欢迎大家加入&#xff01;加入方式在文章最后一节。为什么要创建频道&#xff1f; 解决什么问题**专业性&#xff0c;“你可以在我们群里面钓鱼&#xff0c;因为都是水”** 你肯定加过非常多的这种叫什么技术交流群。你会发现这个…

Maven搭建SpringMVC+Mybatis项目详解【转】

为什么80%的码农都做不了架构师&#xff1f;>>> 前言 最近比较闲&#xff0c;复习搭建一下项目&#xff0c;这次主要使用SpringSpringMVCMybatis。项目持久层使用Mybatis3&#xff0c;控制层使用SpringMVC4.1&#xff0c;使用Spring4.1管理控制器&#xff0c;数据库…

Linux下最简单的修改文件名后缀的命令行技巧

在linux上面一直没有找到好的修改文件的方法&#xff0c;今天找到了一个 mv file.{cpp,c} 上面的意思就是说把文件file结尾为cpp改成c文件格式 看下图 &#xfeff;&#xfeff;

实时事件日志记录和聚合的平台——Sentry

不管你用什么编程语言&#xff0c;都会面临如何处理错误日志的问题。很多程序员对错误日志放任自流&#xff0c;直到出现故障了才追悔莫及&#xff0c;如果问小编怎么办&#xff0c;我会给你推荐 Sentry&#xff01;什么是Sentry&#xff1f;无论测试如何完善的程序&#xff0c…