做前后端网站教程,怎样做网络推广外包,织梦动漫网站模版,做一个网站的建设过程当前项目是 .NET 5 EentityFrameworkCore#xff0c;疑似内存泄漏#xff0c;之所以说是疑似是因为到目前位置还没有能准确的定位到问题。当前这个框架从 .NET Core 2.1 就开始用#xff0c;期间有升级到 3.1、5.0、6.0#xff0c;在排查过程中还把 5.0 分支升级到了 7.0 。… 当前项目是 .NET 5 EentityFrameworkCore疑似内存泄漏之所以说是疑似是因为到目前位置还没有能准确的定位到问题。当前这个框架从 .NET Core 2.1 就开始用期间有升级到 3.1、5.0、6.0在排查过程中还把 5.0 分支升级到了 7.0 。不幸的是这些分支都存在疑似泄漏的现象。 项目部署在 Linux 中的 DockerLinux 有 RedHat、CentOS、DebanDocker 版本也是多个版本。这个项目很多分支版本现在所在公司的主要业务存在很多相似的需求当出现客户定制化需求的时候就会做差异化代码甚至单独切一个分支出来。根据目前的情况来看问题应该是在 .NET Core 2.1 的时候就已经存在。 发现这个问题是在大约是在2023年的6月份这篇过程记录写于10月份问题的现象是部署的 Docker API 服务内存占用从刚刚启动时的 3xx MB然后一直涨只要有人访问就涨。不同的项目存在些许差异。这里主要分析的是两个 .NET Core 3.1 和 .NET 5.0 版本的两个分支项目。 这里虽然提到 .NET 版本并不是说版本存在问题这里可以排除首先作为大厂 Release 版本的分支理论上出现这种情况的概率小之又小另外当前公司存在另外一个 .NET 6 项目使用不同的框架而并没有出现内存泄漏的现象。所以目前怀疑的主要是业务代码。版本只是区分不同项目。 这两个项目的存在使用上的明显的区别 .NET Core 3.1 背景这个项目使用人少但是接入物联网设备数百个一直处于持续运行被动接收数据的状态几乎没有喘息的机会。 现象内存上涨缓慢同比 .NET 5 项目一天 200 MB 的涨幅。 .NET 5 背景这个项目使用的时段主要是工作日的白天其中有几个接口的数据单次拉取大的时候有几十上百兆。晚上几乎没人用。 现象无人访问时内存稳定在调用大数据接口后暴增至 1.xx ~ 2.xx GB在月末月初使用高峰时期内存占用持续增加至物理内存上限然后 Docker 自动重启。服务器有升级见正文 1. 首先想到的是存在没有释放的内存。
排查方案使用 using 释放内存
于是排查整个项目中的代码到处尝试加 using 手动释放资源。这个过程连续搞了大约两周此后断断续续都在反复看代码应该是把整个项目的代码都看完了。这段时间看代码都会自带特效几乎一眼就能看出哪些需要加 using。这个滤镜有一个后遗症就看大部分代码都可以加。感觉不能加的都要手动去加一个试一下万一呢 ~~~
结论没有效果 ~~~ 可能是因为当前项目主要是原生代码这些 GC 都有安排就算没用手动释放只要没有一直处于被应用状态都会被安排回收。期间写了下面这段代码每隔一分钟输出当前 API 情况就是通过这个观察到 Docker 是在内存耗尽时重启的。 #region 系统资源监测[DllImport(kernel32.dll)]public static extern void GlobalMemoryStatus(ref MemoryInfo32 meminfo);[DllImport(kernel32.dll)]public static extern void GlobalMemoryStatus(ref MemoryInfo64 meminfo);public void GetSystemInfo(){var stopwatch Stopwatch.StartNew();try{if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)){var meminfos System.IO.File.ReadAllText(/proc/meminfo).Split( ).Where(o o ! string.Empty).ToList();var remainPercent decimal.Parse(meminfos[5]) / decimal.Parse(meminfos[1]) * 100;// if (remainPercent 79) Collect();Logger.Info($ 物理内存{GetUnit(meminfos[1])});Logger.Info($ 已用内存{GetUnit(meminfos[3])});Logger.Info($ 可用内存{GetUnit(meminfos[5])});Logger.Info($ 剩余内存百分比{remainPercent:N2}%);/*Logger.Info(meminfos.ToJsonString());[MemTotal:,8008064,kB\nMemFree:,1924628,kB\nMemAvailable:,4107656,kB\nBuffers:,15132,kB\nCached:,2466712,kB\nSwapCached:,0,\kB\nActive:,3761208,kB\nInactive:,2060276,kB\nActive(anon):,3373112,kB\nInactive(anon):,88024,kB\nActive(file):,388096,kB\nInactive(file):,1972252,kB\nUnevictable:,0,kB\nMlocked:,0,kB\nSwapTotal:,0,kB\nSwapFree:,0,kB\nDirty:,280,kB\nWriteback:,0,kB\nAnonPages:,3337920,kB\nMapped:,212864,kB\nShmem:,121496,kB\nSlab:,118600,kB\nSReclaimable:,88208,kB\nSUnreclaim:,30392,kB\nKernelStack:,4656,kB\nPageTables:,15832,kB\nNFS_Unstable:,0,kB\nBounce:,0,kB\nWritebackTmp:,0,kB\nCommitLimit:,4004032,kB\nCommitted_AS:,4199784,kB\nVmallocTotal:,34359738367,kB\nVmallocUsed:,20180,kB\nVmallocChunk:,34359684828,kB\nPercpu:,880,kB\nHardwareCorrupted:,0,kB\nAnonHugePages:,1921024,kB\nCmaTotal:,0,kB\nCmaFree:,0,kB\nHugePages_Total:,0\nHugePages_Free:,0\nHugePages_Rsvd:,0\nHugePages_Surp:,0\nHugepagesize:,2048,kB\nDirectMap4k:,81408,kB\nDirectMap2M:,4112384,kB\nDirectMap1G:,6291456,kB\n]*/}else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)){if (RuntimeInformation.OSArchitecture Architecture.X64){var memInfo new MemoryInfo64();GlobalMemoryStatus(ref memInfo);var remainPercent memInfo.AvailablePhysical / (decimal)memInfo.TotalPhysical * 100;// if (remainPercent 79) Collect();Logger.Info($ 物理内存{GetUnit((long)memInfo.TotalPhysical)});Logger.Info($ 已用内存{GetUnit((long)(memInfo.TotalPhysical - memInfo.AvailablePhysical))});Logger.Info($ 可用内存{GetUnit((long)memInfo.AvailablePhysical)});Logger.Info($ 剩余内存百分比{remainPercent:N2}%);}else if (RuntimeInformation.OSArchitecture Architecture.X86){var memInfo new MemoryInfo32();GlobalMemoryStatus(ref memInfo);var remainPercent (decimal)memInfo.AvailablePhysical / (decimal)memInfo.TotalPhysical * 100;// if (remainPercent 79) Collect();Logger.Info($ 物理内存{GetUnit((long)memInfo.TotalPhysical)});Logger.Info($ 已用内存{GetUnit((long)(memInfo.TotalPhysical - memInfo.AvailablePhysical))});Logger.Info($ 可用内存{GetUnit((long)memInfo.AvailablePhysical)});Logger.Info($ 剩余内存百分比{remainPercent:N2}%);}}}catch (Exception ex){Logger.Error($系统信息获取异常{ex.Message});}try{using var proc Process.GetCurrentProcess();Logger.Info($ 启动时间{proc.StartTime});Logger.Info($ 运行时间{Clock.Now - proc.StartTime});// Logger.Info($ 2小时登录/总人数{login}/{total});Logger.Info($ 线程数{proc.Threads.Count});Logger.Info($ 虚拟内存{GetUnit(proc.VirtualMemorySize64)});Logger.Info($ 应用峰值内存{GetUnit(proc.PeakWorkingSet64)});Logger.Info($ 应用当前内存{GetUnit(proc.WorkingSet64)});Logger.Info($ 专用工作集内存{GetUnit(proc.PrivateMemorySize64)});}catch (Exception ex){Logger.Error($进程信息获取异常{ex.Message});}Logger.Info($ 监测日志耗时{stopwatch.ElapsedMilliseconds}ms);}/// summary一次性回收三代内存/summaryprivate void Collect(){GC.Collect(0);GC.Collect(1);GC.Collect(2);}#endregion 2.怀疑是服务器性能不够导致排队处理不过来
排查方案认为服务器不行换服务器
当前服务器是 ARM 4核8GB在迁移到这个服务器之前使用的是 x86 服务器也是 4核8GB并且当时在迁移过来后有明显的“反应慢”的体验。所以怀疑服务器不行其实更怀疑是代码问题但是换服务器比来得快同时当前对于代码其实是没有头绪的。所以选择升级服务器至 x86 8核16GB 这种更快的方式。
结论升级后整个站点的访问性能有明显提升但是内存占用高的问题并没有解决并且占用彷佛就是物理内存的上限当前没有再继续升级内存不能验证。从 ARM 升级至 x86 后整个站点有明显“快” 的感觉。 3.怀疑静态 ConcurrentDictionary 一直在增长
排查方案1.确认 ConcurrentDictionary 是否一直增长。2.验证 Remove 后内存是否释放。 1.将所有代码中的 static ConcurrentDictionary 集中输出 .Count 来观察是不是一直在增加这个动作搞了半天把全是总数都输出了发现只有部分静态变量出现双倍数据没有一直增长。解决了个小问题去掉了一半。期间还把部分 static 转移到 Redis 中去给 API 服务腾空间效果不怎么明显因为内存还是在涨可以说是没用效果 2.通过测试代码验证到底 Remove 后有没有释放内存。观看下面代码左边主要是两个按钮的点击事件第一个是往 ConcurrentDictionary 中插入 10W 个 Guid。可以从右边的内存占用中看到三个峰值就是在这个动作下产生的。 然后执行第二个方法将 ConcurrentDictionary 中的数据 Remove 和 Clear。两种操作看起来并没有完全削峰手动多次后会释放。期间还测试了联系多次继续 TryAdd 内存会一直涨并且在 Remove 和 Clear 后并不会立刻释放。等 GC 大佬安排等多久什么时候完全不清楚 ~~~ 在 67 ~ 70 行新增了手动回收 0、1、2 三代内存多次后会释放内存。 结论static ConcurrentDictionary 变量没有一直增长。在 Remove 和 Clear 后不会释立刻放内存除非手动回收。但是问题并没有解决因为这两个场景的数据量都不是特别的多。 4. 升级至 .NET 7 版本怀疑是 .NET 5 有问题已经开始不要脸了
排查过程当前分支中有 .NET 6.NET 7 说是对于性能和内存管理都有大幅的提升所以乘此机会升级试试万一呢说实话其实内心对于 .NET 出问题的概率还是比较低不至于被我碰到毕竟都是很普通的业务场景没有多少高科技的逻辑。并且 Visusl Studio 2022 的升级频率也相当高但是现在对于解决问题的渴望已经达到了顶点这种感觉就像是前面的 using 明知道没用但是哪怕有一丝丝希望还是愿意尝试。
结论从 .NET 5 升级到 .NET 7 后应用的启动性能有了明显提升但是内存持续增长的问题并没用消失。最后继续不要脸这两个版本都不是长期支持版本 ~~~ 等下一版 .NET 8 发布后继续升级 5. 使用 Visual Studio 诊断工具分析内存占用
排查过程在不断的搜索中接触到 VS 诊断工具不确定这个工具是从哪个版本开始的但是指导2023年才知道可以分析内存占用。这个过程有几天不是全情投入因为“摸不到头脑”又被难住了。通过工具得知 .NET 5 项目之所有在调用接口后内存暴增的原因是 EntityFrameworkCore 的缓存导致的但是对于这个缓存机制的理解仅限于“缓存”这两个字其他的不清楚 ~~~ 又尝试使用 builder.EnableServiceProviderCaching(false); 禁用 EFCore 的缓存确实有效果整个站点立马变得比之前 ARM 平台还要慢几倍吓得我立刻注释掉。毕竟现在是疑似内存泄漏就这个动作解决了问题但是这种体验是不能被接受的。 在搜索 Visual Studio 诊断工具如何使用的过程中又接触到 WinDbg。也搜索了一些 WinDbg 的使用方法成功在服务器中导出了 dmp 文件特别大 ~~~但是这个的使用方法还没入门就进行不下去了大约两天时间经历了一次“从入门到放弃”。 这里顺带提一下这个问题虽然几个月都没能解决但是在人肉运维的加持下还没有出大问题。例如 .NET 5 这个版本虽然内存增长明显但是晚上没人用所以我们就加了凌晨3点的重启自动任务使得 API 每天充满活力我们一边开发一边如痴如醉的排查问题好几次都是把开发任务拖到 deadline 才从死胡同中抽身去完成。内心来说因为这个问题不解决自己面子上有点挂不住几个月过去了其实已经开始习惯了 ~~~
结论知道了 EntityFrameworkCore 默认启用缓存看起来缓存的时间非常的长感觉不重启彷佛一直都在不知道缓存的内部实现规则没有验证禁用缓存是否任然会有内存溢出的因为不能接受没有缓存时的丝滑。 虽然没有真正定位到为题但是根据目前查询的信息来 Visual Studio 诊断工具和 WinDbg 是最有可能定位到问题的方式但是精力不够没能继续探索下去。 6. 将实例引用改为接口引用不知道有没有用因为理解不够透彻
排查方案将项目中的实例应用改为接口引用。
这个项目已经迭代好多年了从 2018 年开始一直由本人亲自开发维护对于这些代码已经熟悉到忘记了。时不时的还会吐槽几年前的自己为什么这么不严谨自己定的规范自己都不遵守。
结论不清楚有没用用但是感觉整个项目的代码正在蜕变并趋于“完美” 7. 排查 new 关键字
排查方案鉴定 new 关键字的业务是否会存在风险
减少多余的 new可以不赋初值的就不赋。而且在 new 数组的时候尽可能的不要填长度因为如果长度太大喀得就把内存给吃了不熟悉的从表面上还看不出来就比如现在这之前的我就没看出来。 在搜索的过程中发现有很多人出现内存泄漏的原因是用到了 HttpClient可惜目前项目中没有“直接”使用 HttpClient而且使用开源项目 WebApiClientCore老实说我怀疑过这个组件我就没有脸不要脸 ~~~ 但是现在项目中应用比较多还不能通过去掉的方式来验证。但是一边又想这个开源项目也是从 .NET Core 2.1 开始用的当时是 WebApiClient.JIT 目前 .NET Core 3.1 项目使用的最新版 1.1.4WebApiClientCore 是在 .NET 5 项目中使用并且也一直在升级 目前使用的版本是 2.0.2 最新版本是 2.0.4 准备有机会就把它点了。 在排查期间整出个幺蛾子一个线程里 while(true) 监测链接状态的业务因为前面排查 static 变量时没注意把 static 去掉了导致一直在 new Thread在本地开发调试没发现问题发布到测试环境因为用的人少也没发现问题。直到上线的时候发现线程数一直在新增直到大约13,000 的时候 Docker 会自动重启。这个问题排查代码找了三天全工时投入期间还不要脸的怀疑有第三方组件出问题又挨个升级了一遍。这个情况比内存泄漏的问题要急迫因为只要用户数一起来一多个小时甚至不到一小时就会重启一次这几天搞得心惊胆战心力憔悴生怕数据出问题。
结论也许有效果如前面的 “3.怀疑静态 ConcurrentDictionary 一直在增长” 相似只要不是一直被引用的部分最终 GC 都会给安排。这次到是减少了部分可以不许 new 赋初值的逻辑感觉代码又变得更“完美”了彷佛仅限于感觉。 8. 怀疑数据库资源没有释放
排查过程当前使用的 PostgreSQL 数据通过下面的语句发现连接数很少一共只有5个其中还要两个是 Navicat 也就是说 API 只有三个连接数backend_start 与 query_start 的时间都是今天的。按理说这种情况就不存在资源没有释放了。
SELECT * FROM pg_stat_activity
结论不确定是不是方法不对虽然认为连接数没问题但是始终认为 EntityFreamworkCore 存在没有释放的资源肯定有什么设置可以解决取消缓存除外。 后续有进展再更新目前问题仍然存在没有解决 2023年10月9日