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

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

簡介

本框架旨在爲EF Core提供Sharding(即讀寫分離分庫分表)支持,不只提供了一套強大的普通數據操做接口,而且下降了分表難度,支持按時間自動分表擴容,提供的操做接口簡潔統一.git

源碼地址:EFCore.SHardinggithub

引言

讀寫分離分庫分表一直是數據庫領域中的重難點,當數據規模達到單庫極限的時候,就不得不考慮分表方案。EF Core做爲.NET Core中最爲主流的ORM,用起來十分方便快捷,可是官方並無相應的Sharding支持,鄙人不才,通過一番摸索以後終於完成這個框架.數據庫

開始

準備

首先根據須要安裝對應的Nuget包c#

包名 說明
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支持
EFCore.Sharding.Oracle Oracle支持(暫不支持3.x)

配置

class Base_UnitTestShardingRule : ModShardingRule<Base_UnitTest>
{
    protected override string KeyField => "Id";
    protected override int Mod => 3;
}

ShardingConfig.Init(config =>
{
    config.AddAbsDb(DatabaseType.SQLite)
        .AddPhysicDb(ReadWriteType.Read | ReadWriteType.Write, "DataSource=db.db")
        .AddPhysicDbGroup()
        .AddPhysicTable<Base_UnitTest>("Base_UnitTest_0")
        .AddPhysicTable<Base_UnitTest>("Base_UnitTest_1")
        .AddPhysicTable<Base_UnitTest>("Base_UnitTest_2")
        .SetShardingRule(new Base_UnitTestShardingRule());
});

上述代碼中完成了Sharding配置框架

  • AddAbsDb是指添加抽象數據庫,抽象數據庫就是將多個分庫當作同一個數據庫來進行操做
  • AddPhysicDbGroup是指添加物理數據庫組,在同一組物理數據庫中,它們數據庫類型相同,擁有的表相同,每一個數據庫擁有的數據是一致的(之間經過主主複製或主從複製進行數據同步)
  • AddPhysicTable是指添加物理數據表,傳入的Base_UnitTest是抽象數據表(即將Base_UnitTest拆分爲Base_UnitTest_0~2)
  • Base_UnitTestShardingRule是採用的分表規則,上述代碼中採用的是哈希取模的分表方式

使用

配置完成,下面開始使用,使用方式很是簡單,與日常使用基本一致
首先獲取分片倉儲接口IShardingRepositoryide

IShardingRepository _db = DbFactory.GetRepository().ToSharding();

而後便可進行數據操做:性能

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.UpdateAny(_newData, new List<string> { "UserName", "Age" });
//更新多條數據指定屬性
_db.UpdateAny(_insertList, new List<string> { "UserName", "Age" });
//更新指定條件數據
_db.UpdateWhere<Base_UnitTest>(x => x.UserId == "Admin", x =>
{
    x.UserId = "Admin2";
});
//GetList獲取表的全部數據
var list=_db.GetList<Base_UnitTest>();
//GetIQPagination獲取分頁後的數據
var list=_db.GetIShardingQueryable<Base_UnitTest>().GetPagination(pagination);
//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 Demo.Common;
using EFCore.Sharding;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Demo.AutoExpandByDate
{
    class Base_UnitTestShardingRule : AbsShardingRule<Base_UnitTest>
    {
        public override DateTime BuildDate(Base_UnitTest obj)
        {
            return obj.CreateTime;
        }
    }

    class Program
    {
        /// <summary>
        /// 表都在同一個數據庫中
        /// </summary>
        public static void OneGroup()
        {
            DateTime startTime = DateTime.Now.AddMinutes(-5);
            DateTime endTime = DateTime.MaxValue;

            //配置初始化
            ShardingConfig.Init(config =>
            {
                config.AddAbsDb(DatabaseType.SqlServer)//添加抽象數據庫
                    .AddPhysicDbGroup()//添加物理數據庫組
                    .AddPhysicDb(ReadWriteType.Read | ReadWriteType.Write, Config.ConString1)//添加物理數據庫1
                    .SetShardingRule(new Base_UnitTestShardingRule())//設置分表規則
                    .AutoExpandByDate<Base_UnitTest>(//設置爲按時間自動分表
                        ExpandByDateMode.PerMinute,
                        (startTime, endTime, ShardingConfig.DefaultDbGourpName)
                        );
            });
            var db = DbFactory.GetShardingRepository();
            while (true)
            {
                db.Insert(new Base_UnitTest
                {
                    Id = Guid.NewGuid().ToString(),
                    Age = 1,
                    UserName = Guid.NewGuid().ToString(),
                    CreateTime = DateTime.Now
                });

                var count = db.GetIShardingQueryable<Base_UnitTest>().Count();
                Console.WriteLine($"當前數據量:{count}");

                Thread.Sleep(50);
            }
        }

        /// <summary>
        /// 表分佈在兩個數據庫測試
        /// </summary>
        public static void TwoGroup()
        {
            DateTime startTime1 = DateTime.Now.AddMinutes(-5);
            DateTime endTime1 = DateTime.Now.AddMinutes(5);
            DateTime startTime2 = endTime1;
            DateTime endTime2 = DateTime.MaxValue;

            string group1 = "group1";
            string group2 = "group2";

            //配置初始化
            ShardingConfig.Init(config =>
            {
                config.AddAbsDb(DatabaseType.SqlServer)//添加抽象數據庫
                    .AddPhysicDbGroup(group1)//添加物理數據庫組1
                    .AddPhysicDbGroup(group2)//添加物理數據庫組2
                    .AddPhysicDb(ReadWriteType.Read | ReadWriteType.Write, Config.ConString1, group1)//添加物理數據庫1
                    .AddPhysicDb(ReadWriteType.Read | ReadWriteType.Write, Config.ConString2, group2)//添加物理數據庫2
                    .SetShardingRule(new Base_UnitTestShardingRule())//設置分表規則
                    .AutoExpandByDate<Base_UnitTest>(//設置爲按時間自動分表
                        ExpandByDateMode.PerMinute,
                        (startTime1, endTime1, group1),
                        (startTime2, endTime2, group2)
                        );
            });

            List<Task> tasks = new List<Task>();
            for (int i = 0; i < 4; i++)
            {
                tasks.Add(Task.Run(() =>
                {
                    var db = DbFactory.GetShardingRepository();
                    while (true)
                    {
                        db.Insert(new Base_UnitTest
                        {
                            Id = Guid.NewGuid().ToString(),
                            Age = 1,
                            UserName = Guid.NewGuid().ToString(),
                            CreateTime = DateTime.Now
                        });

                        var count = db.GetIShardingQueryable<Base_UnitTest>().Count();
                        Console.WriteLine($"當前數據量:{count}");

                        Thread.Sleep(50);
                    }
                }));
            }

            Console.ReadLine();
        }

        static void Main(string[] args)
        {
            OneGroup();

            Console.ReadLine();
        }
    }
}

上面Demo都在源碼中測試

上面的代碼實現了將Base_UnitTest表按照時間自動分表,每分鐘建立一張表,實際使用中根據業務需求設置ExpandByDateMode參數,經常使用按天、按月分表

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

性能測試

using Demo.Common;
using EFCore.Sharding;
using System;
using System.Diagnostics;
using System.Linq;

namespace Demo.Performance
{
    class Base_UnitTestShardingRule : ModShardingRule<Base_UnitTest>
    {
        protected override string KeyField => "Id";
        protected override int Mod => 3;
    }

    class Program
    {
        static void Main(string[] args)
        {
            ShardingConfig.Init(config =>
            {
                config.AddAbsDb(DatabaseType.SqlServer)
                    .AddPhysicDb(ReadWriteType.Read | ReadWriteType.Write, Config.ConString1)
                    .AddPhysicDbGroup()
                    .AddPhysicTable<Base_UnitTest>("Base_UnitTest_0")
                    .AddPhysicTable<Base_UnitTest>("Base_UnitTest_1")
                    .AddPhysicTable<Base_UnitTest>("Base_UnitTest_2")
                    .SetShardingRule(new Base_UnitTestShardingRule());
            });

            var db = DbFactory.GetRepository(Config.ConString1, DatabaseType.SqlServer);
            Stopwatch watch = new Stopwatch();
            var q = db.GetIQueryable<Base_UnitTest>()
                .Where(x => x.UserName.Contains("00001C22-8DD2-4D47-B500-407554B099AB"))
                .OrderByDescending(x => x.Id)
                .Skip(0)
                .Take(30);

            q.ToList();
            q.ToSharding().ToList();
            watch.Restart();
            var list1 = q.ToList();
            watch.Stop();
            Console.WriteLine($"未分表耗時:{watch.ElapsedMilliseconds}ms");
            watch.Restart();
            var list2 = q.ToSharding().ToList();
            watch.Stop();
            Console.WriteLine($"分表後耗時:{watch.ElapsedMilliseconds}ms");

            Console.WriteLine("完成");
        }
    }
}

分表Base_UnitTest_0-2各有100萬數據,而後將這三張表的數據導入Base_UnitTest中(即Base_UnitTest表的數據與Base_UnitTest_0-2三張表總合數據一致)

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

JMSJBQ.png

這裏僅僅分了3張表,其效果立杆見影,若分表幾十張,那效果想一想就很棒

其它簡單操做(非Sharing)

框架不只支持Sharing,並且封裝了經常使用數據庫操做,使用比較簡單
詳細使用方式參考 連接

總結

這個簡單實用強大的框架但願可以幫助到你們,力求爲.NET生態貢獻一份力,你們一塊兒壯大.NET生態

歡迎使用本框架,若以爲不錯,請比心

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

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

QQ羣3:940069478
我的QQ:862520575(歡迎技術支持及商務合做,提供.NET Core + Linux + Nginx+ jenkins + git整套持續集成快速開發平臺

本人將會對這個快速開發框架不斷完善與維護,但願可以幫助到各位
若遇到任何問題或須要技術支持,請聯繫我
------學習永無止境,技術永無上限,代碼就是藝術------
相關文章
相關標籤/搜索