记录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>()改成单利注入。