如果您對分表有以下痛點那么不妨試試我這邊開源的框架sharding-core ,是否需要無感知使用分表組件,是否需要支持abp,是否需要支持自定義分表規則,是否需要支持自定義分表鍵,是否需要支持特定的efcore版本,是否希望框架不帶任何三方框架干凈,是否需要支持讀寫分離,是否需要動態添加表,是否需要支持join,group等操作,是否需要支持追蹤特性,是否想在不修改原先代碼的基礎上擴展分表功能,如果一起上幾個條件任意組合且你在市面上沒辦法找到可替代的框架可以試試本框架。如何使用代碼具體可以參考github 將代碼下載下來如果本地裝了sqlserver直接運行單元測試或者Sample.SqlServer程序會自動在本地新建數據庫新建數據庫表結構,目前初始化數據為用戶信息和用戶對應的月薪信息表,用戶表以用戶id取模,用戶月薪表以月份分表。
首先需要了解本框架的一個版本號不然將對您的使用產生一定的分期,目前框架分為3個版本分別是2.x,3.x,5.x3個版本,分別對應efcore 2.x efcore 3.x efcore 5.x,有人要問為什么不支持6.x呢(小弟剛剛在上周完成對本框架的開發重構,目前還未對efcore 6.x進行著手不過將在不遠的將來即將支持(目測1-2個星期內))。
目前efcore生態下有著許許多多的分表、分庫的解決方案,但是目前來講都有其不足點,比如需要手動設置分表后綴、需要大量替換現有代碼、不支持事務等等一系列問題,所以在這個大前提下我之前開源了sharding-core 分表組件,這個分表組件是目前來說個人認為比較“完美”的分表組件,這個分表組件目前是參考了sharding-jdbc來實現的,但是比sharding-jdbc更加強大(因為C#的表達式)。首先我們來看下目前市面上有的分表組件的缺點我們來針對其缺點進行痛點解決。
efcore支持情況
efcore版本 | 是否支持 |
---|---|
2.x | 支持 |
3.x | 支持 |
5.x | 支持 |
6.x | 即將支持 |
數據庫支持情況
數據庫 | 理論是否支持 |
---|---|
SqlServer | 支持 |
MySql | 支持 |
PostgreSql | 支持 |
SQLite | 支持 |
Oracle | 支持 |
其他 | 支持(只要efcore支持) |
理論上只要是efcore對應版本支持的數據庫,sharding-core都將支持。
如何開始使用
1.創建一個數據庫對象繼承IShardingTable并且在對應的分表字段上進行[ShardingTableKey]特性的標注
/// <summary> /// 用戶表 /// </summary> public class SysUserMod : IShardingTable { /// <summary> /// 用戶Id用于分表 /// </summary> [ShardingTableKey(TailPrefix = "_")] public string Id { get; set; } /// <summary> /// 用戶名稱 /// </summary> public string Name { get; set; } /// <summary> /// 用戶姓名 /// </summary> public int Age { get; set; } }
2.創建對應的實體表對應配置 推薦 fluent api
public class SysTestMap:IEntityTypeConfiguration<SysTest> { public void Configure(EntityTypeBuilder<SysTest> builder) { builder.HasKey(o => o.Id); builder.Property(o => o.Id).IsRequired().HasMaxLength(128); builder.Property(o => o.UserId).IsRequired().HasMaxLength(128); builder.ToTable(nameof(SysTest)); } }
3.創建對應的分表規則 取模分表,參數2代表后綴2位就是00-99最多100張表,3表示模3== key.hashcode() %3
public class SysUserModVirtualTableRoute : AbstractSimpleShardingModKeyStringVirtualTableRoute<SysUserMod> { public SysUserModVirtualTableRoute() : base(2,3) { } }
4.創建對應執行的dbcontext 這一步除了繼承IShardingTableDbContext外其他和普通dbcontext一樣
public class DefaultTableDbContext: DbContext,IShardingTableDbContext { public DefaultTableDbContext(DbContextOptions<DefaultTableDbContext> options) :base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.ApplyConfiguration(new SysUserModMap()); } public IRouteTail RouteTail { get; set; } }
5.添加分表dbcontext
public class DefaultShardingDbContext:AbstractShardingDbContext<DefaultTableDbContext> { public DefaultShardingDbContext(DbContextOptions<DefaultShardingDbContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.ApplyConfiguration(new SysUserModMap()); } public override Type ShardingDbContextType => this.GetType(); }
6.添加配置
public void ConfigureServices(IServiceCollection services) { services.AddControllers(); //原先的dbcontext可以用也可以不用如果原先的dbcontext還在用就繼續 //services.AddDbContext<DefaultTableDbContext>(o => o.UseSqlServer("Data Source=localhost;Initial Catalog=ShardingCoreDBxx3;Integrated Security=True")); services.AddShardingDbContext<DefaultShardingDbContext, DefaultTableDbContext>( o => o.UseSqlServer("Data Source=localhost;Initial Catalog=ShardingCoreDBxx2;Integrated Security=True;") , op => { op.EnsureCreatedWithOutShardingTable = true; op.CreateShardingTableOnStart = true; op.UseShardingOptionsBuilder( (connection, builder) => builder.UseSqlServer(connection).UseLoggerFactory(efLogger),//使用dbconnection創建dbcontext支持事務 (conStr,builder) => builder.UseSqlServer(conStr).UseLoggerFactory(efLogger));//使用鏈接字符串創建dbcontext op.AddShardingTableRoute<SysUserModVirtualTableRoute>(); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { ... //添加啟動項 app.UseShardingCore(); ... } public static class ShardingCoreExtension{ public static IApplicationBuilder UseShardingCore(this IApplicationBuilder app) { var shardingBootstrapper = app.ApplicationServices.GetRequiredService<IShardingBootstrapper>(); shardingBootstrapper.Start(); return app; } }
7.控制器使用
private readonly DefaultShardingDbContext _defaultTableDbContext; public ValuesController(DefaultShardingDbContext defaultTableDbContext) { _defaultTableDbContext = defaultTableDbContext; } [HttpGet] public async Task<IActionResult> Get() { var resultx11231 = await _defaultTableDbContext.Set<SysUserMod>().Where(o => o.Age == 198198).Select(o=>o.Id).ContainsAsync("1981"); var resultx1121 = await _defaultTableDbContext.Set<SysUserMod>().Where(o => o.Id == "198").SumAsync(o=>o.Age); var resultx111 = await _defaultTableDbContext.Set<SysUserMod>().FirstOrDefaultAsync(o => o.Id == "198"); var resultx2 = await _defaultTableDbContext.Set<SysUserMod>().CountAsync(o => o.Age<=10); var resultx = await _defaultTableDbContext.Set<SysUserMod>().Where(o => o.Id == "198").FirstOrDefaultAsync(); var resultx33 = await _defaultTableDbContext.Set<SysUserMod>().Where(o => o.Id == "198").Select(o=>o.Id).FirstOrDefaultAsync(); var resulxxt = await _defaultTableDbContext.Set<SysUserMod>().Where(o => o.Id == "198").ToListAsync(); var result = await _defaultTableDbContext.Set<SysUserMod>().ToListAsync(); var sysUserMod98 = result.FirstOrDefault(o => o.Id == "98"); _defaultTableDbContext.Attach(sysUserMod98); sysUserMod98.Name = "name_update"+new Random().Next(1,99)+"_98"; await _defaultTableDbContext.SaveChangesAsync(); return Ok(result); }
自定義分表鍵,自定義分表規則
目前市面上有的框架要么對分表字段有限制比如僅支持DateTime類型或者int等,要么對分表規則有限制:僅支持按天、按月、取模...等等,但是基于分表規則和分表字段是業務規則所以本框架遵循將其由業務系統自己定義,最大化來實現分表庫的適用性,基本上滿足一切分表規則,且sharding-core目前默認提供一些常用的分表規則可以快速集成。
默認路由
抽象abstract | 路由規則 | tail | 索引 |
---|---|---|---|
AbstractSimpleShardingModKeyIntVirtualTableRoute | 取模 | 0,1,2... | = |
AbstractSimpleShardingModKeyStringVirtualTableRoute | 取模 | 0,1,2... | = |
AbstractSimpleShardingDayKeyDateTimeVirtualTableRoute | 按時間 | yyyyMMdd | >,>=,<,<=,=,contains |
AbstractSimpleShardingDayKeyLongVirtualTableRoute | 按時間戳 | yyyyMMdd | >,>=,<,<=,=,contains |
AbstractSimpleShardingWeekKeyDateTimeVirtualTableRoute | 按時間 | yyyyMMdd_dd | >,>=,<,<=,=,contains |
AbstractSimpleShardingWeekKeyLongVirtualTableRoute | 按時間戳 | yyyyMMdd_dd | >,>=,<,<=,=,contains |
AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute | 按時間 | yyyyMM | >,>=,<,<=,=,contains |
AbstractSimpleShardingMonthKeyLongVirtualTableRoute | 按時間戳 | yyyyMM | >,>=,<,<=,=,contains |
AbstractSimpleShardingYearKeyDateTimeVirtualTableRoute | 按時間 | yyyy | >,>=,<,<=,=,contains |
AbstractSimpleShardingYearKeyLongVirtualTableRoute | 按時間戳 | yyyy | >,>=,<,<=,=,contains |
所謂的索引就是通過改對應的條件操作符可以縮小減少指定表的范圍,加快程序的執行
如果以上默認分表無法滿足您的需求您還可以自定義分表,如何分表可以通過繼承 AbstractShardingOperatorVirtualTableRoute<TEntity,TKey>來實現自定義分表規則(近乎90%的規則都可以實現)
動態添加分表信息
很多分表組件默認不帶動態分表信息導致很多分表沒辦法根據業務系統來進行動態創建,sharding-core默認提供動態建表接口可以支持動態按時間,按租戶等不需要數據做遷移的動態分表信息,
如果需要請參考Samples.AutoByDate.SqlServer
支持select,join,group by等連表聚合函數
目前sharding-core支持select按需查詢,join分表連表查詢,group by聚合查詢,雖然本框架支持但是出于性能原因本框架還是不建議使用join操作符來操作,因為過多的表路由會導致笛卡爾積,會導致需要查詢的表集合增長對數據庫連接比較考驗。
以下代碼來自github的單元測試中,SysUserMod表示用戶表,SysUserSalary表示用戶月薪表用戶表按id取模,用戶月薪表按月分表
//join查詢 var list = await (from u in _virtualDbContext.Set<SysUserMod>() join salary in _virtualDbContext.Set<SysUserSalary>() on u.Id equals salary.UserId select new { u.Id, u.Age, Salary = salary.Salary, DateOfMonth = salary.DateOfMonth, Name = u.Name }).ToListAsync(); //group聚合查詢 var ids = new[] {"200", "300"}; var dateOfMonths = new[] {202111, 202110}; var group = await (from u in _virtualDbContext.Set<SysUserSalary>() .Where(o => ids.Contains(o.UserId) && dateOfMonths.Contains(o.DateOfMonth)) group u by new { UId = u.UserId } into g select new { GroupUserId = g.Key.UId, Count = g.Count(), TotalSalary = g.Sum(o => o.Salary), AvgSalary = g.Average(o => o.Salary), AvgSalaryDecimal = g.Average(o => o.SalaryDecimal), MinSalary = g.Min(o => o.Salary), MaxSalary = g.Max(o => o.Salary) }).ToListAsync();
分頁
我們常說的分頁是分表的難點也是最考驗分表組件的
1我們首先來看普通的分表組件如何分頁
首先我們定義一組組數據比如是1-100的連續數字,然后分成兩張表按奇偶分表
表名 | 數據 |
---|---|
table1 | 1,3,5,7,9... |
table2 | 2,4,6,8,10... |
select * from table limit 2,2理論上結果3,4
如果本次查詢會做落到table1 和table2那么會改寫成 2句sql
第一句 select * from table1 limit 4 ---> 1,3,5,7
第二句 select * from table2 limit 4 ---> 2,4,6,8
將8條數據放入內存然后排序
1,2,3,4,5,6,7,8
獲取第3到4條數據 結果[3,4]
這個情況是我們常見的也是最簡單的分頁,但是這個情況僅僅適用于數據量小的時候,如果用戶不小心點到了分頁的最后一頁那么結果將是災難性的這是毋庸置疑的
那么sharding-core是如何處理的呢
select * from table limit 2,2
首先還是一樣對數據庫語句進行改性并且生成對應的sql
第一句 select * from table1 limit 4
第二句 select * from table2 limit 4
因為ado.net默認DataReader是流式獲取,只要連接不關閉那么可以一直實現next獲取到內存
創建一個優先級隊列一個可以具有排序功能的隊列
因為DataReader的特性我們分別對sql1和sql2進行一次next獲取到2個數組一個是[1,.....] A和數組[2......] B
獲取到兩個數組我們只知道頭部第一個對象因為沒有進行后續的next所以無法知曉剩下的數據但是有一點可以知道后面的數據都是按sql的指定順序的所以都不會比當前頭大或者小
先將1和2放入優先級隊列可以知道如果asc那么數組A放在隊列頭 數組B放在隊列尾部,然后對優先級隊列進行poll彈出,并且對A進行next這個時候A變成了[3,....]再將A放入優先級隊列
這時候優先級隊列就是B在前A在后依次操作,然后對分頁的進行過濾因為要跳過2個對象所以只需要空執行2次那么指針就會指向A數組的3和B數組的4,剩下的只要獲取2個數據就可以了,
這樣做可以保證內存最小化,然后分頁不會成為程序的災難。
無感知使用
目前的分表框架很少有做到無感知使用的,你在使用的時候好一點的框架不依賴三方,一般一點的不但要依賴很多三方框架并且在使用的時候還有一大堆限制,必須使用他的東西還沒辦法做到和dbcontext原生的使用方法。
sharding-core目前使用的是一種類似dbcontext的wrap模式,用一個新的dbcontext來包裝真實的dbcontext,這個包裝的dbcontext我們成為shardingdbcontext,shardingDbContext因為本身也是集成于DbContext所以它的使用方法和原生dbcontext沒有差別。并且僅需少量改動即可支持abp和abp.next
讀寫分離的支持
目前sharding-core已經支持單node節點的讀寫分離操作,將在不久的未來(1-2)天內支持多節點的讀寫分離
services.AddShardingDbContext<ShardingDefaultDbContext, DefaultDbContext>(o => o.UseSqlServer(hostBuilderContext.Configuration.GetSection("SqlServer")["ConnectionString"]) ,op => { op.EnsureCreatedWithOutShardingTable = true; op.CreateShardingTableOnStart = true; op.UseShardingOptionsBuilder((connection, builder) => builder.UseSqlServer(connection).UseLoggerFactory(efLogger), (conStr,builder)=> builder.UseSqlServer("read db connection string").UseLoggerFactory(efLogger)); op.AddShardingTableRoute<SysUserModVirtualTableRoute>(); op.AddShardingTableRoute<SysUserSalaryVirtualTableRoute>(); });
未來計劃將支持分庫,支持強制路由,顯示路由等...
最后具體如何使用且使用方式可以參考github(https://github.com/xuejmnet/sharding-core)
到此這篇關于efcore-ShardingCore呈現“完美”分表的文章就介紹到這了,更多相關ShardingCore“完美”分表內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://www.cnblogs.com/xuejiaming/p/15173965.html