推荐一款基于EF-Core的分库分表利器
在实际应用开发中,有些项目可能数据量特别大,在系统应用一段时间后,性能随着数据量的增加会逐步下降,从而造成系统不定时卡顿等现象,在客户使用过程中也会产生不好的印象。在这种情况下,常规操作是增加索引,优化SQL语句等方案,这种常规操作可能会短暂的解决卡顿问题,但是随着数据量持续增多,效果反而越来越不明显。当常规操作逐渐不起作用的时候,我们就需要往更深层次的去考虑,比如分库,分表等缩减数据体量的方案。今天我们以一个简单的小例子,简述如何在ASP.NET Core WebApi程序中,通过引入ShardingCore组件进行分库分表等操作。什么是ShardingCore?
ShardingCore是一款基于EntityFrameworkCore的高性能、轻量级针对分表分库读写分离的解决方案。它支持efcore2+的所有版本,支持efcore2+的所有数据库、支持自定义路由、动态路由、高性能分页、读写分离的一款组件,一款零依赖第三方组件的扩展,如果您熟悉efcore的使用那么对于这个组件您只需要简单配置即可零成本开始使用。
ShardingCore整体架构图,如下所示:
上图可知整体架构表现为以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,如下所示:
创建模型
我们以订单表(Order)为例,订单表包含订单Id,购买人,订单金额,所属区域,订单状态,创建时间等内容,如下所示:
namespace Okcoder.ShardingCore.Model
{
/// <summary>
/// 订单表
/// </summary>
public class Order
{
/// <summary>
/// 订单Id
/// </summary>
public string Id { get; set; }
/// <summary>
/// 付款人
/// </summary>
public string Payer { get; set; }
/// <summary>
/// 订单金额
/// </summary>
public long Money { get; set; }
/// <summary>
/// 所属区域
/// </summary>
public string Area { get; set; }
/// <summary>
/// 订单状态
/// </summary>
public OrderStatusEnum OrderStatus { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreationTime { get; set; }
}
/// <summary>
/// 订单状态枚举
/// </summary>
public enum OrderStatusEnum
{
NoPay = 1,
Paying = 2,
Payed = 3,
PayFail = 4
}
}
创建DbContext
EF-Core框架需要实现DbContext,在ShardingCore项目中,如果只分库,则仅需要继承自AbstractShardingDbContext;如果还需要分表,则需要实现IShardingTableDbContext接口。本示例需要同时实现分库分表,所以既需要继承自AbstractShardingDbContext,又需要实现IShardingTableDbContext。主要实现OnModelCreating方法,将模型Order和数据表进行映射,以及构造函数。如下所示:
using Microsoft.EntityFrameworkCore;
using Okcoder.ShardingCore.Model;
using ShardingCore.Core.VirtualRoutes.TableRoutes.RouteTails.Abstractions;
using ShardingCore.Sharding.Abstractions;
using ShardingCore.Sharding;
namespace Okcoder.ShardingCore.DAL
{
public class DefaultShardingDbContext : AbstractShardingDbContext, IShardingTableDbContext
{
public DefaultShardingDbContext(DbContextOptions<DefaultShardingDbContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Order>(entity =>
{
entity.HasKey(o => o.Id);
entity.Property(o => o.Id).IsRequired().IsUnicode(false).HasMaxLength(50);
entity.Property(o => o.Payer).IsRequired().IsUnicode(false).HasMaxLength(50);
entity.Property(o => o.Area).IsRequired().IsUnicode(false).HasMaxLength(50);
entity.Property(o => o.OrderStatus).HasConversion<int>();
entity.ToTable(nameof(Order));
});
}
/// <summary>
/// 如果分表,则空实现即可
/// </summary>
public IRouteTail RouteTail { get; set; }
}
}
创建虚拟分表路由
通过ShardingCore架构图可以看出,每一个表对应一个分表路由,同时ShardingCore默认实现了很多路由,这样可以直接继承,省去了很多麻烦,本示例主要继承自AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute,实现按月分表功能。主要定义分表的规则逻辑,包括分表起始时间,分表列,分表后缀等内容,具体如下所示:
using Okcoder.ShardingCore.Model;
using ShardingCore.Core.EntityMetadatas;
using ShardingCore.VirtualRoutes.Months;
namespace Okcoder.ShardingCore.DAL
{
/// <summary>
/// 路由构造函数支持依赖注入,依赖注入的对象生命周期必须是单例
/// </summary>
public class OrderVirtualTableRoute : AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute<Order>
{
/// <summary>
/// 获取分表起始时间
/// </summary>
/// <returns></returns>
public override DateTime GetBeginTime()
{
return new DateTime(2024, 1, 1);
}
/// <summary>
/// 配置分表属性
/// </summary>
/// <param name="builder"></param>
public override void Configure(EntityMetadataTableBuilder<Order> builder)
{
builder.ShardingProperty(o => o.CreationTime);
//builder.AutoCreateTable(true);
}
/// <summary>
/// 允许创建分表流程
/// </summary>
/// <returns></returns>
public override bool AutoCreateTableByTime()
{
return true;
}
/// <summary>
/// 分表后缀格式
/// </summary>
/// <param name="time"></param>
/// <returns></returns>
protected override string TimeFormatToTail(DateTime time)
{
return time.ToString("MM");
}
protected override bool RouteIgnoreDataSource => false;
}
}说明:虚拟路由就是联系虚拟表和物理表的中间介质,虚拟表在整个程序中只有一份,那么程序如何知道要查询系统哪一张表呢,最简单的方式就是通过虚拟表对应的路由IVirtualTableRoute 。
创建虚拟分库路由
分库路由继承自AbstractShardingOperatorVirtualDataSourceRoute,并实现抽象方法,主要包括动态创建数据源,以及路由Filter筛选函数,和ShardingKey和数据源映射方法,如下所示:
using Okcoder.ShardingCore.Model;
using ShardingCore.Core.EntityMetadatas;
using ShardingCore.Core.VirtualRoutes.DataSourceRoutes.Abstractions;
using ShardingCore.Core.VirtualRoutes;
using System.Collections.Concurrent;
namespace Okcoder.ShardingCore.DAL
{
/// <summary>
/// 分库路由
/// </summary>
public class OrderVirtualDbRoute : AbstractShardingOperatorVirtualDataSourceRoute<Order, DateTime>
{
private readonly ConcurrentBag<string> dataSources = new ConcurrentBag<string>();
private readonly object _lock = new object();
public OrderVirtualDbRoute():base()
{
for (int i = 0; i < 10; i++)
{
dataSources.Add(DateTime.Now.AddYears(i).ToString("yyyy"));
}
}
public override bool AddDataSourceName(string dataSourceName)
{
var acquire = Monitor.TryEnter(_lock, TimeSpan.FromSeconds(3));
if (!acquire)
{
return false;
}
try
{
var contains = dataSources.Contains(dataSourceName);
if (!contains)
{
dataSources.Add(dataSourceName);
return true;
}
}
finally
{
Monitor.Exit(_lock);
}
return false;
}
public override void Configure(EntityMetadataDataSourceBuilder<Order> builder)
{
builder.ShardingProperty(o => o.CreationTime);
}
public override List<string> GetAllDataSourceNames()
{
return dataSources.ToList();
}
/// <summary>
/// tail就是2020,2021,2022,2023,2024,2025 所以分片只需要格式化年就可以直接比较了
/// </summary>
/// <param name="shardingKey"></param>
/// <param name="shardingOperator"></param>
/// <returns></returns>
public override Func<string, bool> GetRouteToFilter(DateTime shardingKey, ShardingOperatorEnum shardingOperator)
{
var t = $"{shardingKey:yyyyy}";
switch (shardingOperator)
{
case ShardingOperatorEnum.GreaterThan:
case ShardingOperatorEnum.GreaterThanOrEqual:
return tail => String.Compare(tail, t, StringComparison.Ordinal) >= 0;
case ShardingOperatorEnum.LessThan:
{
var currentYear = new DateTime(shardingKey.Year, 1, 1);
//处于临界值 o=>o.time < 尾巴20210101不应该被返回
if (currentYear == shardingKey)
return tail => String.Compare(tail, t, StringComparison.Ordinal) < 0;
return tail => String.Compare(tail, t, StringComparison.Ordinal) <= 0;
}
case ShardingOperatorEnum.LessThanOrEqual:
return tail => String.Compare(tail, t, StringComparison.Ordinal) <= 0;
case ShardingOperatorEnum.Equal: return tail => tail == t;
default:
{
return tail => true;
}
}
}
public override string ShardingKeyToDataSourceName(object shardingKey)
{
return $"{shardingKey:yyyy}";//年份作为分库数据源名称
}
}
} 说明:数据源名称用来将对象路由到具体的数据源。
配置启动项
在Program的Main方法中配置ShardingCore启动项,主要配置默认数据源,扩展数据源,已经使用数据类型等内容。如下所示:
using Microsoft.EntityFrameworkCore;
using Okcoder.ShardingCore.DAL;
using ShardingCore;
namespace Okcoder.ShardingCore
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddShardingDbContext<DefaultShardingDbContext>()
.UseRouteConfig(op =>
{
op.AddShardingDataSourceRoute<OrderVirtualDbRoute>();
op.AddShardingTableRoute<OrderVirtualTableRoute>();
}).UseConfig(op =>
{
op.UseShardingQuery((connStr, builder) =>
{
//connStr is delegate input param
builder.UseSqlServer(connStr);
});
op.UseShardingTransaction((connection, builder) =>
{
//connection is delegate input param
builder.UseSqlServer(connection);
});
op.AddDefaultDataSource("2024", builder.Configuration.GetConnectionString("Default"));
op.AddExtraDataSource(sp =>
{
var dict = new Dictionary<string, string>();
for (int i = 0; i < 10; i++)
{
var key = DateTime.Now.AddYears(i).ToString("yyyy");
dict.Add(key, $"Server=localhost;Database=TestDb{key};Trusted_Connection=True;User Id=sa;Password=abc123;Encrypt=True;TrustServerCertificate=True;");
}
;
return dict;
});
}).AddShardingCore();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
using (var scope = app.Services.CreateScope())
{
var testDbContext = scope.ServiceProvider.GetService<DefaultShardingDbContext>();
testDbContext.Database.EnsureCreated();
}
app.Services.UseAutoTryCompensateTable();
app.Run();
}
}
}在上述示例中,最重要的实现自动创建分库,分表的是使用UseAutoTryCompensateTable()方法。
配置数据源
在appsettings.json文件中,配置默认数据源,如下所示:
创建控制器
在本示例中,为了测试,创建了OrderController,主要用于插入测试订单,如下所示:
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Okcoder.ShardingCore.DAL;
using Okcoder.ShardingCore.Model;
namespace Okcoder.ShardingCore.Controllers
{
/")]
public class OrderController : ControllerBase
{
private readonly DefaultShardingDbContext dbContext;
public OrderController(DefaultShardingDbContext dbContext)
{
this.dbContext = dbContext;
}
public string Add()
{
dbContext.Add(new Order()
{
Id = Guid.NewGuid().ToString("n"),
Payer = "111",
Area = "123",
OrderStatus = OrderStatusEnum.Payed,
Money = 100,
CreationTime = DateTime.Now
});
dbContext.SaveChanges();
return "Ok";
}
}
}
运行实例
通过上述步骤,可以实现按年分库,按月分表的功能,运行程序后,发现已经创建成功,如下所示:
调用Order/Add接口后,查看数据库后,发现数据已经成功插入,如下所示:
参考文档
本文主要参考官方文档等资料:
官方文档:https://gitee.com/hubo/sharding-core
以上就是《推荐一款基于EF-Core的分库分表利器》的全部内容,旨在抛砖引玉,一起学习,共同进步。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页:
[1]