MyCat数据库中间件入门学习(二)数据分库分表

第三章 数据分片

1、数据分片概念

从虚拟库、虚拟表看到的数据,逻辑上是一个整体。而实际上数据的物理存储是分散在不同物理库、物理表中。每个实际的物理表可以看成是一个数据分片。

数据进入数据库时,经过不同拆分规则的分流进入了不同的数据分片。我们对拆分规则有两点期望:

  • 数据存储的过程中:执行 insert 语句后将数据准确插入到对应分片
  • 数据提取的过程中:能够准确的从指定分片查询到我们需要的数据

2、数据分片的具体算法

第一节 取模分片

1、理解

根据 id 值进行模运算,然后根据取模的计算结果决定数据分流后存入的目标物理表。这里涉及到的用于取模计算的数据库表字段值,不能指望由数据库自增来得到——因为这个数据是在决定分流到哪个物理表时用到的,此时还没有执行 insert 语句。所以这个数据必须由程序员指定,在 Java 代码中生成。

img

2、操作
①创建用于测试的物理表

注意: 到物理库去创建物理表

CREATE TABLE `db_hr`.`t_emp1` ( `emp_id` INT NOT NULL AUTO_INCREMENT, `emp_name` CHAR(100), PRIMARY KEY (`emp_id`) ); 
CREATE TABLE `db_hr`.`t_emp2` ( `emp_id` INT NOT NULL AUTO_INCREMENT, `emp_name` CHAR(100), PRIMARY KEY (`emp_id`) ); 
CREATE TABLE `db_hr`.`t_emp3` ( `emp_id` INT NOT NULL AUTO_INCREMENT, `emp_name` CHAR(100), PRIMARY KEY (`emp_id`) ); 
②配置 subTables 属性
  • 所在位置:schema.xml 配置文件中 schema 标签的子标签 table 的 subTables 属性。
  • 属性值设置方式:
    • 单个值:t_user
    • 多个值:
      • 将多个物理表名称用逗号隔开:t_user1,t_user2,t_user3
      • 使用正则表达式格式指定数值区间:t_user$1-5
      • 将多个用区间表示的物理表名称用逗号隔开:t_user$1-5,t_user$7-10
③配置拆分规则
  • 在 schema.xml 中配置 rule 属性

在 table 标签中通过 rule 属性指定规则名称即可。当前取模分片的名称是 mod-lang,这些规则名称是在 rule.xml 中定义的。

<!-- 取模分片 -->
<schema name="virtualDB">
	<!-- table 标签:配置虚拟表 -->
	<!-- name 属性:虚拟表的名称 -->
	<!-- subTables 属性:虚拟表对应的物理表 -->
	<!-- rule属性指定拆分规则:取模分片-->
	<table name="t_virtual_emp"
		primaryKey="emp_id"
		dataNode="hrDataNode"
		subTables="t_emp$1-3"
		rule="mod-long"
	/>
</schema>
  • 在 rule.xml 中配置 mod-long 规则
    • 配置 tableRule 标签
<tableRule name="mod-long">
    <rule>
        <!--指定用于取模分片的字段-->
        <columns>emp_id</columns>
        <!--具体规则名字-->
        <algorithm>mod-long</algorithm>
    </rule>
</tableRule>
  • 配置 function 标签
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
  <!-- 物理表的数量,需要正好就是取模的数值 -->
  <property name="count">3</property>
</function>
④重启 MyCat

MyCat 配置文件修改后重启 MyCat 程序才能够生效。

3、测试
①操作虚拟库
INSERT INTO t_virtual_emp(emp_id, emp_name) VALUES(1, 'tom01');
INSERT INTO t_virtual_emp(emp_id, emp_name) VALUES(2, 'tom02');
INSERT INTO t_virtual_emp(emp_id, emp_name) VALUES(3, 'tom03');
INSERT INTO t_virtual_emp(emp_id, emp_name) VALUES(4, 'tom04');
INSERT INTO t_virtual_emp(emp_id, emp_name) VALUES(5, 'tom05');

数据插入后,请打开物理表查看是否分流到了三个物理表。

  • t_emp1
emp_idemp_name
3tom03
  • t_emp2
emp_idemp_name
1tom01
4tom04
  • t_emp3
emp_idemp_name
2tom02
5tom05
②数据查询操作
SELECT emp_id,emp_name FROM virtual_t_emp WHERE emp_id=4;

效果:

emp_idemp_name
4tom04
4、注意

如果在 insert 语句中没有指定 emp_id 字段,会报错

第二节 全局id分片

1、理解

全局 id 分片和上面的取模分片就一个区别:id 的来源不同。

  • 取模分片:由程序员提供 id 值。
  • 全局 id 分片:由 MyCat 提供 id 值。

img

具体来说,MyCat 提供 id 值有下面这些办法:

  • 基于本地文件
  • 基于数据库
  • 基于 zookeeper
  • 基于时间戳
2、操作
①基于本地文件

第一步:配置 sequence_conf.properties

文件位置:

img

配置内容:

#使用过的历史分片,可不配置
EMP.HISTRY=
# ID 的起始值,从这个值开始生成ID
EMP.MINID=1
# 最大的ID值
EMP.MAXID=200000
# 当前ID值
EMP.CURID=1000

配置中属性名前缀的作用:

img

第二步:配置 server.xml

指定全局 id 分片具体使用的 id 生成方式。

<!--设置全局序号生成方式
   0:文件
   1:数据库
   2:时间戳
   3:zookeeper
  -->
<!--必须带有MYCATSEQ_或者 mycatseq_进入序列匹配流程 注意MYCATSEQ_有空格的情况-->
<property name="sequenceHandlerType">0</property>

第三步:测试

别忘记重启 MyCat。

INSERT INTO t_virtual_emp(emp_id, emp_name) VALUES('next value for MYCATSEQ_EMP', 'jerry01');
INSERT INTO t_virtual_emp(emp_id, emp_name) VALUES('next value for MYCATSEQ_EMP', 'jerry02');
INSERT INTO t_virtual_emp(emp_id, emp_name) VALUES('next value for MYCATSEQ_EMP', 'jerry03');
INSERT INTO t_virtual_emp(emp_id, emp_name) VALUES('next value for MYCATSEQ_EMP', 'jerry04');
INSERT INTO t_virtual_emp(emp_id, emp_name) VALUES('next value for MYCATSEQ_EMP', 'jerry05');
②基于数据库

第一步:在物理库建表

使用 MyCat 提供的一个 SQL 文件:

img

执行的效果:

img

第二步:在 MYCAT_SEQUENCE 表中增加一条记录

img

第三步:配置 sequence_db_conf.properties

文件位置:

img

配置内容:

# 等号后面是我们当前使用的节点
EMP_INCR=hrDataNode

属性名和其他配置的关系:

img

第四步:配置 server.xml

<!-- 指定全局 id 分片方式 -->
<!--   0 表示基于文件 -->
<!--   1 表示基于数据库 -->
<property name="sequenceHandlerType">1</property>

第五步:重启 MyCat

第六步:测试

INSERT INTO t_virtual_emp(emp_id, emp_name) VALUES('next value for MYCATSEQ_EMP_INCR', 'kate01');
INSERT INTO t_virtual_emp(emp_id, emp_name) VALUES('next value for MYCATSEQ_EMP_INCR', 'kate02');
INSERT INTO t_virtual_emp(emp_id, emp_name) VALUES('next value for MYCATSEQ_EMP_INCR', 'kate03');
INSERT INTO t_virtual_emp(emp_id, emp_name) VALUES('next value for MYCATSEQ_EMP_INCR', 'kate04');
INSERT INTO t_virtual_emp(emp_id, emp_name) VALUES('next value for MYCATSEQ_EMP_INCR', 'kate05');


SELECT emp_id,emp_name FROM t_virtual_emp;
③基于时间戳

第一步:配置 server.xml

<!-- 指定全局 id 分片方式 -->
<!--   0 表示基于文件 -->
<!--   1 表示基于数据库 -->
<!--   2 表示基于时间戳 -->
<property name="sequenceHandlerType">2</property>

第二步:配置 sequence_time_conf.properties

WORKID 与 DATAACENTERID 都是 0-31 任意整数。多 mycat 节点下,每个节点的 WORKID、DATAACENTERID 不能重复,组成唯一标识,总共支持 32*32=1024 种组合。

img

第三步:修改物理表 emp_id 字段宽度

基于时间戳的方式将使用时间戳数值作为 emp_id 值,必须使用 bigint 类型。

第四步:重启 MyCat

略。

第五步:测试

next value for MYCATSEQ_ 后面可以随便写。

INSERT INTO t_virtual_emp(emp_id, emp_name) VALUES('next value for MYCATSEQ_AA', 'BOB01');
INSERT INTO t_virtual_emp(emp_id, emp_name) VALUES('next value for MYCATSEQ_AA', 'BOB02');
INSERT INTO t_virtual_emp(emp_id, emp_name) VALUES('next value for MYCATSEQ_AA', 'BOB03');
INSERT INTO t_virtual_emp(emp_id, emp_name) VALUES('next value for MYCATSEQ_AA', 'BOB04');
INSERT INTO t_virtual_emp(emp_id, emp_name) VALUES('next value for MYCATSEQ_AA', 'BOB05');

第三节 枚举分片

1、理解

img

2、操作
第一步:创建数据库和表

在物理库所在的服务器上执行下面的 SQL 语句:

CREATE DATABASE `db_hr_male`;
CREATE DATABASE `db_hr_female`; 
CREATE TABLE `db_hr_female`.`t_emp` ( `emp_id` INT NOT NULL AUTO_INCREMENT, `emp_name` CHAR(100), `emp_gender` CHAR(100), PRIMARY KEY (`emp_id`) );
CREATE TABLE `db_hr_male`.`t_emp` ( `emp_id` INT NOT NULL AUTO_INCREMENT, `emp_name` CHAR(100), `emp_gender` CHAR(100), PRIMARY KEY (`emp_id`) );
第二步:创建新的 dataNode

在 schema.xml 配置文件中增加配置:

<dataNode name="data-node-male" dataHost="testHost" database="db_hr_male"/>
<dataNode name="data-node-female" dataHost="testHost" database="db_hr_female"/>
第三步:配置 table 标签

在 schema.xml 配置文件中修改配置:

<!--
   1、将虚拟库表设置成和物理表一样
   2、让DataNode属性指向枚举分片的不同的数据节点
   3、去掉subTables属性的配置
   4、rule规则设定成sharding-by-intfile
  -->
<table name="t_emp"
       primaryKey="emp_id"
       dataNode="data-node-male,data-node-female"
       autoIncrement="true"
       fetchStoreNodeByJdbc="true"
       rule="sharding-by-intfile"/>
第四步:配置 rule.xml
  • 先配置 tableRule 标签
<tableRule name="sharding-by-intfile">
    <rule>
        <!--指定提供枚举值的字段-->
        <columns>emp_gender</columns>
        <!--具体算法 hash -->
        <algorithm>hash-int</algorithm>
    </rule>
</tableRule>
  • 再配置 function 标签
<function name="hash-int"
			  class="io.mycat.route.function.PartitionByFileMap">
    <!-- 指定另外一个配置文件,配置枚举值和数据节点之间的对应关系 -->
    <property name="mapFile">partition-hash-int.txt</property>
    <!--type 属性:指定枚举类型,默认值为0:0表示Integer,其他值表示String-->
    <property name="type">1</property>
    <!--defaultNode 当有一些特殊数据信息可以存放与默认节点中,小于0表示不设置默认节点,大于0则设置默认节点
    不能解析的数据存在,默认节点-->
    <property name="defaultNode">0</property>
</function>
第五步:配置 partition-hash-int.txt
#代表第一个datanode
male=0

#代表第二个datanode
female=1
3、测试
USE virtualDB;

INSERT INTO t_emp(emp_id, emp_name,emp_gender) VALUES (1, 'tom','male');
INSERT INTO t_emp(emp_id, emp_name,emp_gender) VALUES (2, 'kate','female');
4、注意点
  • table 标签中不写 subTables 属性,原因是对照枚举规则之后,不同的数据进入了不同数据库中。在各自数据库中的表名是相同的。
  • 在 partition-hash-int.txt 文件中,0 和 1 这些值表示序号,这个序号对应 table 标签中 dataNode 属性值中 dataNode 名称的顺序。比如:dataNode=“dn-docker-female,dn-docker-male” 这个例子中,dn-docker-female 的序号是 0,dn-docker-male 的序号是 1。

第四节 一致性hash分片

1、理解
①目的

最大限度的让数据均匀分布

②原理

一致性 hash 算法引入了 hash 环的概念。环的大小是 0~2³²-1。首先通过 crc16 算法计算出数据节点在 hash 环中的位置。

img

当存储数据时,也会采用同样的算法,计算出数据key的hash值,映射到hash环上。

img

然后从数据映射的位置开始,以顺时针的方式找出距离最近的数据节点,接着将数据存入到该节点中。

img

此时可以发现,数据并没有达到预期的数据均匀,可以发现如果两个数据节点在环上的距离,决定有大量数据存入了dataNode2,而仅有少量数据存入dataNode1。

为了解决数据不均匀的问题,在mycat中可以设置虚拟数据映射节点。同时这些虚拟节点会映射到实际数据节点。

img

数据仍然以顺时针方式寻找数据节点,当找到最近的数据节点无论是实际还是虚拟,都会进行存储,如果是虚拟数据节点的话,最终会将数据保存到实际数据节点中。 从而尽量的使数据均匀分布。

2、操作
①配置 schema.xml

指定拆分规则:sharding-by-murmur

②配置 rule.xml
<tableRule name="sharding-by-murmur">
    <rule>
        <columns>emp_id</columns>
        <algorithm>murmur</algorithm>
    </rule>
</tableRule>

<function name="murmur"
          class="io.mycat.route.function.PartitionByMurmurHash">
    <!-- 默认是0即可 -->
    <property name="seed">0</property>
    
    <!-- 要分片的数据库节点数量,必须指定,否则没法分片 -->
    <property name="count">2</property>
    
    <!-- 一个实际的数据库节点被映射为这么多虚拟节点,默认是160倍,也就是虚拟节点数是物理节点数的160倍 -->
    <property name="virtualBucketTimes">160</property>
</function>
3、测试
USE virtual-db-hello;

INSERT INTO t_emp(emp_id,emp_name,emp_gender) VALUES(111,'eeeeeeeeeee','male');
INSERT INTO t_emp(emp_id,emp_name,emp_gender) VALUES(888,'ttttttttttt','male');

第五节 固定hash分片

1、理解

img

2、操作
第一步:配置 schema.xml
<!-- 固定 hash 分配 -->
<!-- 配置 rule 属性:partition-by-fixed-hash -->
<table name="t_emp"
     primaryKey="emp_id"
     dataNode="data-node-male,data-node-female"
     autoIncrement="true"
     fetchStoreNodeByJdbc="true"
     rule="partition-by-fixed-hash"
/>
第二步:配置 rule.xml
<tableRule name="partition-by-fixed-hash">
  <rule>
    <!-- 指定执行 hash 运算的字段 -->
    <columns>emp_id</columns>
    
    <!-- 具体算法 -->
    <algorithm>partition-by-fixed-hash</algorithm>
  </rule>
</tableRule>

<!--
  partitionCount: 各分片空间中节点的数量。格式是:m,n
  partitionLength: 每个分片空间中分配的范围大小。格式是:x,y
  计算公式是:m * x + n * y = 1024
  当前配置:1 * 256 + 1 * 768 = 1024
 -->
<function name="partition-by-fixed-hash" class="io.mycat.route.function.PartitionByLong">
  <property name="partitionCount">1,1</property>
  <property name="partitionLength">256,768</property>
</function>
3、测试
INSERT INTO t_emp(emp_id,emp_name,emp_gender) VALUES(50,'harry07','male');
INSERT INTO t_emp(emp_id,emp_name,emp_gender) VALUES(80,'harry08','male');
INSERT INTO t_emp(emp_id,emp_name,emp_gender) VALUES(120,'harry09','male');
INSERT INTO t_emp(emp_id,emp_name,emp_gender) VALUES(360,'rose05','female');
INSERT INTO t_emp(emp_id,emp_name,emp_gender) VALUES(1780,'rose06','female');

第六节 固定范围分片

1、理解

img

2、操作
①配置 schema.xml
<!-- 固定范围分片 -->
<!-- 配置 rule 属性:auto-sharding-long -->
<table name="t_emp"
     primaryKey="emp_id"
     dataNode="data-node-male,data-node-female"
     autoIncrement="true"
     fetchStoreNodeByJdbc="true"
     rule="auto-sharding-long"
/>
②配置 rule.xml
<tableRule name="auto-sharding-long">
  <rule>
    <!-- 指定用于做 hash 运算的字段 -->
    <columns>emp_id</columns>
    <algorithm>rang-long</algorithm>
  </rule>
</tableRule>
③配置 autopartition-long.txt
# range start-end ,data node index
# K=1000,M=10000.
# 0-500M=0
# 500M-1000M=1
# 1000M-1500M=2

0-20=0
21-50=1
3、测试
INSERT INTO t_emp(emp_id,emp_name,emp_gender) VALUES(2,'jack13','male');
INSERT INTO t_emp(emp_id,emp_name,emp_gender) VALUES(4,'jack14','male');
INSERT INTO t_emp(emp_id,emp_name,emp_gender) VALUES(6,'jack15','male');
INSERT INTO t_emp(emp_id,emp_name,emp_gender) VALUES(35,'rose09','female');
INSERT INTO t_emp(emp_id,emp_name,emp_gender) VALUES(44,'rose10','female');

SELECT emp_id,emp_name,emp_gender FROM t_emp;

第七节 取模范围分片

1、理解

取模后,根据范围决定数据节点。

2、操作
①配置 schema.xml

指定分片规则:sharding-by-partition

②配置 rule.xml
<tableRule name="sharding-by-partition">
    <rule>
        <!-- 指定用于取模的字段 -->
        <columns>emp_id</columns>
        <algorithm>sharding-by-partition</algorithm>
    </rule>
</tableRule>

<function name="sharding-by-partition" class="io.mycat.route.function.PartitionByPattern">
    <!-- 求模基数 -->
    <property name="patternValue">256</property>
    
    <!-- 默认节点 -->
    <property name="defaultNode">0</property>
    
    <!-- 指定规则配置文件 -->
    <property name="mapFile">partition-pattern.txt</property>
</function>
③配置 partition-pattern.txt

这个文件没有,需要我们自己建:

#0-128表示id%256后的数据范围。
0-128=0
129-256=1
3、测试
INSERT INTO t_emp(emp_id,emp_name,emp_gender) VALUES(90,'peter01','male');
INSERT INTO t_emp(emp_id,emp_name,emp_gender) VALUES(150,'peter02','male');

第八节 字符串hash分片

1、理解

在业务场景下,有时可能会根据某个分片字段的前几个值来进行取模。如地址信息只取省份、姓名只取前一个字的姓等。此时则可以使用该种方式。

其工作方式与取模范围分片类似,该分片方式支持数值、符号、字母取模。

执行流程大致是:字符串截取→hash操作→取模→根据指定范围选择 DataNode

2、操作
[1]配置 schema.xml

指定分片规则:sharding-by-string-hash

[2]配置 rule.xml
<tableRule name="sharding-by-string-hash">
    <rule>
        <columns>emp_name</columns>
        <algorithm>sharding-by-string-hash-function</algorithm>
    </rule>
</tableRule>

<function name="sharding-by-string-hash-function" class="io.mycat.route.function.PartitionByPrefixPattern">
    <!-- 求模基数 -->
    <property name="patternValue">256</property>
    
    <!-- 截取的位数 -->
    <property name="prefixLength">5</property>
    
    <!-- 细节配置文件 -->
    <property name="mapFile">partition-pattern-string-hash.txt</property>
</function>
[3]配置 partition-pattern-string-hash.txt
0-128=0
129-256=1
3、测试
INSERT INTO t_emp(emp_id,emp_name,emp_gender) VALUES(88,'ccccccc','male');
INSERT INTO t_emp(emp_id,emp_name,emp_gender) VALUES(99,'uvwxyz','male');

第九节 时间分片

1、理解

根据时间、日期信息分片。

2、操作
①修改数据库表

给 t_emp 表增加 emp_birthday 字段。

ALTER TABLE `db_hr_male`.`t_emp` ADD COLUMN `emp_birthday` CHAR(100) NULL AFTER `emp_gender`;
ALTER TABLE `db_hr_female`.`t_emp` ADD COLUMN `emp_birthday` CHAR(100) NULL AFTER `emp_gender`;
②配置 schema.xml

指定拆分规则:sharding-by-date

③配置 rule.xml
<tableRule name="sharding-by-date">
    <rule>
        <!-- 指定用于分片的字段 -->
        <columns>emp_birthday</columns>
        <algorithm>partbyday</algorithm>
    </rule>
</tableRule>

<function name="partbyday"
          class="io.mycat.route.function.PartitionByDate">

    <!-- 日期格式 -->
    <property name="dateFormat">yyyy-MM-dd</property>

    <!-- 支持自然日分区属性 -->
    <property name="sNaturalDay">0</property>

    <!-- 开始日期 -->
    <property name="sBeginDate">2014-06-01</property>
  
    <!-- 结束日期 -->
    <property name="sEndDate">2014-06-30</property>
  
    <!-- 每隔几天算一个分片 -->
    <property name="sPartionDay">15</property>
</function>
3、测试
INSERT INTO t_emp(emp_id,emp_birthday) VALUES(666, '2014-06-10');
INSERT INTO t_emp(emp_id,emp_birthday) VALUES(777, '2014-06-25');

第四章 跨库join

概念:A 表和 B 表做关联查询,但是 A 表和 B 表不在同一个数据库中。

1、全局表

系统中基本都会存在数据字典信息,如数据分类信息、项目的配置信息等。这些字典数据最大的特点就是数据量不大并且很少会被改变。同时绝大多数的业务场景都会涉及到字典表的操作。 因此为了避免频繁的跨库join操作,结合冗余数据思想,可以考虑把这些字典信息在每一个分库中都存在一份。

mycat在进行join操作时,当业务表与全局表进行聚合会优先选择相同分片的全局表,从而避免跨库join操作。在进行数据插入时,会把数据同时插入到所有分片的全局表中。

修改 schema.xml

<table name="tb_global" dataNode="dn142,dn145" primaryKey="global_id" type="global"/>

2、ER表

ER 表也是一种为了避免跨库join的手段,在业务开发时,经常会使用到主从表关系的查询,如商品表与商品详情表。

ER 表的出现就是为了让有关系的表数据存储于同一个分片中,从而避免跨库 join 的出现。

修改 schema.xml

<table name="tb_goods" dataNode="dn129,dn130" primaryKey="goods_id" rule="sharding-by-murmur-goods">
    <childTable name="tb_goods_detail" primaryKey="goods_detail_id" joinKey="goods_id" parentKey="goods_id"></childTable>
</table>

再次添加 goods 数据的时候,有关系的表数据存储于同一个分片中

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值