记一次 .NET某酒店后台服务 卡死分析
一:背景
1. 讲故事
停了一个月没有更新文章了,主要是忙于写 C#内功修炼系列的PPT
,现在基本上接近尾声,可以回头继续更新这段时间分析dump的一些事故报告,有朋友微信上找到我,说他们的系统出现了大量的http超时,程序不响应处理了,让我帮忙看下怎么回事,dump也抓到了。
二:WinDbg分析
1. 为什么会出现请求超时
既然超时说明server端不响应这个请求,继而达到了超时时间的一种异常情况,所以首先要想到的就是 线程池的健康度
,可以用 !tp
命令观察,输出如下:
0:000> !tp
CPU utilization: 0%
Worker Thread: Total: 537 Running: 537 Idle: 0 MaxLimit: 32767 MinLimit: 12
Work Request in Queue: 82
Unknown Function: 00007fff566a17d0 Context: 0000020f08cbd658
Unknown Function: 00007fff566a17d0 Context: 0000020f09acfa80
Unknown Function: 00007fff566a17d0 Context: 0000020f08702198
Unknown Function: 00007fff566a17d0 Context: 0000020f09ad9068
Unknown Function: 00007fff566a17d0 Context: 0000020f09abffe8
Unknown Function: 00007fff566a17d0 Context: 0000020f093c9948
Unknown Function: 00007fff566a17d0 Context: 0000020f093cfd28
Unknown Function: 00007fff566a17d0 Context: 0000020f093d9358
Unknown Function: 00007fff566a17d0 Context: 0000020f093c34e8
Unknown Function: 00007fff566a17d0 Context: 0000020f093dc568
...
--------------------------------------
Number of Timers: 2
--------------------------------------
Completion Port Thread:Total: 2 Free: 2 MaxFree: 24 CurrentLimit: 2 MaxLimit: 1000 MinLimit: 12
从上面的卦象看异常非常明显,线程池总共有 537个工作线程都是处于运行状态,相信有经验的朋友应该一眼就知道是怎么回事,专业术语叫:线程饥饿
,并且线程池队列也积压了 82个 待处理的任务。
2. 线程为什么会饥饿
线程饥饿的原因有更多,我特意问了下 chatgpt,列举如下:
- 优先级倾斜:如果某些线程的优先级设置过高,而其他线程的优先级设置过低,高优先级的线程可能会长时间占用CPU资源,导致低优先级线程无法获得执行机会。
- 死锁:当多个线程相互等待对方释放资源时,可能会导致死锁。在死锁情况下,所有线程都无法继续执行,从而导致线程饥饿。
- 资源竞争:多个线程竞争有限的资源(如共享内存、文件、网络连接等)时,可能会导致某些线程长时间无法获取到所需的资源而处于饥饿状态。
- 不公平的调度策略:调度器可能存在不公平的调度策略,导致某些线程无法获得公平的CPU时间片,从而长时间无法执行。
- 线程阻塞:某些线程可能由于等待I/O操作、锁或其他原因而被阻塞,如果阻塞时间过长,可能导致其他线程饥饿。
- 线程池配置不当:如果线程池中的线程数量设置不当,可能会导致某些任务长时间等待执行,从而引发线程饥饿。
那到底是哪一种情况呢?可以用 ~*e !clrstack
看一下各个线程此时正在做什么,输出如下:
0:000> ~*e !clrstack
...
OS Thread Id: 0x2924 (74)
Child SP IP Call Site
000000e0ef47dc30 00007fff60fd6974 [GCFrame: 000000e0ef47dc30]
000000e0ef47dd58 00007fff60fd6974 [HelperMethodFrame_1OBJ: 000000e0ef47dd58] System.Threading.Monitor.ObjWait(Boolean, Int32, System.Object)
000000e0ef47de70 00007ffef33e7269 System.Threading.ManualResetEventSlim.Wait(Int32, System.Threading.CancellationToken)
000000e0ef47df00 00007ffef33e6b58 System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken)
000000e0ef47df70 00007ffef33e69e1 System.Threading.Tasks.Task.InternalWait(Int32, System.Threading.CancellationToken)
000000e0ef47e040 00007ffef60cce33 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task)
000000e0ef47e070 00007ffef9df2c73 Exceptionless.Submission.DefaultSubmissionClient.SendHeartbeat(System.String, Boolean, Exceptionless.ExceptionlessConfiguration)
000000e0ef47e110 00007ffef109f03f System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
000000e0ef47e1e0 00007ffef109e784 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
000000e0ef47e210 00007ffef15b670b System.Threading.TimerQueueTimer.CallCallback()
000000e0ef47e270 00007ffef15b644d System.Threading.TimerQueueTimer.Fire()
000000e0ef47e2e0 00007ffef15b5613 System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
000000e0ef47e320 00007ffef10b8319 System.Threading.ThreadPoolWorkQueue.Dispatch()
000000e0ef47e7a0 00007fff4fa06993 [DebuggerU2MCatchHandlerFrame: 000000e0ef47e7a0]
000000e0ef47e908 00007fff4fa06993 [ContextTransitionFrame: 000000e0ef47e908]
000000e0ef47eb40 00007fff4fa06993 [DebuggerU2MCatchHandlerFrame: 000000e0ef47eb40]
...
发现有 473 个线程都在 Exceptionless.Submission.DefaultSubmissionClient.SendHeartbeat
方法上进行等待,这就有意思了,原来是开源的日志收集组件
发送的心跳检测方法,接下来赶紧看一下这个方法的源码。
public void SendHeartbeat(string sessionIdOrUserId, bool closeSession, ExceptionlessConfiguration config)
{
if (!config.IsValid)
{
return;
}
string requestUri = $"{GetHeartbeatServiceEndPoint(config)}/events/session/heartbeat?id={sessionIdOrUserId}&close={closeSession}";
try
{
_client.Value.AddAuthorizationHeader(config.ApiKey);
_client.Value.GetAsync(requestUri).ConfigureAwait(continueOnCapturedContext: false).GetAwaiter()
.GetResult();
}
catch (Exception exception)
{
config.Resolver.GetLog().Error("Error submitting heartbeat: " + exception.GetMessage());
}
}
从源码看,居然用同步的方式发送 http请求
,在这异步方法满天飞的世界里,上面的写法实属异类。
3. 该如何解决呢?
既然是 Exceptionless 内部写的 SendHeartbeat 方法,我们程序员基本上无法干预,能做到的无非如下两点:
- 升级框架
看下了用的还是超老的 4.3
版本,可以升级到目前最新的 6.0.4
观察试试。
[assembly: AssemblyTitle("Exceptionless")]
[assembly: AssemblyProduct("Exceptionless")]
[assembly: AssemblyCompany("Exceptionless")]
[assembly: AssemblyTrademark("Exceptionless")]
[assembly: AssemblyCopyright("Copyright (c) 2017 Exceptionless. All rights reserved.")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("4.3.2027.0")]
[assembly: AssemblyInformationalVersion("4.3.2027$(VERSION_SUFFIX) f8d73f2fd7")]
[assembly: TargetFramework(".NETFramework,Version=v4.5", FrameworkDisplayName = ".NET Framework 4.5")]
[assembly: AssemblyVersion("4.3.2027.0")]
- 使用替代品,或者不用
哈哈,不用它,这是万能的治根之法。
三:对线程注入速度的解答
1. 朋友提了一个疑问
我现在知道这个 url 某个时段可能响应出了问题,但我线程池里的线程增速应该很快呀,多余的线程不是可以响应客户端请求吗?为什么我发现的情况是全部卡死呢?
2. 疑问的简单解答
这个问题其实是考察对线程池底层的了解,尤其是多久会向线程池注入一个活线程,在 .NET Framework 时代,在线程饥饿的情况下线程池内部的 GateThread线程 会 1s 注入一个活线程,那如何验证呢? 我们观察后续的线程创建时间即可,使用 ~*e .ttime
。
0:000> ~*e .ttime
...
Created: Thu Nov 16 11:10:21.582 2023 (UTC + 8:00)
Kernel: 0 days 0:00:00.000
User: 0 days 0:00:00.000
Created: Thu Nov 16 11:10:22.593 2023 (UTC + 8:00)
Kernel: 0 days 0:00:00.000
User: 0 days 0:00:00.000
Created: Thu Nov 16 11:10:23.562 2023 (UTC + 8:00)
Kernel: 0 days 0:00:00.000
User: 0 days 0:00:00.000
Created: Thu Nov 16 11:10:24.062 2023 (UTC + 8:00)
Kernel: 0 days 0:00:00.000
User: 0 days 0:00:00.000
Created: Thu Nov 16 11:10:24.577 2023 (UTC + 8:00)
Kernel: 0 days 0:00:00.000
User: 0 days 0:00:00.000
Created: Thu Nov 16 11:10:25.562 2023 (UTC + 8:00)
Kernel: 0 days 0:00:00.000
User: 0 days 0:00:00.000
Created: Thu Nov 16 11:10:26.562 2023 (UTC + 8:00)
Kernel: 0 days 0:00:00.000
User: 0 days 0:00:00.015
Created: Thu Nov 16 11:10:27.562 2023 (UTC + 8:00)
Kernel: 0 days 0:00:00.000
User: 0 days 0:00:00.015
Created: Thu Nov 16 11:10:28.562 2023 (UTC + 8:00)
Kernel: 0 days 0:00:00.000
User: 0 days 0:00:00.015
Created: Thu Nov 16 11:10:29.577 2023 (UTC + 8:00)
Kernel: 0 days 0:00:00.000
User: 0 days 0:00:00.015
Created: Thu Nov 16 11:10:30.562 2023 (UTC + 8:00)
Kernel: 0 days 0:00:00.000
User: 0 days 0:00:00.000
从卦中的输出来看,每一个 Created 大概差 1s 钟,这也是 GateThread 的功劳,这种注入速度在 .NET8 中已经做了优化,比如上面这种情况,Task 内部会主动唤醒 GateThread 线程让其立即注入新线程,从而提升程序的响应速度。
四:总结
很多时候分析下来发现是 第三方组件
拖垮了程序,自己又没有太多的介入能力,真的很无奈,框架都用了那么久,现在看到了一只苍蝇,已是食之无味,弃之可惜。
记一次 .NET某酒店后台服务 卡死分析的更多相关文章
- 记一次 .NET 某物管后台服务 卡死分析
一:背景 1. 讲故事 这几个月经常被朋友问,为什么不更新这个系列了,哈哈,确实停了好久,主要还是打基础去了,分析 dump 的能力不在于会灵活使用 windbg,而是对底层知识有一个深厚的理解,比如 ...
- 记一次 .NET 某企业OA后端服务 卡死分析
一:背景 1.讲故事 前段时间有位朋友微信找到我,说他生产机器上的 Console 服务看起来像是卡死了,也不生成日志,对方也收不到我的httpclient请求,不知道程序出现什么情况了,特来寻求帮助 ...
- 记一次 .NET 差旅管理后台 CPU 爆高分析
一:背景 1. 讲故事 前段时间有位朋友在微信上找到我,说他的 web 系统 cpu 运行一段时候后就爆高了,让我帮忙看一下是怎么回事,那就看吧,声明一下,我看 dump 是免费的,主要是锤炼自己技术 ...
- 记一次 .NET 某药品仓储管理系统 卡死分析
一:背景 1. 讲故事 这个月初,有位朋友wx上找到我,说他的api过一段时间后,就会出现只有请求,没有响应的情况,截图如下: 从朋友的描述中看样子程序是被什么东西卡住了,这种卡死的问题解决起来相对简 ...
- 记一次 .NET 某金融企业 WPF 程序卡死分析
一:背景 1. 讲故事 前段时间遇到了一个难度比较高的 dump,经过几个小时的探索,终于给找出来了,在这里做一下整理,希望对大家有所帮助,对自己也是一个总结,好了,老规矩,上 WinDBG 说话. ...
- 记一次 .NET 某数控机床控制程序 卡死分析
一:背景 1. 讲故事 前段时间有位朋友微信上找到我,说它的程序出现了卡死,让我帮忙看下是怎么回事? 说来也奇怪,那段时间求助卡死类的dump特别多,被迫训练了一下对这类问题的洞察力 ,再次声明一下, ...
- 记在Linux上定位后台服务偶发崩溃的问题
问题描述 在最近的后台服务中,新增将某个指令的请求数据落盘保存的功能.在具体实现时,采用成员变量来保存请求消息代理头,在接收响应以及消息管理类释放时进行销毁.测试反馈,该服务偶发崩溃. 问题分析 测试 ...
- iPhone Anywehre虚拟定位提示“后台服务未启动,请重新安装应用后使用”的解决方法
问题描述: iPhone越狱了,之后在Cydia中安装Anywhere虚拟定位,但是打开app提示:后台服务未启动,请重新安装应用后使用. 程序无法正常使用... 解决方法: 打开Cydia-已安装, ...
- 带后台服务配置的tomcat使用
tomcat服务启动,将不需要手动启动startup.bat,避免cmd窗口的出现,因为隐藏到后台服务执行: 1,下载. 官网:http://tomcat.apache.org/download-70 ...
- Android 三级联动选择城市+后台服务加载数据库
技术渣,大家将就着看 首先我们需要一个xml数据保存到数据库,这里我从QQ下面找到一个loclist.xml文件 <CountryRegion Name="中国" Code= ...
随机推荐
- 【中秋国庆不断更】OpenHarmony多态样式stateStyles使用场景
@Styles和@Extend仅仅应用于静态页面的样式复用,stateStyles可以依据组件的内部状态的不同,快速设置不同样式.这就是我们本章要介绍的内容stateStyles(又称为:多态样式). ...
- 牛蛙!GoFrame2.7正式版的监控组件真是及时雨
声明:本文首发在同名公众号:王中阳Go,未经授权禁止转载. GoFrame框架今天发布了v2.7.0正式版本啦! 最大看点 本次版本最大的看点是提供了metric监控组件,主库提供了接口化的metri ...
- 【直播回顾】Hello HarmonyOS系列应用篇完美收官!
6月15日晚上19点,Hello HarmonyOS系列应用篇第七期直播 <分布式应用开发>,在HarmonyOS社群内成功举行.随着本系列直播最后一课的完美收官,开发者们在逐渐掌握技术知 ...
- 第八篇:socket网络编程
一.网络编程简绍 二.socket连接过程 三.socket文件传输 四.socket循环接收 五.socket粘包处理 六.FTP文件传输 七.socketServer 八.web框架 #!/usr ...
- centos 6.4下fdisk分区、格式化、挂载新硬盘
centos 6.4下fdisk分区.格式化.挂载新硬盘 作者: cat 日期: 2013 年 9 月 10 日 发表评论 (0) 查看评论 1.# fdisk -l 查看当前磁盘信息,就会发现最下面 ...
- Flask搭建APP统一管理平台
主页效果: 1.从数据库中获取所有APP的信息,每个卡片上展示APP名称.bundle id.版本构建历史记录,系统类型等构建信息 2.支持查询筛选,模糊查询 3.点击历史记录跳转APP历史记录详情页 ...
- 力扣233(java)-数字1的个数(困难)
题目: 给定一个整数 n,计算所有小于等于 n 的非负整数中数字 1 出现的个数. 示例 1: 输入:n = 13输出:6示例 2: 输入:n = 0输出:0 提示: 0 <= n <= ...
- 跃居AppStore第一!X-Engine如何支撑钉钉数据量激增
钉钉作为国内领先的企业IM工具,在中国有超过亿级别的用户.随着新型冠状病毒肺炎疫情的爆发,大量的企业员工选择了soho模式,企业办公协同工具的需求瞬间爆发. 钉钉作为中国企业办公IM的首选应用,不仅具 ...
- 一个好的科技公司logo长这样!
简介:一个好的科技logo能体现出行业独有的专业性和技术优势,让你的公司科技感加满! 近年来,越来越多的初创公司崭露头角,其中科技互联网公司的比重非常高.小云也收到很多朋友的留言,询问科技类公司应该 ...
- 阿里云CDN产品经理陈章炜:边缘创新技术和落地实践
简介: CDN除了加速外,不断被赋予更多价值.在阿里云CDN推出的<极速奔跑吧 2021>首场直播中,阿里云架构师和产品经理不仅对近期阿里云发布的CDN产品最佳实践图进行了详细解读,还对C ...