一:背景

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,列举如下:

  1. 优先级倾斜:如果某些线程的优先级设置过高,而其他线程的优先级设置过低,高优先级的线程可能会长时间占用CPU资源,导致低优先级线程无法获得执行机会。
  2. 死锁:当多个线程相互等待对方释放资源时,可能会导致死锁。在死锁情况下,所有线程都无法继续执行,从而导致线程饥饿。
  3. 资源竞争:多个线程竞争有限的资源(如共享内存、文件、网络连接等)时,可能会导致某些线程长时间无法获取到所需的资源而处于饥饿状态。
  4. 不公平的调度策略:调度器可能存在不公平的调度策略,导致某些线程无法获得公平的CPU时间片,从而长时间无法执行。
  5. 线程阻塞:某些线程可能由于等待I/O操作、锁或其他原因而被阻塞,如果阻塞时间过长,可能导致其他线程饥饿。
  6. 线程池配置不当:如果线程池中的线程数量设置不当,可能会导致某些任务长时间等待执行,从而引发线程饥饿。

那到底是哪一种情况呢?可以用 ~*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某酒店后台服务 卡死分析的更多相关文章

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

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

  2. 记一次 .NET 某企业OA后端服务 卡死分析

    一:背景 1.讲故事 前段时间有位朋友微信找到我,说他生产机器上的 Console 服务看起来像是卡死了,也不生成日志,对方也收不到我的httpclient请求,不知道程序出现什么情况了,特来寻求帮助 ...

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

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

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

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

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

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

  6. 记一次 .NET 某数控机床控制程序 卡死分析

    一:背景 1. 讲故事 前段时间有位朋友微信上找到我,说它的程序出现了卡死,让我帮忙看下是怎么回事? 说来也奇怪,那段时间求助卡死类的dump特别多,被迫训练了一下对这类问题的洞察力 ,再次声明一下, ...

  7. 记在Linux上定位后台服务偶发崩溃的问题

    问题描述 在最近的后台服务中,新增将某个指令的请求数据落盘保存的功能.在具体实现时,采用成员变量来保存请求消息代理头,在接收响应以及消息管理类释放时进行销毁.测试反馈,该服务偶发崩溃. 问题分析 测试 ...

  8. iPhone Anywehre虚拟定位提示“后台服务未启动,请重新安装应用后使用”的解决方法

    问题描述: iPhone越狱了,之后在Cydia中安装Anywhere虚拟定位,但是打开app提示:后台服务未启动,请重新安装应用后使用. 程序无法正常使用... 解决方法: 打开Cydia-已安装, ...

  9. 带后台服务配置的tomcat使用

    tomcat服务启动,将不需要手动启动startup.bat,避免cmd窗口的出现,因为隐藏到后台服务执行: 1,下载. 官网:http://tomcat.apache.org/download-70 ...

  10. Android 三级联动选择城市+后台服务加载数据库

    技术渣,大家将就着看 首先我们需要一个xml数据保存到数据库,这里我从QQ下面找到一个loclist.xml文件 <CountryRegion Name="中国" Code= ...

随机推荐

  1. js推送网页到扩展屏上--谷歌浏览器

    平时我们推送网页.打开窗口都是用的 window.open,但是谷歌却不支持这种方法,也不是不支持,是可以打开窗口,但是无法将窗口移动到扩展屏上. 后面经过百度,发现了一个支持谷歌推送网页到扩展屏的方 ...

  2. openGauss3.1.0企业版HA环境部署测试

    前言 openGauss 是华为开源的一款高性能关系型数据库,这两年感觉 pg 系的数据库在国内慢慢火起来了,pg 的操作还是跟 mysql 和 oracle 略有差距,还得慢慢学,先从部署开始吧.对 ...

  3. Godot Label样式 Textrue纹理,实现样式修改,背景填充

    目录 前言 运行环境 新建项目 Style 样式讲解 StyleBoxEmpty:普通样式 StyleBoxTexture:字体样式 StyleBoxFlat:填充样式 StyleBoxLine:行样 ...

  4. ArkUI,更高效的框架设计

     原文:https://mp.weixin.qq.com/s/uSIzuBby7Z92drNDmejKXw,点击链接查看更多技术内容.     上期文章我们讲到了ArkUI的三大特性,同时提到了Ark ...

  5. InnoDB常用锁总结(行锁、间隙锁、临键锁、表锁)

    相关文章 数据库系列:MySQL慢查询分析和性能优化 数据库系列:MySQL索引优化总结(综合版) 数据库系列:高并发下的数据字段变更 数据库系列:覆盖索引和规避回表 数据库系列:数据库高可用及无损扩 ...

  6. Jenkins集成GitLab的正确姿势,实现Git代码提交触发CI/CD

    ❝ jenkins和gitlab是目前DevOps工具链中最常见的,抛开gitlab-ci不谈,gitlab代码提交触发jenkins流水线是最经典的搭配. 这里就介绍下如何配置实现jenkins和g ...

  7. Apsara Stack 技术百科 | 如何「场景化」的企业上云

    简介: 企业上云离不开数据和业务上云,如何在确保安全的前提下,低成本高效率的平滑上云,在云上又能真正解决哪些实际业务问题?混合云君今天给大家讲讲最经典的三个场景~ 随着"十四五"规 ...

  8. 网易:Flink + Iceberg 数据湖探索与实践

    导读:今天主要和大家交流的是网易在数据湖 Iceberg 的一些思考与实践.从网易在数据仓库建设中遇到的痛点出发,介绍对数据湖 Iceberg 的探索以及实践之路. 主要内容包括: 数据仓库平台建设的 ...

  9. 前沿分享|阿里云数据库资深技术专家 姚奕玮:AnalyticDB MySQL离在线一体化技术揭秘

    ​简介: 本篇内容为2021云栖大会-云原生数据仓库AnalyticDB技术与实践峰会分论坛中,阿里云数据库资深技术专家 姚奕玮关于"AnalyticDB MySQL离在线一体化技术揭秘&q ...

  10. 延迟绑定与retdlresolve

    延迟绑定与retdlresolve 我们以前在ret2libc的时候,我们泄露的libc地址是通过延迟绑定实现的,我们知道,在调用libc里面的函数时候,它会先通过plt表和gor表绑定到,函数真实地 ...