https://www.cnblogs.com/coldairarrow/p/12733886.html
簡介
本框架旨在為EF Core提供Sharding(即讀寫分離分庫分表)支持,不僅提供了一套強大的普通數據操作接口,並且降低了分表難度,支持按時間自動分表擴容,提供的操作接口簡潔統一.
源碼地址:EFCore.SHarding
引言
讀寫分離分庫分表一直是數據庫領域中的重難點,當數據規模達到單庫極限的時候,就不得不考慮分表方案。EF Core作為.NET Core中最為主流的ORM,用起來十分方便快捷,但是官方並沒有相應的Sharding支持,鄙人不才,經過一番摸索之后終於完成這個框架.
開始
准備
首先根據需要安裝對應的Nuget包
包名 | 說明 |
---|---|
EFCore.Sharding | 必裝包,3.x版本對應EF Core3.x,2.x版本對應EF Core2.x |
EFCore.Sharding.MySql | MySql支持 |
EFCore.Sharding.PostgreSql | PostgreSql支持 |
EFCore.Sharding.SQLite | SQLite支持 |
EFCore.Sharding.SqlServer | SqlServer支持(3.x版本需要SqlServer2012+,若要用低版本則用2.x版本) |
EFCore.Sharding.Oracle | Oracle支持(暫不支持3.x) |
配置
ServiceCollection services = new ServiceCollection(); //配置初始化 services.AddEFCoreSharding(config => { //添加數據源 config.AddDataSource(Config.CONSTRING1, ReadWriteType.Read | ReadWriteType.Write, DatabaseType.SqlServer); //對3取模分表 config.SetHashModSharding<Base_UnitTest>(nameof(Base_UnitTest.Id), 3); });
上述代碼中完成了Sharding配置
- AddEFCoreSharding注入EFCoreSharding
- AddDataSource添加分表數據源
- SetHashModSharding是采用哈希取模的分表規則,分表字段為Id,取模值為3,會自動生成表Base_UnitTest_0,Base_UnitTest_1,Base_UnitTest_2
使用
配置完成,下面開始使用,使用方式非常簡單,與平常使用基本一致
首先通過注入獲取到IShardingDbAccessor
var db=ServiceProvider.GetService<IShardingDbAccessor>();
然后即可進行數據操作:
Base_UnitTest _newData = new Base_UnitTest { Id = Guid.NewGuid().ToString(), UserId = "Admin", UserName = "超級管理員", Age = 22 }; List<Base_UnitTest> _insertList = new List<Base_UnitTest> { new Base_UnitTest { Id = Guid.NewGuid().ToString(), UserId = "Admin1", UserName = "超級管理員1", Age = 22 }, new Base_UnitTest { Id = Guid.NewGuid().ToString(), UserId = "Admin2", UserName = "超級管理員2", Age = 22 } }; //添加單條數據 _db.Insert(_newData); //添加多條數據 _db.Insert(_insertList); //清空表 _db.DeleteAll<Base_UnitTest>(); //刪除單條數據 _db.Delete(_newData); //刪除多條數據 _db.Delete(_insertList); //刪除指定數據 _db.Delete<Base_UnitTest>(x => x.UserId == "Admin2"); //更新單條數據 _db.Update(_newData); //更新多條數據 _db.Update(_insertList); //更新單條數據指定屬性 _db.Update(_newData, new List<string> { "UserName", "Age" }); //更新多條數據指定屬性 _db.Update(_insertList, new List<string> { "UserName", "Age" }); //更新指定條件數據 _db.Update<Base_UnitTest>(x => x.UserId == "Admin", x => { x.UserId = "Admin2"; }); //GetList獲取表的所有數據 var list=_db.GetList<Base_UnitTest>(); //Max var max=_db.GetIShardingQueryable<Base_UnitTest>().Max(x => x.Age); //Min var min=_db.GetIShardingQueryable<Base_UnitTest>().Min(x => x.Age); //Average var min=_db.GetIShardingQueryable<Base_UnitTest>().Average(x => x.Age); //Count var min=_db.GetIShardingQueryable<Base_UnitTest>().Count(); //事務,使用方式與普通事務一致 bool succcess = _db.RunTransaction(() => { _db.Insert(_newData); var newData2 = _newData.DeepClone(); _db.Insert(newData2); }).Success; Assert.AreEqual(succcess, false);
上述操作中表面上是操作Base_UnitTest表,實際上卻在按照一定規則使用Base_UnitTest_0~2三張表,使分片對業務操作透明,極大提高開發效率
具體使用方式請參考單元測試源碼:連接
按時間自動分表
上面的哈希取模的方式雖然簡單,但是卻十分不實用,因為當3張分表到達瓶頸時,將會面臨擴容的問題,這種方式擴容需要進行大量的數據遷移,這無疑是十分麻煩的。因此需要一種方式能夠系統自動建表擴容,並且無需人工干預,這就是按時間自動分表.
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(config => { config.AddConsole(); }); //配置初始化 services.AddEFCoreSharding(config => { //添加數據源 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(); using var scop = serviceProvider.CreateScope(); var db = scop.ServiceProvider.GetService<IShardingDbAccessor>(); var logger = scop.ServiceProvider.GetService<ILogger<Program>>(); while (true) { try { 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); } catch (Exception ex) { Console.WriteLine(ex.Message); } await Task.Delay(1000); } } } }
上面Demo都在源碼中
上面的代碼實現了將Base_UnitTest表按照時間自動分表,每分鍾創建一張表,實際使用中根據業務需求設置ExpandByDateMode參數,常用按天、按月分表
自動分表效果
全程無需人工干預,系統會自動定時創建分表,十分簡單好用
性能測試
using EFCore.Sharding; using EFCore.Sharding.Tests; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using System; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; namespace Demo.Performance { class Program { static void Main(string[] args) { ServiceCollection services = new ServiceCollection(); services.AddEFCoreSharding(config => { config.UseDatabase(Config.CONSTRING1, DatabaseType.SqlServer); //添加數據源 config.AddDataSource(Config.CONSTRING1, ReadWriteType.Read | ReadWriteType.Write, DatabaseType.SqlServer); //對3取模分表 config.SetHashModSharding<Base_UnitTest>(nameof(Base_UnitTest.Id), 3); }); var serviceProvider = services.BuildServiceProvider(); var db = serviceProvider.GetService<IDbAccessor>(); var shardingDb = serviceProvider.GetService<IShardingDbAccessor>(); Stopwatch watch = new Stopwatch(); Expression<Func<Base_UnitTest, bool>> where = x => EF.Functions.Like(x.UserName, $"%00001C22-8DD2-4D47-B500-407554B099AB%"); var q = db.GetIQueryable<Base_UnitTest>() .Where(where) .OrderByDescending(x => x.Id) .Skip(0) .Take(30); var shardingQ = shardingDb.GetIShardingQueryable<Base_UnitTest>() .Where(where) .OrderByDescending(x => x.Id) .Skip(0) .Take(30); //先執行一次預熱 q.ToList(); shardingQ.ToList(); watch.Restart(); var list1 = q.ToList(); watch.Stop(); Console.WriteLine($"未分表耗時:{watch.ElapsedMilliseconds}ms"); watch.Restart(); var list2 = shardingQ.ToList(); watch.Stop(); Console.WriteLine($"分表后耗時:{watch.ElapsedMilliseconds}ms"); Console.WriteLine("完成"); Console.ReadLine(); } } }
分表Base_UnitTest_0-2各有100萬數據,然后將這三張表的數據導入Base_UnitTest中(即Base_UnitTest表的數據與Base_UnitTest_0-2三張表總合數據一致)
分表與不分表測試結果如下
這里僅僅分了3張表,其效果立桿見影,若分表幾十張,那效果想想就很棒
其它簡單操作(非Sharing)
框架不僅支持Sharing,而且封裝了常用數據庫操作,使用比較簡單
詳細使用方式參考 鏈接
高級配置
多主鍵等配置
多主鍵、索引等高級配置請使用IEntityTypeConfiguration
參考fluentApi
讀寫分離
數據庫讀寫分離在大型項目中十分常見,通常在數據庫層完成自動讀寫分離
- MySQL可以使用ProxySQL完成全自動讀寫分離集群
- PostgreSQL可以使用Pgool完成全自動讀寫分離集群
- SQLServer可以使用AlwaysOn,但是需要在連接字符串中加上 ApplicationIntent=ReadOnly,因此只是半自動的
本框架支持將半自動讀寫分離升級成全自動,即在代碼層無需感知讀寫分離切換,代碼層只需跟普通一樣使用IDbAccessor即可
代碼如下(鏈接)
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.ReadWrite { class Program { static async Task Main(string[] args) { ServiceCollection services = new ServiceCollection(); services.AddLogging(config => { config.AddConsole(); }); services.AddEFCoreSharding(config => { config.SetEntityAssembly("EFCore.Sharding"); //SQLITE1作為主庫(寫庫) //SQLITE2作為從庫(讀庫) config.UseDatabase(new (string, ReadWriteType)[] { (Config.SQLITE1, ReadWriteType.Write), (Config.SQLITE2, ReadWriteType.Read) }, DatabaseType.SQLite); }); var serviceProvider = services.BuildServiceProvider(); 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(); //注意:這里數量始終為0,因為SQLITE1與SQLITE2沒有開啟主從復制 //在實際使用中應在數據庫層開啟主從復制 logger.LogWarning("當前數量:{Count}", count); await Task.Delay(1000); } } } }
注意事項
- 查詢盡量使用分表字段進行篩選,避免全表掃描
總結
這個簡單實用強大的框架希望能夠幫助到大家,力求為.NET生態貢獻一份力,大家一起壯大.NET生態
歡迎使用本框架,若覺得不錯,請比心