自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

記一次 .NET 某藥品倉儲管理系統(tǒng) 卡死分析

開發(fā) 后端
既然朋友說 api 有 request 無 response,那怎么去驗證朋友的話對不對呢?我們都知道 .NET 用 HttpContext 來表示一個請求,言外之意就是可以去抓 HttpContext 下的時長屬性,Netext 中有一個 !whttp 命令可以幫助我們。

一:背景

1. 講故事

這個月初,有位朋友wx上找到我,說他的api過一段時間后,就會出現(xiàn)只有請求,沒有響應(yīng)的情況,截圖如下:

從朋友的描述中看樣子程序是被什么東西卡住了,這種卡死的問題解決起來相對簡單,接下來我就用 windbg 給大家分析一下。

二:Windbg 分析

1. Request 請求正在干嘛?

既然朋友說 api 有 request 無 response,那怎么去驗證朋友的話對不對呢?我們都知道 .NET 用 HttpContext 來表示一個請求,言外之意就是可以去抓 HttpContext 下的時長屬性,Netext 中有一個 !whttp 命令可以幫助我們。

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

從 Running 列可以看到大多請求都已經(jīng)達到1分鐘以上,這也驗證了朋友所說的卡死問題,按照經(jīng)驗,可以取 Running 列中最大的 httpContext 所在的線程,也就是上面的 30 和 33 號線程, 看看它們都在干什么?

2. 探究 Running 最長線程

接下來切到 30 和 33 號線程,看看它們的線程棧。

  1. 0:000> ~30s 
  2. ntdll!NtWaitForSingleObject+0xa: 
  3. 00007ffd`b81f024a c3              ret 
  4. 0:030> !clrstack  
  5. OS Thread Id: 0x29d0 (30) 
  6.         Child SP               IP Call Site 
  7. 0000005acc3ac590 00007ffdb81f024a [PrestubMethodFrame: 0000005acc3ac590] xxx.xxx.RedisConnectionHelp.get_Instance() 
  8. 0000005acc3ac850 00007ffd4dd78911 xxx.xxx.RedisCache..ctor(Int32, System.String) 
  9. 0000005acc3ac8c0 00007ffd4dd78038 xxx.xxx.CacheByRedis.HashGet[[System.__Canon, mscorlib]](System.String, System.String, Int32) 
  10. 0000005acc3ac968 00007ffdabef1f7c [StubHelperFrame: 0000005acc3ac968]  
  11. 0000005acc3ac9c0 00007ffd4dd77f18 xxx.xxx.Cache.xxx.GetCacheNotAreaDataEntity[[System.__Canon, mscorlib]](System.String, System.String, System.String) 
  12.  
  13. ... 
  14.  
  15. 0:030> ~33s 
  16. ntdll!NtWaitForMultipleObjects+0xa: 
  17. 00007ffd`b81f07ba c3              ret 
  18. 0:033> !clrstack  
  19. OS Thread Id: 0x3ad4 (33) 
  20.         Child SP               IP Call Site 
  21. 0000005accabae90 00007ffdb81f07ba [GCFrame: 0000005accabae90]  
  22. 0000005accabafb8 00007ffdb81f07ba [HelperMethodFrame_1OBJ: 0000005accabafb8] System.Threading.Monitor.ObjWait(Boolean, Int32, System.Object) 
  23. 0000005accabb0d0 00007ffdaac60d64 System.Threading.ManualResetEventSlim.Wait(Int32, System.Threading.CancellationToken) 
  24. 0000005accabb160 00007ffdaac5b4bb System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken) 
  25. 0000005accabb1d0 00007ffdab5a01d1 System.Threading.Tasks.Task.InternalWait(Int32, System.Threading.CancellationToken) 
  26. 0000005accabb2a0 00007ffdab59cfa7 System.Threading.Tasks.Task`1[[System.__Canon, mscorlib]].GetResultxxx(Boolean) 
  27. 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>>) 
  28. 0000005accabb340 00007ffd4d8d2f40 xxx.Config.xxx.Config`1[[System.__Canon, mscorlib]].get_Item(System.String, System.String) 
  29. 0000005accabb3c0 00007ffd4dd78f7f xxx.Util.BaseConfig.get_GetRedisConn() 
  30. 0000005accabb440 00007ffd4dd78e9c xxx.xxx.RedisConnectionHelp.GetConnectionString() 
  31. 0000005accabb4a0 00007ffd4dd789cb xxx.xxx.RedisConnectionHelp..cctor() 
  32. 0000005accabb940 00007ffdabef6953 [GCFrame: 0000005accabb940]  
  33. 0000005accabc5b0 00007ffdabef6953 [PrestubMethodFrame: 0000005accabc5b0] xxx.xxx.RedisConnectionHelp.get_Instance() 
  34. 0000005accabc870 00007ffd4dd78911 xxx.xxx.RedisCache..ctor(Int32, System.String) 
  35. 0000005accabc8e0 00007ffd4dd78038 xxx.xxx.CacheByRedis.HashGet[[System.__Canon, mscorlib]](System.String, System.String, Int32) 
  36. 0000005accabc988 00007ffdabef1f7c [StubHelperFrame: 0000005accabc988]  
  37. 0000005accabc9e0 00007ffd4dd77f18 xxx.Core.Cache.xxx.GetCacheNotAreaDataEntity[[System.__Canon, mscorlib]](System.String, System.String, System.String) 
  38. ... 

上面的信息不難發(fā)現(xiàn) 30 號線程正卡在 RedisConnectionHelp.get_Instance() 處,33 號線已經(jīng)進入了 RedisConnectionHelp.get_Instance() 方法中,最后在 GetConfig() 處等待 Result 的結(jié)果,按經(jīng)驗來說,30 號線程看樣子正在鎖等待, 33 號正在等待異步結(jié)果,接下來的突破點就是探究下 RedisConnectionHelp.Instance 處代碼。

3. 尋找問題代碼

接下來用反編譯工具 ILSpy 找到問題代碼。

  1. public static class RedisConnectionHelp 
  2.  public static ConnectionMultiplexer Instance 
  3.  { 
  4.   get 
  5.   { 
  6.    if (_instance == null
  7.    { 
  8.     lock (Locker) 
  9.     { 
  10.      if (_instance == null || !_instance.IsConnected) 
  11.      { 
  12.       _instance = GetManager(); 
  13.      } 
  14.     } 
  15.    } 
  16.    return _instance; 
  17.   } 
  18.  } 

30 號線程果然是卡在 Locker 處,接下來深挖下 33 號線程所執(zhí)行的 GetManager() 方法,簡化后代碼如下:

  1. public T this[string dataId, string key = ""
  2.  get 
  3.  { 
  4.   try 
  5.   { 
  6.    string config = GetConfig(configListener, new NacosConfigClient(Base.NacosConfiguration).DoGetConfigAsync); 
  7.  
  8.    return JsonConvert.DeserializeObject<T>(config); 
  9.   } 
  10.   catch (Exception ex) 
  11.   { 
  12.    return default(T); 
  13.   } 
  14.  } 
  15.  
  16. private string GetConfig(ConfigListener listener, Func<GetConfigRequest, Task<string>> action
  17.  var text2 = action(new GetConfigRequest 
  18.  { 
  19.   DataId = listener.DataId, 
  20.   Group = listener.Group
  21.   Tenant = text 
  22.  }).Result; 
  23.  
  24.  return text2; 
  25.  
  26. internal async Task<string> DoGetConfigAsync(GetConfigRequest request) 
  27.  IRestResponse restResponse = await HttpUtil.Request(currentServerAddr, Method.GET, request.ParamValues(), xxx); 
  28.  
  29.  return restResponse.Content; 

可以看到代碼卡在了 Result 上無限期等待,到這里我就想到了 同步上下文 ,我看他這個程序是 .NET 4.8 下的 ASP.NET MVC,記得不錯上下文應(yīng)該是 AspNetSynchronizationContext,具體死鎖原因可參見我的這篇文章:一句 Task.Result 就死鎖, 這代碼還怎么寫?,解決辦法大概有四種。

  • 使用 .ConfigureAwait(false)
  • 改成全異步
  • 用 Task 再包一層。
  • 改成全同步

三:總結(jié)

其實本次事故主要還是因為在 同步代碼 中做了 異步代碼.Result 導(dǎo)致的死鎖問題,有非常多的文章都在抨擊這種現(xiàn)象,在 asp.net core 中已經(jīng)移除了這種同步上下文的大坑,給到朋友的建議是改成全同步,死鎖問題也隨之消失。

 哈哈,真替朋友開心!

本文轉(zhuǎn)載自微信公眾號「 一線碼農(nóng)聊技術(shù)」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系一線碼農(nóng)聊技術(shù)公眾號。

 

責任編輯:武曉燕 來源: 一線碼農(nóng)聊技術(shù)
相關(guān)推薦

2024-07-01 13:00:24

.NET網(wǎng)絡(luò)邊緣計算

2024-11-29 10:06:59

2024-09-14 10:28:56

.NET卡死程序

2023-05-15 11:15:50

.NET門診語句

2022-10-13 18:40:05

.NETOA后端

2023-09-27 07:23:10

.NET監(jiān)控軟件

2024-05-20 09:39:02

.NETurl線程池

2024-06-06 10:51:15

自動化系統(tǒng)推測

2023-06-29 17:55:00

.NET日志WinDbg

2024-05-28 10:18:30

WPF程序數(shù)據(jù)

2021-11-02 07:54:41

內(nèi)存.NET 系統(tǒng)

2024-03-28 12:56:36

2023-04-06 10:52:18

2024-07-09 11:51:20

Windows線程池源碼

2023-03-26 20:24:50

ERP網(wǎng)站系統(tǒng)

2024-03-26 00:44:53

.NETCIM系統(tǒng)

2024-06-04 10:54:34

.NET代碼程序

2024-08-08 11:21:01

2023-06-26 00:12:46

2024-12-27 13:31:18

.NETdump調(diào)試
點贊
收藏

51CTO技術(shù)棧公眾號