经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » ASP.net » 查看文章
记一次 .NET某网络边缘计算系统 卡死分析
来源:cnblogs  作者:一线码农  时间:2024/7/1 13:10:38  对本文有异议

一:背景

1. 讲故事

早就听说过有什么 网络边缘计算,这次还真给遇到了,有点意思,问了下 chatgpt 这是干嘛的 ?

网络边缘计算是一种计算模型,它将计算能力和数据存储位置从传统的集中式数据中心向网络边缘的用户设备、传感器和其他物联网设备移动。这种模型的目的是在接近数据生成源头的地方提供更快速的计算和数据处理能力,从而减少数据传输延迟并提高服务质量。网络边缘计算使得在设备本地进行数据处理和决策成为可能,同时也有助于减轻对中心数据中心的网络流量和负载。

看到.NET还有这样的应用场景还是挺欣慰的,接下来就来分析下这个dump到底是怎么回事?

二:WinDbg 分析

1. 为什么会卡死

不同程序的卡死有不同的分析方式,所以要先鉴别下程序的类型以及主线程的调用栈即可,参考如下:

  1. 0:000> !eeversion
  2. 5.0.721.25508
  3. 5.0.721.25508 @Commit: 556582d964cc21b82a88d7154e915076f6f9008e
  4. Server mode with 64 gc heaps
  5. SOS Version: 8.0.10.10501 retail build
  6. 0:000> k
  7. # Child-SP RetAddr Call Site
  8. 00 0000ffff`e0dddac0 0000fffd`c194c30c libpthread_2_28!pthread_cond_wait+0x238
  9. ...
  10. 18 (Inline Function) --------`-------- libcoreclr!RunMain::$_0::operator()::{lambda(Param *)#1}::operator()+0x14c [/__w/1/s/src/coreclr/src/vm/assembly.cpp @ 1536]
  11. 19 (Inline Function) --------`-------- libcoreclr!RunMain::$_0::operator()+0x188 [/__w/1/s/src/coreclr/src/vm/assembly.cpp @ 1538]
  12. 1a 0000ffff`e0dde600 0000fffd`c153e860 libcoreclr!RunMain+0x298 [/__w/1/s/src/coreclr/src/vm/assembly.cpp @ 1538]
  13. ...
  14. 20 0000ffff`e0dded10 0000fffd`c1bf7800 libhostpolicy!corehost_main+0xc0 [/root/runtime/src/installer/corehost/cli/hostpolicy/hostpolicy.cpp @ 409]
  15. 21 (Inline Function) --------`-------- libhostfxr!execute_app+0x2c0 [/root/runtime/src/installer/corehost/cli/fxr/fx_muxer.cpp @ 146]
  16. 22 (Inline Function) --------`-------- libhostfxr!<unnamed-namespace>::read_config_and_execute+0x3b4 [/root/runtime/src/installer/corehost/cli/fxr/fx_muxer.cpp @ 520]
  17. 23 0000ffff`e0ddeeb0 0000fffd`c1bf6840 libhostfxr!fx_muxer_t::handle_exec_host_command+0x57c [/root/runtime/src/installer/corehost/cli/fxr/fx_muxer.cpp @ 1001]
  18. 24 0000ffff`e0ddf000 0000fffd`c1bf4090 libhostfxr!fx_muxer_t::execute+0x2ec
  19. 25 0000ffff`e0ddf130 0000aaad`c9e1d22c libhostfxr!hostfxr_main_startupinfo+0xa0 [/root/runtime/src/installer/corehost/cli/fxr/hostfxr.cpp @ 50]
  20. 26 0000ffff`e0ddf200 0000aaad`c9e1d468 dotnet!exe_start+0x36c [/root/runtime/src/installer/corehost/corehost.cpp @ 239]
  21. 27 0000ffff`e0ddf370 0000fffd`c1c63fe0 dotnet!main+0x90 [/root/runtime/src/installer/corehost/corehost.cpp @ 302]
  22. 28 0000ffff`e0ddf3b0 0000aaad`c9e13adc libc_2_28!_libc_start_main+0xe0
  23. 29 0000ffff`e0ddf4e0 00000000`00000000 dotnet!start+0x34

从卦中的指标来看,这是一个 Linux 上部署的 Web网站,既然是网站的卡死,那就要关注各个线程都在做什么。

2. 线程都在干嘛

以我多年的分析经验,绝大多数都是由于 线程饥饿 或者说 线程池耗尽 导致的,首先我们看下线程池的情况。

  1. 0:000> !t
  2. ThreadCount: 365
  3. UnstartedThread: 0
  4. BackgroundThread: 354
  5. PendingThread: 0
  6. DeadThread: 10
  7. Hosted Runtime: no
  8. Lock
  9. DBG ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
  10. 0 1 31eaf 0000AAADF267C600 2020020 Preemptive 0000000000000000:0000000000000000 0000aaadf26634b0 -00001 Ukn
  11. ...
  12. 423 363 36d30 0000FFDDB4000B20 1020220 Preemptive 0000000000000000:0000000000000000 0000aaadf26634b0 -00001 Ukn (Threadpool Worker)
  13. 424 364 36d31 0000FFDDA8000B20 1020220 Preemptive 0000000000000000:0000000000000000 0000aaadf26634b0 -00001 Ukn (Threadpool Worker)
  14. 425 365 36d32 0000FFDDAC000B20 1020220 Preemptive 0000000000000000:0000000000000000 0000aaadf26634b0 -00001 Ukn (Threadpool Worker)
  15. 0:000> !tp
  16. Using the Portable thread pool.
  17. CPU utilization: 9%
  18. Workers Total: 252
  19. Workers Running: 236
  20. Workers Idle: 13
  21. Worker Min Limit: 64
  22. Worker Max Limit: 32767
  23. Completion Total: 0
  24. Completion Free: 0
  25. Completion MaxFree: 128
  26. Completion Current Limit: 0
  27. Completion Min Limit: 64
  28. Completion Max Limit: 1000

从卦中看当前有 365 个托管线程,这个算多吗?对于64core 来说,这个线程其实算是正常,训练营里的朋友都知道,server版的gc仅gc线程就有 64*2=128 个,接下来再看一个指标就是当前是否存在任务积压? 可以使用 !ext tpq 命令,参考输出如下:

  1. 0:000> !ext tpq
  2. global work item queue________________________________
  3. local per thread work items_____________________________________

从卦中看当前没有任务积压,这就有点反经验了。

3. 真的不是线程饥饿吗

最后一招比较彻底,就是看各个线程栈都在做什么,可以使用 ~*e !clrstack 命令。

这不看不知道,一看吓一跳,有 193 个线程在 Task.Result 上等待,这玩意太经典了,然后从上面的调用栈 UIUpdateTimer_Elapsed 来看,貌似是一个定时器导致的,接下来我就好奇这代码是怎么写的?

分析上面的代码之后,我发现它是和 Linux Shell 窗口进行命令交互,不知道为何 Shell 没有响应导致代码在这里卡死。

4. 为什么线程池没有积压

相信有很多朋友对这个反经验的东西很好奇为什么请求没有积压在线程池,其实这个考验的是你对 PortableThreadPool 的底层了解,这里我就简单说一下吧。

  1. 在 ThreadPool 中有一个 GateThread 线程是专门给线程池动态注入线程的,参考代码如下:
  1. private static class GateThread
  2. {
  3. private static void GateThreadStart()
  4. {
  5. while (true)
  6. {
  7. bool wasSignaledToWake = DelayEvent.WaitOne((int)delayHelper.GetNextDelay(tickCount));
  8. WorkerThread.MaybeAddWorkingWorker(threadPoolInstance);
  9. }
  10. }
  11. }
  1. 一旦有人调用了 Task.Result 代码,内部会主动唤醒 DelayEvent 事件,告诉 GateThread 赶紧通过 MaybeAddWorkingWorker 方法给我注入新的线程,参考代码如下:
  1. private bool SpinThenBlockingWait(int millisecondsTimeout, CancellationToken cancellationToken)
  2. {
  3. bool flag3 = ThreadPool.NotifyThreadBlocked();
  4. }
  5. internal static bool NotifyThreadBlocked()
  6. {
  7. if (UsePortableThreadPool)
  8. {
  9. return PortableThreadPool.ThreadPoolInstance.NotifyThreadBlocked();
  10. }
  11. return false;
  12. }
  13. public bool NotifyThreadBlocked()
  14. {
  15. GateThread.Wake(this);
  16. }

上面这种主动唤醒的机制是 C# 版 PortableThreadPool 做的优化来缓解线程饥饿的,这里有一个重点就是它只能缓解,换句话说如果上游太猛了还是会有请求积压的,但为什么这里没有积压呢? 很显然上游不猛呗,那如何眼见为实呢? 这就需要看 timer 的周期数即可,到当前的线程栈上给扒出来。

  1. 0:417> !DumpObj /d 0000ffee380757f8
  2. Name: System.Timers.Timer
  3. MethodTable: 0000fffd4ab24030
  4. EEClass: 0000fffd4ad6e140
  5. Size: 88(0x58) bytes
  6. File: /home/user/env/dotnet/shared/Microsoft.NETCore.App/5.0.7/System.ComponentModel.TypeConverter.dll
  7. Fields:
  8. MT Field Offset Type VT Attr Value Name
  9. 0000fffd4c947498 400001c 8 ...ponentModel.ISite 0 instance 0000000000000000 _site
  10. 0000000000000000 400001d 10 ....EventHandlerList 0 instance 0000000000000000 _events
  11. 0000fffd479195d8 400001b 98 System.Object 0 static 0000000000000000 s_eventDisposed
  12. 0000fffd47926f60 400000e 40 System.Double 1 instance 3000.000000 _interval
  13. 0000fffd4791fb10 400000f 48 System.Boolean 1 instance 1 _enabled
  14. 0000fffd4791fb10 4000010 49 System.Boolean 1 instance 0 _initializing
  15. 0000fffd4791fb10 4000011 4a System.Boolean 1 instance 0 _delayedEnable
  16. 0000fffd4ab241d8 4000012 18 ...apsedEventHandler 0 instance 0000ffee3807aae8 _onIntervalElapsed
  17. 0000fffd4791fb10 4000013 4b System.Boolean 1 instance 1 _autoReset
  18. 0000fffd4c944ea0 4000014 20 ...SynchronizeInvoke 0 instance 0000000000000000 _synchronizingObject
  19. 0000fffd4791fb10 4000015 4c System.Boolean 1 instance 0 _disposed
  20. 0000fffd49963e28 4000016 28 ...m.Threading.Timer 0 instance 0000ffee38098dc8 _timer
  21. 0000fffd48b90a30 4000017 30 ...ing.TimerCallback 0 instance 0000ffee3807aaa8 _callback
  22. 0000fffd479195d8 4000018 38 System.Object 0 instance 0000ffee38098db0 _cookie

从卦中看当前是 3s 为一个周期,这就能解释为什么线程池没有积压的底层原因了。

三:总结

这个卡死事故还是蛮好解决的,如果有一些经验直接用dotnet-counter也是能搞定的,重点在于这是一个 Linux的dump,同时又是 .NET上的一个很好玩的场景,故此分享出来。

图片名称

原文链接:https://www.cnblogs.com/huangxincheng/p/18277831

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号