基於efcore的分表元件開源

薛家明發表於2021-02-03

ShardingCore

ShardingCore 是一個支援efcore 2.x 3.x 5.x的一個對於資料庫分表的一個簡易擴充套件,.Net下並沒有類似mycat或者sharding-jdbc之類的開源元件或者說有但是並沒有非常適用的或者說個人在用過後有一些地方因為限制沒法很好使用所以決定自己開發這個庫,目前該庫暫未支援分庫(未來會支援),僅支援分表,該專案的理念是讓你可以已最少的程式碼量來實現自動分表的實現,經過多個開源專案的摸索參考目前正式開源本專案
專案地址 github 喜歡的朋友可以點下star Thanks♪(・ω・)ノ

依賴

Release EF Core .NET Standard .NET (Core) Sql Server Pomelo.EntityFrameworkCore.MySql
5.x.x.x >= 5.0.x 2.1 3.0+ >= 2012 5.0.0-alpha.2
3.x.x.x 3.1.10 2.0 2.0+ >= 2012 3.2.4
2.x.x.x 2.2.6 2.0 2.0+ >= 2008 2.2.6

開始

以下所有例子都以Sql Server為例 MySql亦如此

簡介

目前該庫處於初期階段,有很多bug也希望各位多多理解,一起努力為.net生態做出一份微薄之力,目前該庫支援的分表可以進行完全的自定義,基本上可以滿足95%以上的
業務需求,唯一的限制就是分表規則必須滿足 x+y+z,x表示固定的表名,y表示固定的表名和表字尾之間的聯絡(可以為空),z表示表字尾,可以按照你自己的任意業務邏輯進行切分,
如:user_0,user_1或者user202101,user202102...當然該庫同樣適用於多租戶模式下的隔離,該庫為了支援之後的分庫已經重寫了之前的union all查詢模式,並且支援多種api,
支援多種查詢包括join,group by,max,count,min,avg,sum ...等一系列查詢,之後可能會新增更多支援,目前該庫的使用非常簡單,基本上就是針對IQueryable的擴充套件,為了保證
該庫的簡介目前僅使用該庫無法或者說難以實現自動建表,但是隻需要配合定時任務該庫即可完成24小時無人看管自動管理。該庫提供了 IShardingTableCreator
作為建表的依賴,如果需要可以參考 按天自動建表

概念

本庫的幾個簡單的核心概念:

  • [Tail]
    尾巴、字尾物理表的字尾
  • [TailPrefix]
    尾巴字首虛擬表和物理表的字尾中間的字元
  • [物理表]
    顧名思義就是資料庫對應的實際表資訊,表名(tablename+ tailprefix+ tail) IPhysicTable
  • [虛擬表]
    虛擬表就是系統將所有的物理表在系統裡面進行抽象的一個總表對應到程式就是一個entityIVirtualTable
  • [虛擬路由]
    虛擬路由就是聯絡虛擬表和物理表的中間介質,虛擬表在整個程式中只有一份,那麼程式如何知道要查詢系統哪一張表呢,最簡單的方式就是通過虛擬表對應的路由IVirtualRoute
    ,由於基本上所有的路由都是和業務邏輯相關的所以虛擬路由由使用者自己實現,該框架提供一個高階抽象

優點

  • [支援自定義分表規則]
  • [支援任意型別分表key]
  • [針對iqueryable的擴充套件方便使用]
  • [支援分表下的連表] join
  • [支援針對批處理的使用] BulkInsert、BulkUpdate、BulkDelete
  • [提供多種預設分表規則路由] 按時間按取模,自定義(AbstractShardingOperatorVirtualRoute<T, TKey>)
  • [針對分頁進行優化] 大頁數跳轉支援低記憶體流式處理

缺點

  • [暫不支援分庫(不久後會支援)]
  • [消耗連線]出現分表與分表物件進行join如果條件沒法索引到具體表會生成笛卡爾積導致連線數爆炸,後期會進行鍼對該情況的配置
  • [該庫比較年輕] 可能會有一系列bug或者單元測試不到位的情況,但是隻要你在群裡或者提了issues我會盡快解決

安裝

<PackageReference Include="ShardingCore.SqlServer" Version="5.0.0.4" />

配置

配置entity 推薦 fluent api 可以實現自動建表功能
IShardingEntity資料庫物件必須繼承該介面
ShardingKey分表欄位需要使用該特性

    public class SysUserMod:IShardingEntity
    {
        /// <summary>
        /// 使用者Id用於分表
        /// </summary>
        [ShardingKey]
        public string Id { get; set; }
        /// <summary>
        /// 使用者名稱稱
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 使用者姓名
        /// </summary>
        public int Age { get; set; }
    }
    
    public class SysUserModMap:IEntityTypeConfiguration<SysUserMod>
    {
        public void Configure(EntityTypeBuilder<SysUserMod> builder)
        {
            builder.HasKey(o => o.Id);
            builder.Property(o => o.Id).IsRequired().HasMaxLength(128);
            builder.Property(o => o.Name).HasMaxLength(128);
            builder.ToTable(nameof(SysUserMod));
        }
    }
    

建立virtual route
實現 AbstractShardingOperatorVirtualRoute<T, TKey>
抽象,或者實現系統預設的虛擬路由
框架預設有提供幾個簡單的路由 預設路由


    public class SysUserModVirtualRoute : AbstractSimpleShardingModKeyStringVirtualRoute<SysUserMod>
    {
        public SysUserModVirtualRoute() : base(3)
        {
        }
    }
  • GetAllTails
    現在資料庫已存在的尾巴有哪些

Startup.cs 下的 ConfigureServices(IServiceCollection services)


 services.AddShardingSqlServer(o =>
  {
      o.ConnectionString = "";
      o.AddSharding<SysUserModVirtualRoute>();
      o.UseShardingCoreConfig((provider, config) =>
      {
          //如果是development就判斷並且新建資料庫如果不存在的話(ishardingentity不會被建立)
          config.EnsureCreated = provider.GetService<IHostEnvironment>().IsDevelopment();
          //ishardingentity表是否需要在啟動時建立(如果已建立可以選擇不建立)
          config.CreateShardingTableOnStart = true;
      });
  });

Startup.cs 下的 Configure(IApplicationBuilder app, IWebHostEnvironment env) 你也可以自行封裝app.UseShardingCore()


            var shardingBootstrapper = app.ApplicationServices.GetRequiredService<IShardingBootstrapper>();
            shardingBootstrapper.Start();

使用

    
        private readonly IVirtualDbContext _virtualDbContext;

        public ctor(IVirtualDbContext virtualDbContext)
        {
            _virtualDbContext = virtualDbContext;
        }

        public async Task ToList_All()
        {
             //查詢list集合
            var all=await _virtualDbContext.Set<SysUserMod>().ToShardingListAsync();
            //連結查詢
            var list = await (from u in _virtualDbContext.Set<SysUserMod>()
                join salary in _virtualDbContext.Set<SysUserSalary>()
                    on u.Id equals salary.UserId
                select new
                {
                    Salary = salary.Salary,
                    DateOfMonth = salary.DateOfMonth,
                    Name = u.Name
                }).ToShardingListAsync();
            //聚合查詢
            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),
                    MinSalary = g.Min(o => o.Salary),
                    MaxSalary = g.Max(o => o.Salary)
                }).ToShardingListAsync();
        }

更多操作可以參考單元測試

Api

方法 Method SqlServer Unit Test MySql Unit Test
獲取集合 ToShardingListAsync yes yes
第一條 ShardingFirstOrDefaultAsync yes yes
最大 ShardingMaxAsync yes yes
最小 ShardingMinAsync yes yes
是否存在 ShardingAnyAsync yes yes
分頁 ToShardingPageResultAsync yes yes
數目 ShardingCountAsync yes yes
求和 ShardingSumAsync yes yes
分組 ShardingGroupByAsync yes yes

預設路由

抽象abstract 路由規則 tail 索引
AbstractSimpleShardingModKeyIntVirtualRoute 取模 0,1,2... =
AbstractSimpleShardingModKeyStringVirtualRoute 取模 0,1,2... =
AbstractSimpleShardingDayKeyDateTimeVirtualRoute 按時間 yyyyMMdd >,>=,<,<=,=,contains
AbstractSimpleShardingDayKeyLongVirtualRoute 按時間戳 yyyyMMdd >,>=,<,<=,=,contains
AbstractSimpleShardingWeekKeyDateTimeVirtualRoute 按時間 yyyyMMdd_dd >,>=,<,<=,=,contains
AbstractSimpleShardingWeekKeyLongVirtualRoute 按時間戳 yyyyMMdd_dd >,>=,<,<=,=,contains
AbstractSimpleShardingMonthKeyDateTimeVirtualRoute 按時間 yyyyMM >,>=,<,<=,=,contains
AbstractSimpleShardingMonthKeyLongVirtualRoute 按時間戳 yyyyMM >,>=,<,<=,=,contains
AbstractSimpleShardingYearKeyDateTimeVirtualRoute 按時間 yyyy >,>=,<,<=,=,contains
AbstractSimpleShardingYearKeyLongVirtualRoute 按時間戳 yyyy >,>=,<,<=,=,contains

注:contains表示為o=>ids.contains(o.shardingkey)

高階

批量操作

批量操作將對應的dbcontext和資料進行分離由使用者自己選擇第三方框架比如zzz進行批量操作或者batchextension

 virtualDbContext.BulkInsert<SysUserMod>(new List<SysUserMod>())
.BatchGroups.ForEach(pair =>
{
    ///zzz or other
    pair.Key.BlukInsert(pair.Value);
});
var shardingBatchUpdateEntry = virtualDbContext.BulkUpdate<SysUserMod>(o => o.Id == "1", o => new SysUserMod()
{
Name = "name_01"
});
shardingBatchUpdateEntry.DbContexts.ForEach(context =>
{
//zzz or other
context.Where(shardingBatchUpdateEntry.Where).Update(shardingBatchUpdateEntry.UpdateExp);
});

手動路由

        var shardingQueryable = _virtualDbContext.Set<SysUserMod>().AsSharding();
        //禁用自動路由
        shardingQueryable.DisableAutoRouteParse();
        //新增路由直接查詢尾巴0的表
        shardingQueryable.AddManualRoute<SysUserMod>("0");
        //新增路由針對該條件的路由
        shardingQueryable.AddManualRoute<SysUserMod>(o=>o.Id=="100");
        var list=await shardingQueryable.ToListAsync();

自動建表

參考

事務

預設savechanges支援事務如果需要where.update需要手動開啟事務


            _virtualDbContext.BeginTransaction();
            var shardingBatchUpdateEntry = _virtualDbContext.BulkUpdate<SysUserMod>(o=>o.Id=="123",o=>new SysUserMod()
            {
                Name = "name_modify"
            });
            foreach (var dbContext in shardingBatchUpdateEntry.DbContexts)
            {
             //zzz or other batch   
            }
            await  _virtualDbContext.SaveChangesAsync();

注意事項

該庫的IVirtualDbContext.Set使用asnotracking所以基本不支援跟蹤,目前框架採用AppDomain.CurrentDomain.GetAssemblies();
可能會導致程式集未被載入所以儘可能在api層載入所需要的dll

計劃

  • [提供官網如果該專案比較成功的話]
  • [開發更完善的文件]
  • [支援分庫]
  • [支援更多資料庫查詢]

最後

理論上該庫的思想可以解決大部分orm的分表,目前是僅針對efcore的後期如果可以獲取也會對其他orm進行sharding庫的開發
該框架借鑑了大部分分表元件的思路,目前提供的介面都已經實現,並且支援跨表查詢,基於分頁查詢該框架也使用了流式查詢保證不會再skip大資料的時候記憶體會爆炸,至於groupby目前已經在開發支援了,相信不久後就會發布新版本,目前這個庫只是一個剛剛成型的庫還有很多不完善的地方希望大家多多包涵,如果喜歡的話也希望大家給個star.
該文件是我晚上趕工趕出來的也想趁熱打鐵希望更多的人關注,也希望更多的人可以交流。

憑藉各大開源生態圈提供的優秀程式碼和思路才有的這個框架,希望可以為.Net生態提供一份微薄之力,該框架本人會一直長期維護,有大神技術支援可以聯絡下方方式歡迎star ?

部落格

QQ群:771630778

個人QQ:326308290(歡迎技術支援提供您寶貴的意見)

個人郵箱:326308290@qq.com

相關文章