记录netcore一次内存暴涨的坑
- 2022 年 2 月 13 日
- 筆記
项目用到了Coldairarrow/EFCore.Sharding: Database Sharding For EFCore (github.com)这个组件,最初是因为分表做的还不错所以用了它。
因为项目一直在开发测试中,所以有个服务内存一直暴涨,重启就好了,时间一长就起来了。不得不说微软文档真的很给力
dotnet-counters 诊断工具 – .NET CLI | Microsoft Docs
一开始是看到这篇文章来的,没有它,也不会去深究微软的诊断工具了。
【.Net Core】分析.net core在linux下内存占用过高问题–持续更新 – 郑立赛 – 博客园 (cnblogs.com)
上图发现System.Diagnostics.StackFrame占用内存是最高的,后面分析了所有的输出信息,发现是数据库输出的信息非常多,…定位到是分表不能释放导致。
通过下载示例代码:
using EFCore.Sharding; using EFCore.Sharding.Tests; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System; using System.Threading.Tasks; namespace Demo.DateSharding { class Program { static async Task Main(string[] args) { DateTime startTime = DateTime.Now.AddMinutes(-5); ServiceCollection services = new ServiceCollection(); //配置初始化 services.AddLogging(x => { x.AddConsole(); }); services.AddEFCoreSharding(config => { config.SetEntityAssemblies(typeof(Base_UnitTest).Assembly); //添加数据源 config.AddDataSource(Config.CONSTRING1, ReadWriteType.Read | ReadWriteType.Write, DatabaseType.SqlServer); //按分钟分表 config.SetDateSharding<Base_UnitTest>(nameof(Base_UnitTest.CreateTime), ExpandByDateMode.PerMinute, startTime); }); var serviceProvider = services.BuildServiceProvider(); new EFCoreShardingBootstrapper(serviceProvider).StartAsync(default).Wait(); for (int i = 0; i < 2000; i++) { using var scop = serviceProvider.CreateScope(); var db = scop.ServiceProvider.GetService<IShardingDbAccessor>(); var logger = scop.ServiceProvider.GetService<ILogger<Program>>(); await db.InsertAsync(new Base_UnitTest { Id = Guid.NewGuid().ToString(), Age = 1, UserName = Guid.NewGuid().ToString(), CreateTime = DateTime.Now }); DateTime time = DateTime.Now.AddMinutes(-2); var count = await db.GetIShardingQueryable<Base_UnitTest>() .Where(x => x.CreateTime >= time) .CountAsync(); logger.LogWarning("当前数据量:{Count}", count); } Console.ReadKey(); } } }
using var scop = serviceProvider.CreateScope()如果多次创建,这个内存是持续上涨的,不会释放。
如果把这一行代码移出去的话,内存就会很稳定。虽然继承了idisposiable ,用了using但是仍然不会释放,很头痛。 但是下面的代码改造成上面的就不会出现内存保障情况。
using EFCore.Sharding; using EFCore.Sharding.Tests; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System; using System.Threading.Tasks; namespace Demo.HelloWorld { class Program { static async Task Main(string[] args) { ServiceCollection services = new ServiceCollection(); services.AddLogging(config => { config.AddConsole(); }); services.AddEFCoreSharding(config => { config.SetEntityAssemblies(typeof(Base_UnitTest).Assembly); config.UseDatabase(Config.SQLITE1, DatabaseType.SQLite); }); var serviceProvider = services.BuildServiceProvider(); new EFCoreShardingBootstrapper(serviceProvider).StartAsync(default).Wait(); using var scop = serviceProvider.CreateScope(); //拿到注入的IDbAccessor即可进行所有数据库操作 var db = scop.ServiceProvider.GetService<IDbAccessor>(); var logger = scop.ServiceProvider.GetService<ILogger<Program>>(); while (true) { await db.InsertAsync(new Base_UnitTest { Age = 100, CreateTime = DateTime.Now, Id = Guid.NewGuid().ToString(), UserId = Guid.NewGuid().ToString(), UserName = Guid.NewGuid().ToString() }); var count = await db.GetIQueryable<Base_UnitTest>().CountAsync(); logger.LogWarning("当前数量:{Count}", count); await Task.Delay(1000); } } } }
后来发现IDbAccessor是通过工厂创建的,没有通过service注入。但是IShardingDbAccessor是通过 services.AddScoped<IShardingDbAccessor, ShardingDbAccessor>()注入到服务中的。
项目需要解决办法就是把
services.AddScoped<IShardingDbAccessor, ShardingDbAccessor>()改成单利注入。