找回密码
 立即注册
首页 业界区 业界 记一次 .NET某工控任务调度系统 卡死分析 ...

记一次 .NET某工控任务调度系统 卡死分析

益竹月 前天 22:23
一:背景

1. 讲故事

前段时间有位朋友加我微信,来了就要进我的训练营,并且附带着纠结了他几个月的一个疑难杂症,让我帮忙看下怎么回事,问题描述截图如下:
1.png

由于这个定时任务是 furion 写的,刚好这位学员是VIP客户,找了小僧大佬,大佬需要最小化的问题代码,由于不能本地复现,也就没下文了,毕竟也是触发了 13w 次之后才出现的问题,确实比较难搞,截图如下:
2.png

像这种带着问题进训练营的朋友还是蛮多的,对这类需求我也是严格,谨慎,认真的对待,毕竟是骡子是马,得要拉出来溜溜。
二:为什么会任务延迟

1. 初步分析

经过和学员的沟通和截图确认,是一个叫 M71EnterPortService 的服务出现的延迟,这种问题相对来说比较简单,可能任务卡死在某个地方,通过 ~*e !clrstack 观察下各个线程栈上是否有 M71EnterPortService 字样就能知道,截图如下:
3.png

从卦中看,尼玛,居然没有 M71EnterPortService 关键词,这说明任务压根就没执行?难度是任务被意外退出了吗? 但朋友截图出来的面板信息还是蛮全的,而且底层框架对这些容错性应该还是非常强的,所以个人推论,大概率不应该是任务退出,说实话,有点进入迷雾了。。。
2. 走出迷雾

要想走出迷雾,需要回头看下 M71EnterPortService 类的调度方法,源码方法参考如下:
  1.         [JobDetail("job_M71xxx", Description = "M7-1xxx作业", GroupName = "default", Concurrent = false)]
  2.         [Period(500L, TriggerId = "trigger_M71xxx", Description = "M7-1xxx作业")]
  3.         public class M71EnterPortService : IJob
  4.         {
  5.             public static string processCode = "M7-1A";
  6.             public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
  7.             {
  8.                 _ = string.Empty;
  9.                 try
  10.                 {
  11.                     plcProcessPara = await xxxService.CheckProcess(processCode);
  12.                     if (!plcProcessPara.IsProcess)
  13.                     {
  14.                         return;
  15.                     }
  16.                     ...
  17.                     if (string.IsNullOrWhiteSpace(rFIDReaderResultModel.RfidUid))
  18.                     {
  19.                         PLCxxxDriver.WritePlcxxxData($"PLC/{processCode}/xxxIPC", "2");
  20.                         await Task.Delay(2000);
  21.                     }
  22.                     await Task.CompletedTask;
  23.                 }
  24.                 catch (Exception ex)
  25.                 {
  26.                     xxxService.xxxWarn(produceRecord, plcProcessPara, ex);
  27.                 }
  28.                 await Task.CompletedTask;
  29.             }
  30.         }
复制代码
从源码看,这是一个纯异步的写法,看到这个纯异步我就想到了新版的sos提供了一个 !dumpasync 命令,专门观察状态机链的,输出如下:
  1. 0:000> !dumpasync
  2. ...
  3. STACK 9
  4. 0000025fc3a02f78 00007ff90f3f75e8 (-1) xxx.ComProcessService+<CheckProcess>d__1 @ 7ff90fdf02b0
  5.   0000025fc3a03008 00007ff90f3f82c0 (0) xxx.M71EnterPortService+<ExecuteAsync>d__3 @ 7ff90fe15a80
  6.     0000025fc3a03090 00007ff90f3f8a60 (0) Furion.Schedule.ScheduleHostedService+<>c__DisplayClass23_3+<<BackgroundProcessing>b__3>d @ 7ff90fdf0000
  7.       0000025fc3a030f8 00007ff90f3f8f58 (0) Furion.FriendlyException.Retry+<InvokeAsync>d__1 @ 7ff90fdef840
  8.         0000025fc3a03198 00007ff90f3f93d0 (1) Furion.Schedule.ScheduleHostedService+<>c__DisplayClass23_2+<<BackgroundProcessing>b__2>d @ 7ff90fded340
  9. ...
复制代码
我去,真的卡在 M71EnterPortService 下的 CheckProcess 中,接下来拿 CheckProcess 在所有线程栈上再次搜索,本以为有惊喜,同样毛都没有,我去。。。截图如下。
4.png

这就有点无语了,接下来我们观察下状态机地址0x0000025fc3a02f78 中的内部字段,从内部字段的赋值情况观察代码执行流,输出如下:
  1. 0:000> !dumpasync --address 0x0000025fc3a02f78   --fields
  2. STACK 1
  3. 0000025fc3a02f78 00007ff90f3f75e8 (-1) xxx.ComProcessService+<CheckProcess>d__1 @ 7ff90fdf02b0
  4.                  Address               MT Type                                        Value Name
  5.         0000025fc3a02fe0 00007ff90c3f7408 System.Int32                                   -1 <>1__state
  6.         0000025fc3a02fe8 00007ff90d872010 ...y.PLCPara.xxx> 0000025fc3a02ff0            <>t__builder
  7.         0000025f80683200 00007ff90c44bf40 System.String                             "M7-1A" processCode
  8.         0000025fc3a028e0 00007ff90d822c00 ...ty.PLCPara.xxx 0000025fc3a028e0 <result>5__2
  9.         0000025fc3a02978 00007ff90c4f5b00 System.IDisposable 0000025fc3a02978 <>7__wrap2
  10.         0000025faf0ac600 00007ff90d8228d0 ...tity.PLCPara.xxx 0000025faf0ac600 <processMark>5__4
  11.         0000025fc3a0aaf8 00007ff90d824358 ...ntity.PLCPara.xxx 0000025fc3a0aaf8 <plcPara>5__5
  12.         0000025fc3a02ff0 00007ff90d872118 ...tity.PLCPara.xxx> 0000025fc3a02ff8 <>u__1
  13.         0000025fc3a02ff8 00007ff90c98b920 ....CompilerServices.TaskAwaiter 0000025fc3a03000 <>u__2
  14.   0000025fc3a03008 00007ff90f3f82c0 (0) xxx.M71EnterPortService+<ExecuteAsync>d__3 @ 7ff90fe15a80
  15.                    Address               MT Type                                        Value Name
  16.           0000025fc3a03058 00007ff90c3f7408 System.Int32                                    0 <>1__state
  17.           0000025fc3a03060 00007ff90c98b540 ...rvices.AsyncTaskMethodBuilder 0000025fc3a03068 <>t__builder
  18.           0000000000000000 00007ff90d82ec98 ....DBModel.xxx             null <produceRecord>5__2
  19.           0000000000000000 00007ff90d822c00 ...ty.PLCPara.xxx             null <plcProcessPara>5__3
  20.           0000025fc3a03068 00007ff90d878de0 ...y.PLCPara.xxx> 0000025fc3a03070 <>u__1
  21.           0000025fc3a03070 00007ff90d879368 ...DBModel.xxx> 0000025fc3a03078 <>u__2
  22.           0000025fc3a03078 00007ff90d878eb8 ...Entity.xxx> 0000025fc3a03080 <>u__3
  23.           0000025fc3a03080 00007ff90c98b920 ....CompilerServices.TaskAwaiter 0000025fc3a03088 <>u__4
  24.     0000025fc3a03090 00007ff90f3f8a60 (0) Furion.Schedule.ScheduleHostedService+<>c__DisplayClass23_3+<<BackgroundProcessing>b__3>d @ 7ff90fdf0000
  25.                      Address               MT Type                                        Value Name
  26.             0000025fc3a030d8 00007ff90c3f7408 System.Int32                                    0 <>1__state
  27.             0000025fc3a030e0 00007ff90c98b540 ...rvices.AsyncTaskMethodBuilder 0000025fc3a030e8 <>t__builder
  28.             0000025fc3a02560 00007ff90cbbc378 ...Service+<>c__DisplayClass23_3 0000025fc3a02560 <>4__this
  29.             0000025fc3a030e8 00007ff90c98b920 ....CompilerServices.TaskAwaiter 0000025fc3a030f0 <>u__1
  30.       0000025fc3a030f8 00007ff90f3f8f58 (0) Furion.FriendlyException.Retry+<InvokeAsync>d__1 @ 7ff90fdef840
  31.                        Address               MT Type                                        Value Name
  32.               0000025fc3a03168 00007ff90c3f7408 System.Int32                                    0 <>1__state
  33.               0000025fc3a03180 00007ff90c98b540 ...rvices.AsyncTaskMethodBuilder 0000025fc3a03188 <>t__builder
  34.               0000025fc3a02860 00007ff90dd040f8 ...<System.Threading.Tasks.Task> 0000025fc3a02860 action
  35.               0000025fc3a0316c 00007ff90c3f7408 System.Int32                                    0 numRetries
  36.               0000025fc3a03178 00007ff90c3f2f78 System.Boolean                               true finalThrow
  37.               0000000000000000 00007ff90f237fc8 ... System.Threading.Tasks.Task>             null fallbackPolicy
  38.               0000000000000000 00007ff90c528d80 System.Type[]                                null exceptionTypes
  39.               0000000000000000 00007ff90cbd2f28 ...on.Retry+<>c__DisplayClass1_0             null <>8__1
  40.               0000025fc3a028a0 00007ff90f233868 ...n<System.Int32, System.Int32> 0000025fc3a028a0 retryAction
  41.               0000025fc3a03170 00007ff90c3f7408 System.Int32                                 1000 retryTimeout
  42.               0000025fc3a03174 00007ff90c3f7408 System.Int32                                    0 <totalNumRetries>5__2
  43.               0000025fc3a03188 00007ff90c98b920 ....CompilerServices.TaskAwaiter 0000025fc3a03190 <>u__1
  44.               0000000000000000 00007ff90c334730 System.Object                                null <>7__wrap2
  45.               ...
复制代码
要想理解上面的字段,需要大家对状态机内部的有一些了解,比如:

  • 5__xxx   表示 await 的返回值。
  • 1__state 表示当一个方法中有多个await 时,这个字段会阶段性的记录当前是第几个await。
结合 5__xxx 赋值情况 和 processMark 的数据标记情况,推测出是卡死在 SavePlcPara 中,截图如下:
5.png

下钻找到了 SavePlcPara 之后,继续回头从 ~*e !clrstack中找结果,终于水滴石穿,真有一个线程在 SavePlcPara 方法中,截图如下:
6.png

从卦中看,没找到M71EnterPortService关键词应该是被栈inline了,根据调用栈,可以发现是查询时序数据库 TDengine 时卡住导致的雪崩,TDengine 虽然我没用过,但听说是一个好东西,放一下描述给大家。
TDengine 是一款 开源、高性能、云原生 的时序数据库(Time Series Database, TSDB), 它专为物联网、车联网、工业互联网、金融、IT 运维等场景优化设计。同时它还带有内建的缓存、流式计算、数据订阅等系统功能,能大幅减少系统设计的复杂度,降低研发和运营成本,是一款极简的时序数据处理平台。
最后就是让朋友重点观察下 TDengine.Driver.Impl.NativeMethods.NativeMethods.QueryWithReqid 方法,可以用排除法观察。
三:总结

这次任务延迟事故在分析过程中还是有相当大的迷惑性,如果你缺乏对状态机的理解以及不知!dumpasync命令的使用,我相信这个问题你很难搞定。
7.jpg


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册