專案用到了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迴圈外。