找回密码
 立即注册
首页 业界区 安全 推荐一款基于EF-Core的分库分表利器

推荐一款基于EF-Core的分库分表利器

拓拔梨婷 2025-6-24 10:42:54
在实际应用开发中,有些项目可能数据量特别大,在系统应用一段时间后,性能随着数据量的增加会逐步下降,从而造成系统不定时卡顿等现象,在客户使用过程中也会产生不好的印象。在这种情况下,常规操作是增加索引,优化SQL语句等方案,这种常规操作可能会短暂的解决卡顿问题,但是随着数据量持续增多,效果反而越来越不明显。当常规操作逐渐不起作用的时候,我们就需要往更深层次的去考虑,比如分库,分表等缩减数据体量的方案。今天我们以一个简单的小例子,简述如何在ASP.NET Core WebApi程序中,通过引入ShardingCore组件进行分库分表等操作。
 
什么是ShardingCore?

 
ShardingCore是一款基于EntityFrameworkCore的高性能、轻量级针对分表分库读写分离的解决方案。它支持efcore2+的所有版本,支持efcore2+的所有数据库、支持自定义路由、动态路由、高性能分页、读写分离的一款组件,一款零依赖第三方组件的扩展,如果您熟悉efcore的使用那么对于这个组件您只需要简单配置即可零成本开始使用。
1.png

ShardingCore整体架构图,如下所示:
2.png

上图可知整体架构表现为以entity作为核心驱动来牵引整个框架。通过ShardingCore,可以实现同时兼容分库分表操作,还可以兼容部分表不分库分表等场景。
 
开发环境

 
在本示例中,开发环境如下:

  • 开发工具:Visual Studio 2022。
  • 项目框架:基于.NET8.0的ASP.NET WebApi项目。
  • ORM框架:Entity Framework Core (EF Core)  v9.0.6
  • 分库分表组件:ShardingCore 7.9.1.24
 
操作步骤

 
使用用ShardingCore进行分库分表操作,主要步骤如下所示:
 
创建项目并安装组件

 
首先创建一个ASP.NET Core WebApi项目,然后安装ShardingCore组件。在Visual Studio 2022中,通过Nuget包管理器进行安装,当前最新版本为7.9.1.24,如下所示:
3.png

 
创建模型

 
我们以订单表(Order)为例,订单表包含订单Id,购买人,订单金额,所属区域,订单状态,创建时间等内容,如下所示:
  1. namespace Okcoder.ShardingCore.Model
  2. {
  3.     /// <summary>
  4.     /// 订单表
  5.     /// </summary>
  6.     public class Order
  7.     {
  8.         /// <summary>
  9.         /// 订单Id
  10.         /// </summary>
  11.         public string Id { get; set; }
  12.         /// <summary>
  13.         /// 付款人
  14.         /// </summary>
  15.         public string Payer { get; set; }
  16.         /// <summary>
  17.         /// 订单金额
  18.         /// </summary>
  19.         public long Money { get; set; }
  20.         /// <summary>
  21.         /// 所属区域
  22.         /// </summary>
  23.         public string Area { get; set; }
  24.         /// <summary>
  25.         /// 订单状态
  26.         /// </summary>
  27.         public OrderStatusEnum OrderStatus { get; set; }
  28.         /// <summary>
  29.         /// 创建时间
  30.         /// </summary>
  31.         public DateTime CreationTime { get; set; }
  32.     }
  33.     /// <summary>
  34.     /// 订单状态枚举
  35.     /// </summary>
  36.     public enum OrderStatusEnum
  37.     {
  38.         NoPay = 1,
  39.         Paying = 2,
  40.         Payed = 3,
  41.         PayFail = 4
  42.     }
  43. }
复制代码
 
创建DbContext

 
EF-Core框架需要实现DbContext,在ShardingCore项目中,如果只分库,则仅需要继承自AbstractShardingDbContext;如果还需要分表,则需要实现IShardingTableDbContext接口。本示例需要同时实现分库分表,所以既需要继承自AbstractShardingDbContext,又需要实现IShardingTableDbContext。主要实现OnModelCreating方法,将模型Order和数据表进行映射,以及构造函数。如下所示:
  1. using Microsoft.EntityFrameworkCore;
  2. using Okcoder.ShardingCore.Model;
  3. using ShardingCore.Core.VirtualRoutes.TableRoutes.RouteTails.Abstractions;
  4. using ShardingCore.Sharding.Abstractions;
  5. using ShardingCore.Sharding;
  6. namespace Okcoder.ShardingCore.DAL
  7. {
  8.     public class DefaultShardingDbContext : AbstractShardingDbContext, IShardingTableDbContext
  9.     {
  10.         public DefaultShardingDbContext(DbContextOptions<DefaultShardingDbContext> options) : base(options)
  11.         {
  12.         }
  13.         protected override void OnModelCreating(ModelBuilder modelBuilder)
  14.         {
  15.             base.OnModelCreating(modelBuilder);
  16.             modelBuilder.Entity<Order>(entity =>
  17.             {
  18.                 entity.HasKey(o => o.Id);
  19.                 entity.Property(o => o.Id).IsRequired().IsUnicode(false).HasMaxLength(50);
  20.                 entity.Property(o => o.Payer).IsRequired().IsUnicode(false).HasMaxLength(50);
  21.                 entity.Property(o => o.Area).IsRequired().IsUnicode(false).HasMaxLength(50);
  22.                 entity.Property(o => o.OrderStatus).HasConversion<int>();
  23.                 entity.ToTable(nameof(Order));
  24.             });
  25.         }
  26.         /// <summary>
  27.         /// 如果分表,则空实现即可
  28.         /// </summary>
  29.         public IRouteTail RouteTail { get; set; }
  30.     }
  31. }
复制代码
 
创建虚拟分表路由

 
通过ShardingCore架构图可以看出,每一个表对应一个分表路由,同时ShardingCore默认实现了很多路由,这样可以直接继承,省去了很多麻烦,本示例主要继承自AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute,实现按月分表功能。主要定义分表的规则逻辑,包括分表起始时间,分表列,分表后缀等内容,具体如下所示:
  1. using Okcoder.ShardingCore.Model;
  2. using ShardingCore.Core.EntityMetadatas;
  3. using ShardingCore.VirtualRoutes.Months;
  4. namespace Okcoder.ShardingCore.DAL
  5. {
  6.     /// <summary>
  7.     /// 路由构造函数支持依赖注入,依赖注入的对象生命周期必须是单例
  8.     /// </summary>
  9.     public class OrderVirtualTableRoute : AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute<Order>
  10.     {
  11.         /// <summary>
  12.         /// 获取分表起始时间
  13.         /// </summary>
  14.         /// <returns></returns>
  15.         public override DateTime GetBeginTime()
  16.         {
  17.             return new DateTime(2024, 1, 1);
  18.         }
  19.         /// <summary>
  20.         /// 配置分表属性
  21.         /// </summary>
  22.         /// <param name="builder"></param>
  23.         public override void Configure(EntityMetadataTableBuilder<Order> builder)
  24.         {
  25.             builder.ShardingProperty(o => o.CreationTime);
  26.             //builder.AutoCreateTable(true);
  27.         }
  28.         /// <summary>
  29.         /// 允许创建分表流程
  30.         /// </summary>
  31.         /// <returns></returns>
  32.         public override bool AutoCreateTableByTime()
  33.         {
  34.             return true;
  35.         }
  36.         /// <summary>
  37.         /// 分表后缀格式
  38.         /// </summary>
  39.         /// <param name="time"></param>
  40.         /// <returns></returns>
  41.         protected override string TimeFormatToTail(DateTime time)
  42.         {
  43.             return time.ToString("MM");
  44.         }
  45.         protected override bool RouteIgnoreDataSource => false;
  46.     }
  47. }
复制代码
说明:虚拟路由就是联系虚拟表和物理表的中间介质,虚拟表在整个程序中只有一份,那么程序如何知道要查询系统哪一张表呢,最简单的方式就是通过虚拟表对应的路由IVirtualTableRoute 。
 
创建虚拟分库路由

 
分库路由继承自AbstractShardingOperatorVirtualDataSourceRoute,并实现抽象方法,主要包括动态创建数据源,以及路由Filter筛选函数,和ShardingKey和数据源映射方法,如下所示:
  1. using Okcoder.ShardingCore.Model;
  2. using ShardingCore.Core.EntityMetadatas;
  3. using ShardingCore.Core.VirtualRoutes.DataSourceRoutes.Abstractions;
  4. using ShardingCore.Core.VirtualRoutes;
  5. using System.Collections.Concurrent;
  6. namespace Okcoder.ShardingCore.DAL
  7. {
  8.     /// <summary>
  9.     /// 分库路由
  10.     /// </summary>
  11.     public class OrderVirtualDbRoute : AbstractShardingOperatorVirtualDataSourceRoute<Order, DateTime>
  12.     {
  13.         private readonly ConcurrentBag<string> dataSources = new ConcurrentBag<string>();
  14.         private readonly object _lock = new object();
  15.         public OrderVirtualDbRoute():base()
  16.         {
  17.             for (int i = 0; i < 10; i++)
  18.             {
  19.                 dataSources.Add(DateTime.Now.AddYears(i).ToString("yyyy"));
  20.             }
  21.         }
  22.         public override bool AddDataSourceName(string dataSourceName)
  23.         {
  24.             var acquire = Monitor.TryEnter(_lock, TimeSpan.FromSeconds(3));
  25.             if (!acquire)
  26.             {
  27.                 return false;
  28.             }
  29.             try
  30.             {
  31.                 var contains = dataSources.Contains(dataSourceName);
  32.                 if (!contains)
  33.                 {
  34.                     dataSources.Add(dataSourceName);
  35.                     return true;
  36.                 }
  37.             }
  38.             finally
  39.             {
  40.                 Monitor.Exit(_lock);
  41.             }
  42.             return false;
  43.         }
  44.         public override void Configure(EntityMetadataDataSourceBuilder<Order> builder)
  45.         {
  46.             builder.ShardingProperty(o => o.CreationTime);
  47.         }
  48.         public override List<string> GetAllDataSourceNames()
  49.         {
  50.             return dataSources.ToList();
  51.         }
  52.         /// <summary>
  53.         /// tail就是2020,2021,2022,2023,2024,2025 所以分片只需要格式化年就可以直接比较了
  54.         /// </summary>
  55.         /// <param name="shardingKey"></param>
  56.         /// <param name="shardingOperator"></param>
  57.         /// <returns></returns>
  58.         public override Func<string, bool> GetRouteToFilter(DateTime shardingKey, ShardingOperatorEnum shardingOperator)
  59.         {
  60.             var t = $"{shardingKey:yyyyy}";
  61.             switch (shardingOperator)
  62.             {
  63.                 case ShardingOperatorEnum.GreaterThan:
  64.                 case ShardingOperatorEnum.GreaterThanOrEqual:
  65.                     return tail => String.Compare(tail, t, StringComparison.Ordinal) >= 0;
  66.                 case ShardingOperatorEnum.LessThan:
  67.                     {
  68.                         var currentYear = new DateTime(shardingKey.Year, 1, 1);
  69.                         //处于临界值 o=>o.time < [2021-01-01 00:00:00] 尾巴20210101不应该被返回
  70.                         if (currentYear == shardingKey)
  71.                             return tail => String.Compare(tail, t, StringComparison.Ordinal) < 0;
  72.                         return tail => String.Compare(tail, t, StringComparison.Ordinal) <= 0;
  73.                     }
  74.                 case ShardingOperatorEnum.LessThanOrEqual:
  75.                     return tail => String.Compare(tail, t, StringComparison.Ordinal) <= 0;
  76.                 case ShardingOperatorEnum.Equal: return tail => tail == t;
  77.                 default:
  78.                     {
  79.                         return tail => true;
  80.                     }
  81.             }
  82.         }
  83.         public override string ShardingKeyToDataSourceName(object shardingKey)
  84.         {
  85.             return $"{shardingKey:yyyy}";//年份作为分库数据源名称
  86.         }
  87.     }
  88. }
复制代码
 说明:数据源名称用来将对象路由到具体的数据源。
 
配置启动项

 
在Program的Main方法中配置ShardingCore启动项,主要配置默认数据源,扩展数据源,已经使用数据类型等内容。如下所示:
  1. using Microsoft.EntityFrameworkCore;
  2. using Okcoder.ShardingCore.DAL;
  3. using ShardingCore;
  4. namespace Okcoder.ShardingCore
  5. {
  6.     public class Program
  7.     {
  8.         public static void Main(string[] args)
  9.         {
  10.             var builder = WebApplication.CreateBuilder(args);
  11.             // Add services to the container.
  12.             builder.Services.AddControllers();
  13.             builder.Services.AddShardingDbContext<DefaultShardingDbContext>()
  14.                 .UseRouteConfig(op =>
  15.                 {
  16.                     op.AddShardingDataSourceRoute<OrderVirtualDbRoute>();
  17.                     op.AddShardingTableRoute<OrderVirtualTableRoute>();
  18.                 }).UseConfig(op =>
  19.                 {
  20.                     op.UseShardingQuery((connStr, builder) =>
  21.                     {
  22.                         //connStr is delegate input param
  23.                         builder.UseSqlServer(connStr);
  24.                     });
  25.                     op.UseShardingTransaction((connection, builder) =>
  26.                     {
  27.                         //connection is delegate input param
  28.                         builder.UseSqlServer(connection);
  29.                     });
  30.                     op.AddDefaultDataSource("2024", builder.Configuration.GetConnectionString("Default"));
  31.                     op.AddExtraDataSource(sp =>
  32.                     {
  33.                         var dict = new Dictionary<string, string>();
  34.                         for (int i = 0; i < 10; i++)
  35.                         {
  36.                             var key = DateTime.Now.AddYears(i).ToString("yyyy");
  37.                             dict.Add(key, $"Server=localhost;Database=TestDb{key};Trusted_Connection=True;User Id=sa;Password=abc123;Encrypt=True;TrustServerCertificate=True;");
  38.                         }
  39.                         ;
  40.                         return dict;
  41.                     });
  42.                 }).AddShardingCore();
  43.             var app = builder.Build();
  44.             // Configure the HTTP request pipeline.
  45.             app.UseHttpsRedirection();
  46.             app.UseAuthorization();
  47.             app.MapControllers();
  48.             using (var scope = app.Services.CreateScope())
  49.             {
  50.                 var testDbContext = scope.ServiceProvider.GetService<DefaultShardingDbContext>();
  51.                 testDbContext.Database.EnsureCreated();
  52.             }
  53.             app.Services.UseAutoTryCompensateTable();
  54.             app.Run();
  55.         }
  56.     }
  57. }
复制代码
在上述示例中,最重要的实现自动创建分库,分表的是使用UseAutoTryCompensateTable()方法。
 
配置数据源

 
在appsettings.json文件中,配置默认数据源,如下所示:
4.png

 
创建控制器

 
在本示例中,为了测试,创建了OrderController,主要用于插入测试订单,如下所示:
  1. using Microsoft.AspNetCore.Mvc;
  2. using Microsoft.EntityFrameworkCore;
  3. using Okcoder.ShardingCore.DAL;
  4. using Okcoder.ShardingCore.Model;
  5. namespace Okcoder.ShardingCore.Controllers
  6. {
  7.     [ApiController]
  8.     [Route("[controller]/[action]")]
  9.     public class OrderController : ControllerBase
  10.     {
  11.         private readonly DefaultShardingDbContext dbContext;
  12.         public OrderController(DefaultShardingDbContext dbContext)
  13.         {
  14.             this.dbContext = dbContext;
  15.         }
  16.         [HttpGet()]
  17.         public string Add()
  18.         {
  19.             dbContext.Add(new Order()
  20.             {
  21.                 Id = Guid.NewGuid().ToString("n"),
  22.                 Payer = "111",
  23.                 Area = "123",
  24.                 OrderStatus = OrderStatusEnum.Payed,
  25.                 Money = 100,
  26.                 CreationTime = DateTime.Now
  27.             });
  28.             dbContext.SaveChanges();
  29.             return "Ok";
  30.         }
  31.     }
  32. }
复制代码
 
运行实例

 
通过上述步骤,可以实现按年分库,按月分表的功能,运行程序后,发现已经创建成功,如下所示:
5.png

调用Order/Add接口后,查看数据库后,发现数据已经成功插入,如下所示:
 
6.png

 
参考文档

 
本文主要参考官方文档等资料:
官方文档:https://gitee.com/hubo/sharding-core
以上就是《推荐一款基于EF-Core的分库分表利器》的全部内容,旨在抛砖引玉,一起学习,共同进步。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册