MyCat分库分表 - xinwu-yang/cube-java GitHub Wiki
-
能不分就不分,1000 万以内的表,不建议分片,通过合适的索引,读写分离等方式,可以很好的解决性能问题。
-
分片数量尽量少,分片尽量均匀分布在多个 DataHost 上,因为一个查询 SQL 跨分片越多,则总体性能越差,虽然要好于所有数据在一个分片的结果,只在必要的时候进行扩容,增加分片数量。
-
分片规则需要慎重选择,分片规则的选择,需要考虑数据的增长模式,数据的访问模式,分片关联性问题,以及分片扩容问题,最近的分片策略为范围分片,枚举分片,一致性 Hash 分片,这几种分片都有利于扩容。
-
尽量不要在一个事务中的 SQL 跨越多个分片,分布式事务一直是个不好处理的问题。
-
查询条件尽量优化,尽量避免 Select * 的方式,大量数据结果集下,会消耗大量带宽和 CPU 资源,查询尽量避免返回大量结果集,并且尽量为频繁使用的查询语句建立索引。
这里特别强调一下分片规则的选择问题,如果某个表的数据有明显的时间特征,比如订单、交易记录等,则他们通常比较合适用时间范围分片,因为具有时效性的数据,我们往往关注其近期的数据,查询条件中往往带有时间字段进行过滤,比较好的方案是,当前活跃的数据,采用跨度比较短的时间段进行分片,而历史性的数据,则采用比较长的跨度存储。
总体上来说,分片的选择是取决于最频繁的查询 SQL 的条件,因为不带任何 Where 语句的查询 SQL,会遍历所有的分片,性能相对最差,因此这种 SQL 越多,对系统的影响越大,所以我们要尽量避免这种 SQL 的产生。
一个数据库由很多表的构成,每个表对应着不同的业务,垂直切分是指按照业务将表进行分类,分布到不同的数据库上面,这样也就将数据或者说压力分担到不同的库上面,如下图:
系统被切分成了,用户,订单交易,支付几个模块。
- 拆分后业务清晰,拆分规则明确
- 系统之间整合或扩展容易
- 数据维护简单
- 部分业务表无法 join,只能通过接口方式解决,提高了系统复杂度
- 受每种业务不同的限制存在单库性能瓶颈,不易数据扩展跟性能提高
- 事务处理复杂
相对于垂直拆分,水平拆分不是将表做分类,而是按照某个字段的某种规则来分散到多个库之中,每个表中包含一部分数据。简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中的某些行切分到一个数据库,而另外的某些行又切分到其他的数据库中,如下图:
拆分数据就需要定义分片规则。关系型数据库是行列的二维模型,拆分的第一原则是找到拆分维度。比如:
-
从会员的角度来分析,商户订单交易类系统中查询会员某天某月某个订单,那么就需要按照会员结合日期来拆分,不同的数据按照会员 ID 做分组,这样所有的数据查询 join 都会在单库内解决
-
如果从商户的角度来讲,要查询某个商家某天所有的订单数,就需要按照商户 ID 做拆分
-
但是如果系统既想按会员拆分,又想按商家数据,则会有一定的困难。如何找到合适的分片规则需要综合考虑衡量
- 按照用户 ID 求模,将数据分散到不同的数据库,具有相同数据用户的数据都被分散到一个库中
- 按照日期,将不同月甚至日的数据分散到不同的库中
- 按照某个特定的字段求摸,或者根据特定范围段分散到不同的库中
- 拆分规则抽象好,join 操作基本可以数据库做
- 不存在单库大数据,高并发的性能瓶颈
- 应用端改造较少
- 提高了系统的稳定性跟负载能力
- 拆分规则难以抽象
- 分片事务一致性难以解决
- 数据多次扩展难度跟维护量极大
- 跨库 join 性能较差
一个或多个数据库集群构成的。
是数据切分后,分布在一个或多个分片库中,也可以不做数据切分,不分片,只有一个表构成。
本篇文档实战版本:1.6.7.5
├── bin
│ ├── mycat #启动脚本
├── conf
│ ├── rule.xml #定义分片规则
│ ├── schema.xml #定义逻辑库,表、分片节点等内容
│ ├── server.xml #定义用户以及系统相关变量,如端口等
│ ├── sequence_conf.properties #本地文件方式主键自增配置
│ ├── sequence_db_conf.properties #数据库方式主键自增配置
│ ├── sequence_distributed_conf.properties #基于zookeeper的分布式主键Id
│ ├── sequence_http_conf.properties #暂无描述
│ ├── sequence_time_conf.properties #本地时间戳方式主键自增配置
├── logs
│ ├── mycat.log #每次启动的日志,历史日志按年月存储到对应文件夹
- server.xml 定义用户以及系统相关变量,如端口等
<!-- 定义用户 -->
<user name="root" defaultAccount="true">
<property name="password">123456</property>
<property name="schemas">TESTDB</property>
<property name="defaultSchema">TESTDB</property>
</user>
- schema.xml
<!-- 定义逻辑库 -->
<schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100" randomDataNode="dn1">
<!-- 定义逻辑表 -->
<table name="test" primaryKey="id" dataNode="dn1,dn2" rule="sharding-by-month" autoIncrement="true" fetchStoreNodeByJdbc="true"></table>
</schema>
<!-- 定义数据节点 -->
<dataNode name="dn1" dataHost="25.30.9.15" database="test1" />
<dataNode name="dn2" dataHost="25.30.9.15" database="test2" />
<!-- 数据节点具体参数 -->
<dataHost name="25.30.9.15" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<!-- 心跳检测SQL -->
<heartbeat>select user()</heartbeat>
<!-- 这里可以配置多个写入节点 -->
<writeHost host="hostM1" url="jdbc:mysql://25.30.9.15:3306" user="root" password="chengxun"></writeHost>
<!-- <writeHost host="hostM2" url="localhost:3316" user="root" password="123456"/> -->
</dataHost>
- rule.xml
<!-- 定义分片规则 -->
<tableRule name="sharding-by-month">
<rule>
<!-- 按照那一列分 -->
<columns>create_time</columns>
<!-- 具体分片规则 -->
<algorithm>partbymonth</algorithm>
</rule>
</tableRule>
<!-- 具体分片规则 -->
<function name="partbymonth" class="io.mycat.route.function.PartitionByMonth">
<property name="dateFormat">yyyy-MM-dd</property>
<!-- 开始分片时间 -->
<property name="sBeginDate">2020-12-01</property>
</function>
数据格式:自定义位数的 int 类型
- 配置server.xml
<property name="sequnceHandlerType">0</property>
- 配置 sequence_conf.properties
GLOBAL_SEQ.HISIDS=
GLOBAL_SEQ.MINID=1001
GLOBAL_SEQ.MAXID=1000000000
GLOBAL_SEQ.CURID=1000
其中 HISIDS 表示使用过的历史分段(一般无特殊需要可不配置),MINID 表示最小 ID 值,MAXID 表示最大 ID 值,CURID 表示当前 ID 值。
数据格式:18 位数的 long 类型
- 配置server.xml
<property name="sequnceHandlerType">2</property>
- 在mycat下配置:sequence_time_conf.properties
WORKID=0-31 任意整数
DATAACENTERID=0-31 任意整数
在数据库中建立一张表,存放 sequence 名称(name),sequence 当前值(current_value),步长(increment int 类型每次读取多少个 sequence,假设为 K)等信息。
<property name="sequnceHandlerType">1</property>
<property name="sequnceHandlerType">3</property>
Zk 的连接信息统一在 myid.properties 的 zkURL 属性中配置。 配置文件:sequence_distributed_conf.properties,只要配置里面INSTANCEID=ZK 就是从 ZK 上获取InstanceID。
<property name="sequnceHandlerType">4</property>
Zk 的连接信息统一在 myid.properties 的 zkURL 属性中配置。 配置文件:sequence_conf.properties
- 枚举值
本规则适用于特定的场景,比如有些业务需要按照省份或区县来做保存,而全国省份区县固定的,这类业务使用本条规则。
- 固定分片 hash 算法
希望将数据水平分成 3 份,前两份各占 25%,第三份占 50%。
- 范围约定
适用于提前规划好分片字段某个范围属于哪个分片。
- 取模
每个节点均摊数据
- 按日期(天)分片
此规则为按天分片。
- 取模范围约束
可以自主决定取模后数据的节点分布。
- 截取数字做 hash 求模范围约束
可以自主决定取模后数据的节点分布,支持字母符号等
- 应用指定
运行阶段由应用自主决定路由到那个分片,对数据格式有要求。
- 截取数字 hash 解析
此规则是截取字符串中的 int 数值 hash 分片。
- 一致性 hash
一致性 hash 预算有效解决了分布式数据的扩容问题。
- 按单月小时拆分
此规则是单月内按照小时拆分,最小粒度是小时,可以一天最多 24 个分片,最少 1 个分片,一个月完后下月从头开始循环。
- 范围求模分片
先进行范围分片计算出分片组,组内再求模。优点可以避免扩容时的数据迁移,又可以一定程度上避免范围分片的热点问题。
- 日期范围 hash 分片
思想与范围求模一致,当由于日期在取模会有数据集中问题,所以改成 hash 方法。先根据日期分组,再根据时间 hash 使得短期内数据分布的更均匀.优点可以避免扩容时的数据迁移,又可以一定程度上避免范围分片的热点问题。要求日期格式尽量精确些,不然达不到局部均匀的目的。
- 冷热数据分片
根据日期查询日志数据 冷热数据分布 ,最近 n 个月的到实时交易库查询,超过 n 个月的按照 m 天分片。
- 自然月分片
按月份列分区 ,每个自然月一个分片,每个分片需要对应一个数据库,比较麻烦的是需要提前规划数据库建设。
- 有状态分片算法
为数据自动迁移而设计的。
- crc32slot 分片算法
crc32solt 是有状态分片算法的实现之一。