記錄netcore一次記憶體暴漲的坑

星仔007發表於2022-02-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>()改成單利注入。
或者
using var scop = serviceProvider.CreateScope()提升作用於範圍,移到for迴圈外。
 

 

相關文章