zoukankan      html  css  js  c++  java
  • 重磅来袭,使用CRL实现大数据分库分表方案

    关于分库分表方案详细介绍

    http://blog.csdn.net/bluishglc/article/details/7696085

    这里就不作详细描述了

    分库分表方案基本脱离不了这个结构,受制于实现的难度,好像没有看到有很方便的实现方案框架

    为了解决此问题,在CRL框架基础上作了扩展,使CRL能很好实现此方案,以之前了解到的需求,基本能满足了

    本方案拆分结构表示为

    会员为业务核心,所有业务围绕会员来进行,所以垂直划分用会员编号作索引,将会员分配到不同的库

    会员订单增长量是不固定的,所以需要平水拆分,和分库一样,一个表只存指定会员编号区间的订单

    了解基本需求,就可以制作方案了,以下主索引表示主数据编号

    库表结构配置

    进行操作时,需要知道这个数据放在哪个库,哪个表,因此需要把这个划分结构做成可配置,需要配置有:

    • 数据库:一共划分为几个库,主索引区间是多少
    • 数据表:一共有几个分表,每个分表容量是多少
    • 数据表分表:属于哪个表,主索引区间是多少

    将结构以对象形式表示

    DataBase 库

    /// <summary>
        ////// 按主数据分垂直划分,将主数据按不同段存在不同库中
        /// </summary>
        public class DataBase:CRL.IModelBase
        {
            /// <summary>
            /// 库名
            /// </summary>
            public string Name
            {
                get;
                set;
            }
            /// <summary>
            /// 主数据开始INDEX
            /// </summary>
            public int MainDataStartIndex
            {
                get;
                set;
            }
            /// <summary>
            /// 主数据结束INDEX
            /// </summary>
            public int MainDataEndIndex
            {
                get;
                set;
            }
            /// <summary>
            /// 主数据表最大数据量
            /// </summary>
            public int MaxMainDataTotal
            {
                get;
                set;
            }
            public override string ToString()
            {
                return string.Format("名称:{0} 最大主数据量:{1} 索引开始:{2} 结束{3}", Name, MaxMainDataTotal, MainDataStartIndex, MainDataEndIndex);
            }
        }
    View Code

    Table 表

     /// <summary>
        ////// 主数据表不分表,只按库分,其它表再按主数据段分表
        /// </summary>
        public class Table:CRL.IModelBase
        {
            /// <summary>
            /// 源表名
            /// </summary>
            [CRL.Attribute.Field(FieldIndexType = Attribute.FieldIndexType.非聚集)]
            public string TableName
            {
                get;
                set;
            }
            /// <summary>
            /// 库名
            /// </summary>
            [CRL.Attribute.Field(FieldIndexType = Attribute.FieldIndexType.非聚集)]
            public string DataBaseName
            {
                get;
                set;
            }
            /// <summary>
            /// 分表数
            /// </summary>
            public int TablePartTotal
            {
                get;
                set;
            }
            /// <summary>
            /// 分表最大数据量
            /// </summary>
            public int MaxPartDataTotal
            {
                get;
                set;
            }
            /// <summary>
            /// 是否为主数据表
            /// 主数据表在当前库只存在一个
            /// </summary>
            public bool IsMainTable
            {
                get;
                set;
            }
    
        }
    View Code

    TablePart 分表

    /// <summary>
        /// 分表,主数据表不分表,只按库分
        /// 其它表按主数据段分
        /// </summary>
        public class TablePart : CRL.IModelBase
        { 
            /// <summary>
            /// 库名
            /// </summary>
            [CRL.Attribute.Field(FieldIndexType = Attribute.FieldIndexType.非聚集)]
            public string DataBaseName
            {
                get;
                set;
            }
            /// <summary>
            /// 源表名
            /// </summary>
            [CRL.Attribute.Field(FieldIndexType = Attribute.FieldIndexType.非聚集)]
            public string TableName
            {
                get;
                set;
            }
            /// <summary>
            /// 分表名
            /// </summary>
            public string PartName
            {
                get;
                set;
            }
            /// <summary>
            /// 分表索引,从0开始
            /// </summary>
            public int PartIndex
            {
                get;
                set;
            }
            /// <summary>
            /// 主数据开始索引值
            /// </summary>
            public int MainDataStartIndex
            {
                get;
                set;
            }
            /// <summary>
            /// 主数据结束索引值
            /// </summary>
            public int MainDataEndIndex
            {
                get;
                set;
            }
            ///// <summary>
            ///// 分表最大数据量
            ///// </summary>
            //public int MaxPartDataTotal
            //{
            //    get;
            //    set;
            //}
        }
    View Code

    数据定位

    通过以上配置,就可以按主索引进行表定位了

    过程表示为:初始库表配置=>按主索引定位库=>再定位到表

    /// <summary>
            /// 初始表
            /// </summary>
            public static void Init()
            {
                _DataBase = DataBaseManage.Instance.QueryList();
                _Table = TableManage.Instance.QueryList();
                _TablePart = TablePartManage.Instance.QueryList();
            }
            /// <summary>
            /// 按主数据索引,确定库
            /// </summary>
            /// <param name="mainDataIndex"></param>
            /// <returns></returns>
            public static DataBase GetDataBase(int mainDataIndex)
            {
                if (_DataBase.Count() == 0)
                {
                    Init();
                }
                var db = _DataBase.Find(b => mainDataIndex >= b.MainDataStartIndex && mainDataIndex <= b.MainDataEndIndex);
                if (db == null)//找属于哪个库
                {
                    throw new Exception("找不到指定的库,在主数据索引:" + mainDataIndex);
                }
                return db;
            }
            /// <summary>
            /// 按主数据索引,获取该查询位置
            /// </summary>
            /// <param name="tableName"></param>
            /// <param name="mainDataIndex"></param>
            /// <param name="db"></param>
            /// <returns></returns>
            public static Location GetLocation(string tableName, int mainDataIndex, DataBase db)
            {
                var table = _Table.Find(b => b.TableName == tableName && b.DataBaseName == db.Name);
                if (table == null)//找哪个表
                {
                    throw new Exception(string.Format("找不到指定的表{1}在库{0}", db.Name, tableName));
                }
                TablePart part;
                //找分表
                if (table.IsMainTable)//如果只是主数据表,只按一个找就行了
                {
                    part = _TablePart.Find(b => b.TableName == tableName && b.DataBaseName == db.Name);
                }
                else//其它表,按分表找
                {
                    part = _TablePart.Find(b => mainDataIndex >= b.MainDataStartIndex && mainDataIndex <= b.MainDataEndIndex && b.TableName == tableName && b.DataBaseName == db.Name);
                }
                if (part == null)
                {
                    throw new Exception(string.Format("找不到指定的表{1}在库{0}", db.Name, tableName));
                }
                return new Location() { DataBase = db, TablePart = part };
            }

    传入主索引编号,调用定位方法,得到对应的库和表名称

    var db=GetDataBase(100);//定位库
    var location=GetLocation("Order", 100, db);//定位表

    这样,库表位置就有了,剩下的就是对数据进行操作了,不过这需要数据访问框架能动态切换数据连接和映射对象,还好CRL能轻松实现

    CRL内部调用比较复杂,这里不写具体实现了,只列出关键方法,详细实现可加入QQ群获取最新源代码

    数据连接动态切换

    因为CRL的数据访问是在应用层委托实现的,很容易实现,本次升级后增加了分库的区分

    //配置数据连接
                CRL.SettingConfig.GetDbAccess = (type) =>
                {
                    //可按type区分数据库
                    if (type.ShardingDataBase != null)
                    {
                        if (type.ShardingDataBase.Name == "db1")//定位到DB1
                        {
                            return WebTest.Code.LocalSqlHelper.TestConnection;
                        }
                        else
                        {
                            return WebTest.Code.LocalSqlHelper.TestConnection2;//定位到DB2
                        }
                    }
                    else
                    {
                        return WebTest.Code.LocalSqlHelper.TestConnection;//默认库
                    }
                };

    分表映射

    在CRL内部实现了分表映射,如果传入了定位,则按定位找到分表名

    public static string GetTableName(string tableName, DbContext dbContext)
            {
                if (dbContext != null && dbContext.UseSharding)
                {
                    if (dbContext.ShardingMainDataIndex == 0)
                    {
                        throw new Exception("未设置分表定位索引,dbContext.ShardingMainDataIndex");
                    }
                    var location = Sharding.DBService.GetLocation(tableName, dbContext.ShardingMainDataIndex, dbContext.DBLocation.ShardingDataBase);
                    tableName = location.TablePart.PartName;
                }
                return tableName;
            }

    以上过程表示为

    整体封装

    和之前CRL业务类封装一样,增加了CRL.Sharding.BaseProvider,继承实现业务对象,就能实现分库分表了

    对比之前,此类删除了一些无关方法,增加了SetLocation定位方法

    以文档带的例子讲解

    会员实现

    这里要注意主键的问题,不能为自增

    public class MemberSharding : CRL.IModel
        {
            [CRL.Attribute.Field(KeepIdentity=true)]//保持插入主键
            public int Id
            {
                get;
                set;
            }
            public string Name
            {
                get;
                set;
            }
        }
        public class MemberManage : CRL.Sharding.BaseProvider<MemberSharding>
        {
            public static MemberManage Instance
            {
                get { return new MemberManage(); }
            }
        }

    订单实现

    public class OrderSharding : CRL.IModelBase
        {
            public int MemberId
            {
                get;
                set;
            }
            public string Remark
            {
                get;
                set;
            }
        }
        public class OrderManage : CRL.Sharding.BaseProvider<OrderSharding>
        {
            public static OrderManage Instance
            {
                get { return new OrderManage(); }
            }
        }

    初始库表配置

    以下会创建两个库 db1,db2
    db1会员编号为1~10 ,db2会员编号为 11~20 ,当插入会员编号小于11的数据,则会定位到db1,11到20则会定位到db2
    订单表OrderSharding设定为最大主数据容量5,1~5编号的会员订单会放在OrderSharding,6~10则会放到OrderSharding_1

    CRL.Sharding.DB.DataBaseManage.Instance.CleanData();
                //创建库分组
                var db = new CRL.Sharding.DB.DataBase();
                db.Name = "db1";
                db.MaxMainDataTotal = 10;
                CRL.Sharding.DB.DataBaseManage.Instance.Create(db);
                CRL.Sharding.DB.DataBaseManage.Instance.Create(db);
                //创建表
                var dbList = CRL.Sharding.DB.DataBaseManage.Instance.QueryList();
                foreach(var item in dbList)
                {
                    var table = new CRL.Sharding.DB.Table();
                    table.TableName = "MemberSharding";
                    table.IsMainTable = true;
                    CRL.Sharding.DB.TableManage.Instance.Create(item, table, out error);
    
                    var table2 = new CRL.Sharding.DB.Table();
                    table2.TableName = "OrderSharding";
                    table2.IsMainTable = false;
                    table2.MaxPartDataTotal = 5;
                    CRL.Sharding.DB.TableManage.Instance.Create(item, table2, out error);
    
                    //创建分区
                    CRL.Sharding.DB.TablePartManage.Instance.Create(table2, out error);
                }

    插入会员和订单测试

    插入的会员和订单会按库表配置分配到不同的表

    这里为了演示,将定位信息保存了

    var m = new Code.Sharding.MemberSharding();
                m.Id = Convert.ToInt32(TextBox1.Text);//主索引编号
                var location = CRL.Sharding.DBService.GetLocation("MemberSharding", m.Id);
                m.Name = location.ToString();
                Code.Sharding.MemberManage.Instance.SetLocation(m.Id).Add(m);
    
                var order = new Code.Sharding.OrderSharding();
                order.MemberId = m.Id;
                var location2 = CRL.Sharding.DBService.GetLocation("OrderSharding", m.Id);
                order.Remark = location2.ToString();
                Code.Sharding.OrderManage.Instance.SetLocation(m.Id).Add(order);
                Label1.Text = "插入会员编号" + m.Id + "," + location + " 订单" + location2;

    数据查询

    通过主索引编号定位库表,就能跟正常查询一样操作了

     //会员查询
    var id  = Convert.ToInt32(TextBox1.Text);//主索引编号
                var list = Code.Sharding.OrderManage.Instance.SetLocation(id).QueryList(b => b.MemberId == id);
                GridView1.DataSource = list;
                GridView1.DataBind();
    //订单查询
     var id = Convert.ToInt32(TextBox1.Text);//主索引编号
                var list = Code.Sharding.MemberManage.Instance.SetLocation(id).QueryList(b => b.Id == id);
                GridView1.DataSource = list;
                GridView1.DataBind();

    结果演示如下

    分表联合查询

    查询当前库所有分表订单union结果

    设定union方式后,会遍历分表生成union查询

    var id = Convert.ToInt32(TextBox1.Text);
                var orderManage = Code.Sharding.OrderManage.Instance.SetLocation(id);
                var query = orderManage.GetLambdaQuery();
                query.UnionType = UnionType.UnionAll;//只需设定union方式即可
                var list = orderManage.QueryList(query);
                GridView1.DataSource = list;
                GridView1.DataBind();

    主索引的问题

    以上演示主索引编号为手动输入,实际业务是需要从索引表产生,CRL.Sharding也增加了索引获取

    CRL.Sharding.DB.DataSequenceManage.Instance.GetSequence();

    分表结构创建

    分表创建貌似比较麻烦,需要手动创建维护,然而不需要,CRL自动帮你创建了,这就是CRL强大之所在,在找不到表结构时,会按对象结构创建

    分库分表难点

    库表配置比较容易实现,比较麻烦的地方是从统一入口进行数据操作管理,要达到通用性,框架需要适应不同业务需求,封装和继承需要得到很好的支持

    本方案已封装在CRL2.4,在文档示例中也有体现

    演示地址:http://crl.changqidongli.com/page/Sharding.aspx

    CRL最新版2.4代码在QQ群可以得到,有其它想法欢迎讨论,CRL3.0征集思路中,想共同开发的可与我联系

    QQ群:1582632  密语:CRL

    CRL框架介绍和早期源码:http://www.cnblogs.com/hubro/p/4616570.html

  • 相关阅读:
    devstack安装openstack
    Stacks of Flapjacks
    二、Reids基础命令--字符串
    数据库筛选用户,然后去掉一部分(列表求差),再随机返回一个用户。sqlalchemy + python集合(set) + random
    利用Powershell获取公司内部机器的资源信息,作为企业兴许资产管理的基本途径!
    《编程导论(Java)&#183;3.2.4 循环语句》
    Android自己主动检測版本号及自己主动升级
    基于bootstrap的富文本框——wangEditor【欢迎增加开发】
    找球号(三)
    #308 (div.2) B. Vanya and Books
  • 原文地址:https://www.cnblogs.com/hubro/p/4821399.html
Copyright © 2011-2022 走看看