第三十二节:ShardingCore框架简介、路由详解、分表详解实操
一. 框架简介
1 官方地址
官方文档:https://xuejmnet.github.io/sharding-core-doc/ (文档里版本不是最新的,但是不影响,结合代码样例一起看就可以)
gitee:https://gitee.com/xuejm/sharding-core
github:https://github.com/dotnetcore/sharding-core
2. 说明
ShardingCore一款针对efcore下高性能、轻量级针对分表分库读写分离的解决方案,具有零依赖、零学习成本、零业务代码入侵。(类似于java中的shardingjdbc)
最大的特点:零业务侵入,比如配置好后,无论是 增删改查,都是直接使用efcore原生的方法即可,代码写法上无需任何改动,所有的分库分表逻辑,都是框架内实现的。
包含的功能:分表、分库、分库且分表、读写分离、动态追加分库分表、批量操作、事务、多字段分片等等。
3. 如何选择版本?
当前使用的版本是 EFCore8.0 + ShardingCore 7.8.1.22
shardingcore x.x.x.x 说明
-
shardingcore 最新版本.efcore版本.x.x
-
版本号第一位x 是shardingcore的版本号使用最大的即可
-
版本号第二位x 是efcore版本号使用对应的版本号即可
-
最后两位版本号使用最大即可
- efcore8 使用 shardingcore7.8.x.x,
-
efcore7 使用 shardingcore7.7.x.x,
-
efcore6 使用 shardingcore7.6.x.x,
4 注意事项
(1) 升级如果不使用app.ApplictaionServices.UseAutoShardingCreate()
将不会自动创建表任务;请注意:如果您是IIS代理,那么请关闭应用程序池的固定回收和空闲回收设置为0;不然ShardingCore的创建表的任务可能不会生效
二. 路由详解
1. 默认的分表路由
针对分表默认提供多种路由:int取模、string取模、long时间按年月周日分表、DateTime时间按年月周日分表,并且按时间分表支持自动创建对应的表无需手动处理
注意:默认xxxKeyLong的路由代表分片时间是毫秒的意思,需要自行判断你生成的long和ShardingCoreHelper.ConvertDateTimeToLong(DateTime.Now)
是否一样
类名 |
路由规则 |
表的后缀格式_ |
索引 |
AbstractSimpleShardingModKeyIntVirtualTableRoute |
Int取模 |
0,1,2... |
=,contains |
AbstractSimpleShardingModKeyStringVirtualTableRoute |
字符串取模 |
0,1,2... |
=,contains |
AbstractSimpleShardingDayKeyDateTimeVirtualTableRoute |
按天分表(DateTime类型) |
yyyyMMdd |
>,>=,<,<=,=,contains |
AbstractSimpleShardingDayKeyLongVirtualTableRoute |
按天分表(Long时间戳) |
yyyyMMdd |
>,>=,<,<=,=,contains |
AbstractSimpleShardingWeekKeyDateTimeVirtualTableRoute |
按周分表(DateTime类型) |
yyyyMMdd_dd |
>,>=,<,<=,=,contains |
AbstractSimpleShardingWeekKeyLongVirtualTableRoute |
按周分表(Long时间戳) |
yyyyMMdd_dd |
>,>=,<,<=,=,contains |
AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute |
按月分表(DateTime类型) |
yyyyMM |
>,>=,<,<=,=,contains |
AbstractSimpleShardingMonthKeyLongVirtualTableRoute |
按月分表(Long时间戳) |
yyyyMM |
>,>=,<,<=,=,contains |
AbstractSimpleShardingYearKeyDateTimeVirtualTableRoute |
按年分表(DateTime类型) |
yyyy |
>,>=,<,<=,=,contains |
AbstractSimpleShardingYearKeyLongVirtualTableRoute |
按年分表(Long时间戳) |
yyyy |
>,>=,<,<=,=,contains |
2. 自定义分表路由
【后续补充,或后面单独章节】
3. 自定义分库路由
分库仅提供默认路由,所以需要用户自行实现AbstractShardingOperatorVirtualDataSourceRoute抽象
在重写的ShardingKeyToDataSourceName方法中进行分库逻辑。
原理: 1. 该方法的参数为分片键的值,返回值为 数据源的名称。
2. program中会事先把多个DB注入,每个DB都会对应一个名称。
3. 方法内部,自定义算法规则,对分片键进行运算,最终会返回数据源的名称,代表这个数据,进入这个数据源名称对应的实际数据库中。
/// <summary>
/// 自定义分库的逻辑
/// </summary>
/// <param name="shardingKey"></param>
/// <returns>返回的是上述数据源名称中的某一个</returns>
public override string ShardingKeyToDataSourceName(object shardingKey)
{
//1.对分库键求hashcode
var hashCode = ShardingCoreHelper.GetStringHashCode(shardingKey?.ToString() ?? string.Empty);
//2,因为分了3个库,所以对3取模,值可能为 0,1,2
var result1 = hashCode % 3;
//3 求绝对值
var result2 = Math.Abs(result1);
//4 因为上述的数据源名称00、01、02,所以借助PadLeft函数,在左侧加1个0,就组成00、01、02了
var finalResult = result2.ToString().PadLeft(2, '0');
return finalResult;
//这里靠的是返回数据源的名称,返回00,则对应数据源名称为00,这个shardingkey则进入00数据源对应的DB
//00 指的是program中定义的名称,也就是这里的_dataSources
//至于 名称00 对应的DB的真实名称是什么,不是很关键,可以叫 ShardingDB_00 ,也可以叫 ShardingDB_AAAA
//等价于上面的步骤
//return Math.Abs(ShardingCoreHelper.GetStringHashCode(shardingKey?.ToString() ?? string.Empty) % 3).ToString().PadLeft(2, '0');
}
4. 其他高级路由
【后续补充,或后面单独章节】
三. 分表实操
(此处重点分享:int类型取模、string类型hash取模、DateTime时间按年分表,以int类型取模详细介绍)
依赖程序集:
1 SysUserMod实体
(1). 实体内容
public class SysUserModInt
{
/// <summary>
/// 用户Id用于分表
/// </summary>
public int Id { get; set; }
/// <summary>
/// 用户名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 用户姓名
/// </summary>
public int Age { get; set; }
public int AgeGroup { get; set; }
}
(2). fluentApi配置
public class SysUserModIntMap:IEntityTypeConfiguration<SysUserModInt>
{
public void Configure(EntityTypeBuilder<SysUserModInt> builder)
{
builder.HasKey(o => o.Id);
//一个用于属性配置的选项。它表示指定属性的值不会由数据库或 EF Core 自动生成,而是完全由应用程序来设置。
//这是一种重要的配置方式,用于明确告诉 EF Core 不要对该属性进行任何自动的值生成操作。
builder.Property(o => o.Id).ValueGeneratedNever();
builder.Property(o => o.Name).HasMaxLength(128);
builder.ToTable(nameof(SysUserModInt));
}
}
2 分表路由
/// <summary>
/// 分表路由
/// AbstractSimpleShardingModKeyIntVirtualTableRoute:表示根据int型取模 (不支持long类型雪花算法)
/// 根据字符串ID进行hash取模
/// </summary>
public class SysUserModIntVirtualTableRoute : AbstractSimpleShardingModKeyIntVirtualTableRoute<SysUserModInt>
{
/// <summary>
/// base中第1个参数:表示分表名称后面的长度
/// 2表示分表后的格式:xxx_01
/// 3表示分表后的格式:xxx_001
/// 4表示分表后的格式:xxx_0001
/// base中的第2个参数:表示分表取模的除数 和 自动建表的个数
/// 如下3,表示对3取模, 自动建表的时候会建三张表 xxx_00 xxx_01 xxx_02
/// 注:就算不使用框架的自动建表,手动建表也需要按照这个格式
///
/// </summary>
public SysUserModIntVirtualTableRoute() : base(2, 3) { }
public override void Configure(EntityMetadataTableBuilder<SysUserModInt> builder)
{
//表示根据Id进行取模分表
builder.ShardingProperty(o => o.Id);
builder.TableSeparator("_"); //表中间的分隔符,默认就是这个
//表示是否需要在启动的时候建表: null表示根据全局配置, true:表示需要, false:表示不需要,
//默认null, 走的是program的
//经测试:这个配置无效
//builder.AutoCreateTable(true);
}
}
3 EFCore上下文
/// <summary>
/// EFCore上下文
/// 包含ShardingCoreDB的上下文 和 原生DBContext上下文的所有功能
/// </summary>
public class MyDbContext : AbstractShardingDbContext, IShardingTableDbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//将fluentApi形式的映射
modelBuilder.ApplyConfiguration(new SysUserModIntMap());
}
public IRouteTail RouteTail { get; set; }
}
4. program中的配置
(1) AddShardingDbContext注入
AddShardingDbContext配置包含了原生的AddDbContext+AddShardingConfigure,无需单独注入原生DBContext
/*
ShardingCore相关配置
1. AddShardingDbContext配置包含了原生的AddDbContext+AddShardingConfigure,无需单独注入原生DBContext
2. MyDbContext继承了原生DBContext,随意具有原生的所有功能
*/
builder.Services.AddShardingDbContext<MyDbContext>()
.UseRouteConfig(op =>
{
//分表路由规则
op.AddShardingTableRoute<SysUserModIntVirtualTableRoute>();
})
.UseConfig(op =>
{
#region 一些配置 【暂时注释,需要什么开启什么】
//{
// //当查询无法匹配到对应的路由是否抛出异常 true表示抛出异常 false表示返回默认值
// op.ThrowIfQueryRouteNotMatch = false;
// //如果开启读写分离是否在savechange commit rollback后自动将dbcontext切换为写库 true表示是 false表示不是
// op.AutoUseWriteConnectionStringAfterWriteDb = false;
// //创建表如果出现错误是否忽略掉错误不进行日志出输出 true表示忽略 false表示不忽略
// op.IgnoreCreateTableError = false;
// //默认的系统默认迁移并发数,分库下会进行并发迁移
// op.MigrationParallelCount = Environment.ProcessorCount;
// //补偿表并发线程数 补偿表用来进行最后一次补偿缺少的分片表
// op.CompensateTableParallelCount = Environment.ProcessorCount;
// //默认最大连接数限制 如果出现跨分片查询需要开启n个链接,设置这个值会将x个链接分成每n个为一组进行同库串行执行
// op.MaxQueryConnectionsLimit = Environment.ProcessorCount;
// //链接模式的选择系统自动
// op.ConnectionMode = ConnectionModeEnum.SYSTEM_AUTO;
//}
#endregion
//如何通过字符串查询创建DbContext
op.UseShardingQuery((conStr, builder) =>
{
builder.UseSqlServer(conStr, o => o.UseCompatibilityLevel(120)).UseLoggerFactory(efLogger);
});
//如何通过事务创建DbContext
op.UseShardingTransaction((connection, builder) =>
{
builder.UseSqlServer(connection, o => o.UseCompatibilityLevel(120)).UseLoggerFactory(efLogger);
});
//添加默认数据源
op.AddDefaultDataSource("A", SQLServerStr);
})
.AddShardingCore();
(2) 自动生成对应规则的表
//启动进行表补偿(自行判断缺少的分片对象(包括分表或者分库)并且会自动创建对应的数据)
serviceProvider.UseAutoTryCompensateTable();
5. 自动运行代码,生成相应的表
6. 测试代码
using Ypf.Test; namespace ShardingCoreDemo1.Controllers; /// <summary> /// SysUserMod表根据string主键Id进行分表 /// /// bug: 雪花算法做Id,取模值 0,1,2 不一定正好对应00、01、02表,可能模值0,对应01表,这就很尴尬了 /// </summary> [Route("api/[controller]/[action]")] [ApiController] public class Test1Controller : Controller { private readonly MyDbContext shardingDb; public Test1Controller(MyDbContext _shardingDb) { shardingDb = _shardingDb; } /// <summary> /// 插入数据---有bug /// </summary> /// <returns></returns> [HttpPost] public async Task<IActionResult> InsertData() { #region 使用int类型转换成string效果更直观 if (!shardingDb.Set<SysUserMod>().Any()) { var ids = Enumerable.Range(1, 100); var userMods = new List<SysUserMod>(); foreach (var id in ids) { userMods.Add(new SysUserMod() { Id = id.ToString(), Age = id, Name = $"name_{id}", AgeGroup = Math.Abs(id % 10) }); } shardingDb.AddRange(userMods); await shardingDb.SaveChangesAsync(); } #endregion #region 使用雪花id--有bug //{ // //取模值 0,1,2 不一定正好对应00、01、02表,可能模值0,对应01表,这就很尴尬了 // SnowflakeIdGenerator generator = new(1); // var list = new List<SysUserMod>(); // for (var i = 0; i < 100; i++) // { // list.Add(new SysUserMod() // { // Id = generator.GenerateId().ToString(), //雪花算法 // Age = i, // Name = $"name_{i}", // AgeGroup = Math.Abs(i % 10) // }); // } // shardingDb.AddRange(list); // await shardingDb.SaveChangesAsync(); //} #endregion return Json(new { status = "ok", msg = "插入成功" }); } /// <summary> /// 查询所有数据 /// </summary> /// <returns></returns> [HttpPost] public async Task<IActionResult> ToList_All() { var mods = await shardingDb.Set<SysUserMod>().ToListAsync(); var modUser1 = await shardingDb.Set<SysUserMod>().OrderBy(o => o.Age).ToListAsync(); var modUser2 = await shardingDb.Set<SysUserMod>().OrderByDescending(o => o.Age).ToListAsync(); return Json(new { status = "ok", msg = "查询成功", data = new { mods, modUser1, modUser2 } }); } /// <summary> /// 根据age查询 /// </summary> /// <param name="age">年龄</param> /// <returns></returns> [HttpPost] public async Task<IActionResult> GetInfoByAge(int age) { var info = await shardingDb.Set<SysUserMod>().Where(u => u.Age == age).ToListAsync(); return Json(new { status = "ok", msg = "查询成功", data = info }); } /// <summary> /// 清空所有数据 /// </summary> /// <returns></returns> [HttpPost] public async Task<IActionResult> DelAll() { int count = await shardingDb.Set<SysUserMod>().ExecuteDeleteAsync(); return Json(new { status = "ok", msg = $"删除成功,条数为{count}", data = "" }); } /// <summary> /// 根据age删除 /// </summary> /// <param name="age">年龄</param> /// <returns></returns> [HttpPost] public async Task<IActionResult> DelByAge(int age) { int count = await shardingDb.Set<SysUserMod>().Where(u => u.Age == age).ExecuteDeleteAsync(); return Json(new { status = "ok", msg = $"删除成功,条数为{count}", data = "" }); } /// <summary> /// 根据age修改 /// </summary> /// <param name="age">年龄</param> /// <returns></returns> [HttpPost] public async Task<IActionResult> UpdateByAge(int age) { var list= shardingDb.Set<SysUserMod>().Where(u => u.Age == age).ToList(); foreach (var item in list) { item.Name = "ypfypfypf"; } int count =await shardingDb.SaveChangesAsync(); return Json(new { status = "ok", msg = $"修改成功,条数为{count}", data = "" }); } }
7. hash取模、按年分表的路由分享
/// <summary>
/// 分表路由
/// AbstractSimpleShardingModKeyStringVirtualTableRoute:表示根据string类型hash取模
/// 根据字符串ID进行hash取模
/// </summary>
public class SysUserModStringVirtualTableRoute : AbstractSimpleShardingModKeyStringVirtualTableRoute<SysUserMod>
{
/// <summary>
/// base中第1个参数:表示分表名称后面的长度
/// 2表示分表后的格式:xxx_01
/// 3表示分表后的格式:xxx_001
/// 4表示分表后的格式:xxx_0001
/// base中的第2个参数:表示分表取模的除数 和 自动建表的个数
/// 如下3,表示对3取模, 自动建表的时候会建三张表 xxx_00 xxx_01 xxx_02
/// 注:就算不使用框架的自动建表,手动建表也需要按照这个格式
///
/// </summary>
public SysUserModStringVirtualTableRoute() : base(2, 3){ }
public override void Configure(EntityMetadataTableBuilder<SysUserMod> builder)
{
//表示根据Id进行取模分表
builder.ShardingProperty(o => o.Id);
builder.TableSeparator("_"); //表中间的分隔符,默认就是这个
}
}
/// <summary>
/// 按时间分表,以年为单位
/// AbstractSimpleShardingYearKeyDateTimeVirtualTableRoute表示按年分表
/// </summary>
public class OrderCreateTimeVirtualTableRoute : AbstractSimpleShardingYearKeyDateTimeVirtualTableRoute<Order>
{
/// <summary>
/// 自动建表
/// (目前没发现作用,即使为false也会自动建表)
/// (无效!!)
/// </summary>
/// <returns></returns>
public override bool AutoCreateTableByTime()
{
return true;
}
/// <summary>
/// 配置分表键
/// </summary>
/// <param name="builder"></param>
public override void Configure(EntityMetadataTableBuilder<Order> builder)
{
builder.ShardingProperty(u => u.CreateTime);
}
/// <summary>
/// 配置开始时间
/// </summary>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public override DateTime GetBeginTime()
{
return new DateTime(2022, 1, 1);
}
}
对应的表:
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。