第三十二节: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 = "" });
    }


}
View Code

 

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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @ 2024-11-13 20:43  Yaopengfei  阅读(5)  评论(1编辑  收藏  举报