EFCore.Sharding(EFCore開源分表框架)


簡介

本框架旨在為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參數,常用按天、按月分表

自動分表效果
JKxE8K.png
全程無需人工干預,系統會自動定時創建分表,十分簡單好用

性能測試

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三張表總合數據一致)

分表與不分表測試結果如下

JMSJBQ.png

這里僅僅分了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生態

歡迎使用本框架,若覺得不錯,請比心

Github歡迎星星:https://github.com/Coldairarrow

博客園歡迎點贊:https://www.cnblogs.com/coldairarrow/

QQ群3:940069478
個人QQ:862520575(歡迎技術支持及商務合作

本人將會對這個快速開發框架不斷完善與維護,希望能夠幫助到各位
若遇到任何問題或需要技術支持,請聯系我
------學習永無止境,技術永無上限,代碼就是藝術------


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM