MyCat分库分表 - xinwu-yang/cube-java GitHub Wiki

分片原则

  1. 能不分就不分,1000 万以内的表,不建议分片,通过合适的索引,读写分离等方式,可以很好的解决性能问题。

  2. 分片数量尽量少,分片尽量均匀分布在多个 DataHost 上,因为一个查询 SQL 跨分片越多,则总体性能越差,虽然要好于所有数据在一个分片的结果,只在必要的时候进行扩容,增加分片数量。

  3. 分片规则需要慎重选择,分片规则的选择,需要考虑数据的增长模式,数据的访问模式,分片关联性问题,以及分片扩容问题,最近的分片策略为范围分片,枚举分片,一致性 Hash 分片,这几种分片都有利于扩容。

  4. 尽量不要在一个事务中的 SQL 跨越多个分片,分布式事务一直是个不好处理的问题。

  5. 查询条件尽量优化,尽量避免 Select * 的方式,大量数据结果集下,会消耗大量带宽和 CPU 资源,查询尽量避免返回大量结果集,并且尽量为频繁使用的查询语句建立索引。

这里特别强调一下分片规则的选择问题,如果某个表的数据有明显的时间特征,比如订单、交易记录等,则他们通常比较合适用时间范围分片,因为具有时效性的数据,我们往往关注其近期的数据,查询条件中往往带有时间字段进行过滤,比较好的方案是,当前活跃的数据,采用跨度比较短的时间段进行分片,而历史性的数据,则采用比较长的跨度存储。

总体上来说,分片的选择是取决于最频繁的查询 SQL 的条件,因为不带任何 Where 语句的查询 SQL,会遍历所有的分片,性能相对最差,因此这种 SQL 越多,对系统的影响越大,所以我们要尽量避免这种 SQL 的产生。

垂直分片

一个数据库由很多表的构成,每个表对应着不同的业务,垂直切分是指按照业务将表进行分类,分布到不同的数据库上面,这样也就将数据或者说压力分担到不同的库上面,如下图:

系统被切分成了,用户,订单交易,支付几个模块。

优点

  • 拆分后业务清晰,拆分规则明确
  • 系统之间整合或扩展容易
  • 数据维护简单

缺点

  • 部分业务表无法 join,只能通过接口方式解决,提高了系统复杂度
  • 受每种业务不同的限制存在单库性能瓶颈,不易数据扩展跟性能提高
  • 事务处理复杂

水平分片

相对于垂直拆分,水平拆分不是将表做分类,而是按照某个字段的某种规则来分散到多个库之中,每个表中包含一部分数据。简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中的某些行切分到一个数据库,而另外的某些行又切分到其他的数据库中,如下图:

拆分数据就需要定义分片规则。关系型数据库是行列的二维模型,拆分的第一原则是找到拆分维度。比如:

  • 从会员的角度来分析,商户订单交易类系统中查询会员某天某月某个订单,那么就需要按照会员结合日期来拆分,不同的数据按照会员 ID 做分组,这样所有的数据查询 join 都会在单库内解决

  • 如果从商户的角度来讲,要查询某个商家某天所有的订单数,就需要按照商户 ID 做拆分

  • 但是如果系统既想按会员拆分,又想按商家数据,则会有一定的困难。如何找到合适的分片规则需要综合考虑衡量

几种典型的分片规则

  • 按照用户 ID 求模,将数据分散到不同的数据库,具有相同数据用户的数据都被分散到一个库中
  • 按照日期,将不同月甚至日的数据分散到不同的库中
  • 按照某个特定的字段求摸,或者根据特定范围段分散到不同的库中

优点

  • 拆分规则抽象好,join 操作基本可以数据库做
  • 不存在单库大数据,高并发的性能瓶颈
  • 应用端改造较少
  • 提高了系统的稳定性跟负载能力

缺点

  • 拆分规则难以抽象
  • 分片事务一致性难以解决
  • 数据多次扩展难度跟维护量极大
  • 跨库 join 性能较差

基础概念

逻辑库

一个或多个数据库集群构成的。

逻辑表

是数据切分后,分布在一个或多个分片库中,也可以不做数据切分,不分片,只有一个表构成。

MyCat讲解

MyCat目录结构

本篇文档实战版本: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 #每次启动的日志,历史日志按年月存储到对应文件夹

核心配置文件

  1. server.xml 定义用户以及系统相关变量,如端口等
<!-- 定义用户 -->
<user name="root" defaultAccount="true">
    <property name="password">123456</property>
    <property name="schemas">TESTDB</property>
    <property name="defaultSchema">TESTDB</property>
</user>
  1. 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>
  1. 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 类型

  1. 配置server.xml
<property name="sequnceHandlerType">0</property>
  1. 配置 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 类型

  1. 配置server.xml
<property name="sequnceHandlerType">2</property>
  1. 在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>

通过Zookeeper实现分布式ID

<property name="sequnceHandlerType">3</property>

Zk 的连接信息统一在 myid.properties 的 zkURL 属性中配置。 配置文件:sequence_distributed_conf.properties,只要配置里面INSTANCEID=ZK 就是从 ZK 上获取InstanceID。

通过Zookeeper实现自增主键

<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 是有状态分片算法的实现之一。

⚠️ **GitHub.com Fallback** ⚠️