Mycat分片规则实操记录

一、何为数据切分

        在使用Mycat之前,需要明白一个概念,即什么是数据切分。

        对于这个问题,简单的来说,就是指通过某种特定的条件,将我们存放在同一个数据库中的数据分散存放到多个数据库(主机)上面,以达到分散单台设备负载的效果。

        那么从切分层面来讲,切分分为两种形式,垂直切分和水平切分。

1、垂直切分

        在实际实施中,垂直切分通常是以功能模块进行切分。比如我们有一个系统,包含用户系统、订单系统、支付系统等诸多子系统,如果是一个小型的项目,它的功能模块自然是共用一个数据库,但是当项目上为了满足生产需要,就会涉及到拆分,拆分成不同的模块,而每个模块是拥有自己的数据库,那么这就带来的数据的切分。拆分如图

                                      

      那么垂直拆分了以后,有以下几个优点:

  • 数据库的拆分简单明了,拆分规则明确;
  •  应用程序模块清晰明确,整合容易;
  •  数据维护方便易行,容易定位;

    当然,也会存在以下几个缺点:

  • 部分表关联无法在数据库级别完成,需要在程序中完成,存在跨库join的问题,对于这类的表,就需要去做平衡,是数据库让步业务,共用一个数据源,还是分成多个库,业务之间通过接口来做调用;在系统初期,数据量比较少,或者资源有限的情况下,会选择共用数据源,但是当数据发展到了一定的规模,负载很大的情况,就需要必须去做分割。
  •  对于访问极其频繁且数据量超大的表仍然存在性能瓶颈,不一定能满足要求;
  •  事务处理相对更为复杂;
  •  切分达到一定程度之后,扩展性会遇到限制;
  •  过多切分可能会带来系统过渡复杂而难以维护。

2、水平切分 

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

       (1)优点

  • 表关联基本能够在数据库端全部完成;
  • 不会存在某些超大型数据量和高负载的表遇到瓶颈的问题;
  • 应用程序端整体架构改动相对较少;
  • 事务处理相对简单;
  • 只要切分规则能够定义好,基本上较难遇到扩展性限制;

(2)缺点

  • 切分规则相对更为复杂,很难抽象出一个能够满足整个数据库的切分规则;
  • 后期数据的维护难度有所增加,人为手工定位数据更困难;
  • 应用系统各模块耦合度较高,可能会对后面数据的迁移拆分造成一定的困难。
  • 跨节点合并排序分页问题。
  • 多数据源管理问题。

3、几种典型的分片规则

       在实际使用,不可能所有分片规则都会用到,需要根据来决定,采用什么的分片规则。

       那么一般有以下几种比较类似的典型:

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

      在实际拆分中,我们肯定不能瞎拆分,是需要遵循一定的规则,主要从以下几个方面考虑:

  • 能不切分尽量不要切分。      
  • 如果要切分一定要选择合适的切分规则,提前规划好。      
  • 数据切分尽量通过数据冗余或表分组(Table Group)来降低跨库Join的可能。      
  • 由于数据库中间件对数据Join实现的优劣难以把握,而且实现高性能难度极大,业务读取尽量少使用多表Join。

 

     综上所述,无论是水平拆分,还是垂直拆分,都会带来几个比较核心的问题,也是比较令人头疼的问题。

  • 引入分布式事务的问题。
  • 跨节点 Join 的问题。
  • 跨节点合并排序分页问题。

 

二、实施

        前面讲到了如何拆分以及折分会带来哪些问题。对于数据库拆分,目前有比较多的方案,比如Mycat、ShardingSphere等数据库中间件。

        那么本节仅以Mycat进行说明。

1、Mycat是什么

        Mycat是一个数据库代理,它本身不存储数据,只做数据的路由。截止目前,Mycat支持的数据库比较多,例如MySQL、SQL Server、Oracle、DB2、PostgreSQL等主流数据库,也支持MongoDB这种新型NoSQL方式的存储。

        在Mycat的原理中最重要的一个动词是“拦截”,它拦截了用户发送过来的SQL语句,首先对SQL语句做了一些特定的分析:如分片分析、路由分析、读写分离分析、缓存分析等,然后将此SQL发往后端的真实数据库,并将返回的结果做适当的处理,最终再返回给用户。

                               

2、Mycat实践

(1)安装

  至于mycat的下载安装,可以自行借助强大的搜索引擎的力量。我这里使用的是Mycat的源码。

(2)启动

  将下载好的源码,导入到idea开发工具中,配置VM参数,就可以跑起来。

使用navicat连接,效果如图

(3)关键概念

         逻辑库(schema) ——存在在mycat里面的虚拟库 逻辑表(table) 存在在mycat里面的虚拟表 。

         分片表 ——是指那些原有的很大数据的表,需要切分到多个数据库的表,这样,每个分片都有一部分数据,所有分片构成了完整的数据。

         非分片表—— 不需要进行数据切分的表。

         ER表——子表的记录与所关联的父表记录存放在同一个数据分片上,即子表依赖于父表,通过表分组(Table Group)保证数据Join不会跨库操作。表分组(Table Group)是解决跨分片数据join的一种很好的思路,也是数据切分规划的重要一条规则。

        全局表——例如字典表,每一个数据分片节点上有保存了一份字典表数据 数据冗余是解决跨分片数据join的一种很好的思路,也是数据切分规划的另外一条重要规则。

分片节点(dataNode) ——数据切分后,一个大表被分到不同的分片数据库上面,每个表分片所在的数据库就是分片节点。

        节点主机(dataHost) ——数据切分后,每个分片节点(dataNode)不一定都会独占一台机器,同一机器上面可以有多个分片数据库,这样一个或多个分片节点(dataNode)所在的机器就是节点主机(dataHost),为了规避单节点主机并发数限制,尽量将读写压力高的分片节点(dataNode)均衡的放在不同的节点主机(dataHost)。

        分片规则(rule)——前面讲了数据切分,一个大表被分成若干个分片表,就需要一定的规则,这样按照某种业务规则把数据分到某个分片的规则就是分片规则,数据切分选择合适的分片规则非常重要,将极大的避免后续数据处理的难度。

        全局序列号(sequence) ——数据切分后,原有的关系数据库中的主键约束在分布式条件下将无法使用,因此需要引入外部机制保证数据唯一性标识,这种保证全局性的数据唯一标识的机制就是全局序列号(sequence)。

(4)全局序列号

  • 文件方式获得序列号

         进入到resources目录下,找到“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值。GLOBAL_SEQ你可以自己定义。

         然后在server.xml文件中,做如下配置

<system><property name="sequnceHandlerType">0</property></system> 

        注:sequnceHandlerType需要配置为0,表示使用本地文件方式。

       使用示例: insert into table1(id,name) values('next value for MYCATSEQ_ORDER','test');

       效果如图:

       缺点:当MyCAT重新发布后,配置文件中的sequence会恢复到初始值。

       优点:本地加载,读取速度较快。

 

  • 基于zookeeper生成全局序列

        进入到resources目录下,找到“myid.properties”文件,做如下配置

loadZk=true    #  注意,如是要不使用zk时,这个值必须设置为false
zkURL=192.168.1.105:2181
clusterId=01
myid=mycat_fz_01
clusterSize=1
clusterNodes=mycat_fz_01

       找到“sequence_distributed_conf.properties”文件,做如下配置

#从zk中获取实例id
INSTANCEID=ZK
#集群id
CLUSTERID=01

      然后在server.xml文件中,做如下配置

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

      效果如下图

  • 基于时间戳生成全局序列

        进入到resources目录下,找到“sequence_time_conf.properties”文件,做如下配置

# 0-31为整数,每一个mycat节点的这两个配置值都不一样
WORKID=01   # 机器的id
DATAACENTERID=01  # 机房的id

       单从这个配置上看,它是借助了雪花算法的思想。

       然后在server.xml文件中,做如下配置

<property name="sequenceHandlerType">2</property>

       效果如下图

          

 

(4)分片规则

        首先需要说明的配置文件

        schema.xml——配置逻辑表、逻辑库与真实库的映射关系,以及分片采用的规则。内容参考

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
	<!--逻辑库,跟数据库的database的概念相同-->
	<!--schema上面的dataNode是逻辑库下面的表的默认数据节点
			sqlMaxLimit:效果类似于sql语句里面加上limit,不过如果schema为非分片库则该属性不生效
	-->
	<schema name="enjoyDB" checkSQLschema="true" dataNode="dn105" sqlMaxLimit="100">
		<!--mycat中的逻辑表-->
		<!--
				name:逻辑表的名称,名称必须唯一
				dataNode:值必须跟dataNode标签中的name对应,如果值过多可以用 dataNode="dn$0-99,cn$100-199"
				rule:分片规则配置,定义在rule.xml中,必须与tableRule中的name对应
				ruleRequired:该属性用于指定表是否绑定分片规则,如果配置为true,但没有配置具体rule的话 ,程序会报错
				primaryKey:该逻辑表对应真实表的主键,例如:分片的规则是使用非主键进行分片的,那么在使用主键查询的时候,就会发送查询语句到所有配置的DN上,如果使用该属性配置真实表的主键。难么MyCat会缓存主键与具体DN的信息,那么再次使用非主键进行查询的时候就不会进行广播式的查询,就会直接发送语句给具体的DN,但是尽管配置该属性,如果缓存并没有命中的话,还是会发送语句给具体的DN,来获得数据
				type:全局表:global  每一个dn都会保存一份全局表,普通表:不指定该值为globla的所有表
				autoIncrement:autoIncrement=“true”,默认是禁用的
				needAddLimit:默认是true
		-->
		<!--注意:这里并没有指定分表,所以做表操作的时候,就是操作的真实的表本身-->
		<!--	另说明:通过mycat中打印的日志中,可以看出,节点0对应的就是dn105, 1对应的是dn110-->
		<table dataNode="dn105,dn110"  primaryKey="orderId"  name="t_order_sharing_by_intfile" rule="sharding-by-intfile"></table>
	</schema>

	<dataNode name="dn105" database="consult_rule" dataHost="host105"/>
	<dataNode name="dn110" database="consult_rule" dataHost="host110"/>
	<!--
				maxCon:指定每个读写实例连接池的最大连接。也就是说,标签内嵌套的writeHost、readHost标签都会使用这个属性的值来实例化出连接池的最大连接数
				minCon:指定每个读写实例连接池的最小连接,初始化连接池的大小
				balance:
						1. balance="0", 不开启读写分离机制,所有读操作都发送到当前可用的writeHost上。
						2. balance="1",全部的readHost与stand by writeHost参与select语句的负载均衡,简单的说,当双主双从模式(M1->S1,M2->S2,并且M1与 M2互为主备),正常情况下,M2,S1,S2都参与select语句的负载均衡。
						3. balance="2",所有读操作都随机的在writeHost、readhost上分发。
						4. balance="3",所有读请求随机的分发到writeHost对应的readhost执行,writerHost不负担读压力,注意balance=3只在1.4及其以后版本有,1.3没有。
							writeType:
							负载均衡类型,目前的取值有3种:
									1. writeType="0", 所有写操作发送到配置的第一个writeHost,第一个挂了切到还生存的第二个writeHost,重新启动后以切换后的为准,切换记录在配置文件中:dnindex.properties .
									2. writeType="1",所有写操作都随机的发送到配置的writeHost。
									3. writeType="2",没实现
										switchType:
										  -1 表示不自动切换
										   1 默认值,自动切换
										   2 基于MySQL主从同步的状态决定是否切换
											  心跳语句为 show slave status
										   3 基于MySQL galary cluster的切换机制(适合集群)(1.4.1)
											  心跳语句为 show status like ‘wsrep%’
	-->


	<dataHost name="host105" maxCon="1000" minCon="10" balance="3"  writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1"  slaveThreshold="100">
		<!--heartbeat标签
				MYSQL可以使用select user()
				Oracle可以使用select 1 from dual
		-->
		<heartbeat>select user()</heartbeat>
		<connectionInitSql></connectionInitSql>
		<!-- can have multi write hosts -->
		<!--
				如果writeHost指定的后端数据库宕机,那么这个writeHost绑定的所有readHost都将不可用。
				另一方面,由于这个writeHost宕机系统会自动的检测到,并切换到备用的writeHost上去
		-->
		<writeHost host="hostM1" url="jdbc:mysql://192.168.1.105:3400"  user="root"  password="123456">
			<readHost host="hostM2"  url="jdbc:mysql://192.168.1.105:3401"  user="root"  password="123456" />
		</writeHost>
	</dataHost>

	<dataHost name="host110" maxCon="1000" minCon="10" balance="3"  writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1"  slaveThreshold="100">
		<heartbeat>select user()</heartbeat>
		<connectionInitSql></connectionInitSql>
		<writeHost host="hostM1" url="jdbc:mysql://192.168.1.110:3400"  user="root"  password="123456">
			<readHost host="hostM2"  url="jdbc:mysql://192.168.1.110:3401"  user="root"  password="123456" />
		</writeHost>
	</dataHost>
</mycat:schema>

      server.xml——系统级别的配置。比如可以在这个文件里面配置连接mycat的账号密码等,如下图

                              

      rule.xml——配置分片规则的地方。内容如下

<?xml version="1.0" encoding="UTF-8"?>
<!-- - - Licensed under the Apache License, Version 2.0 (the "License"); 
	- you may not use this file except in compliance with the License. - You 
	may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 
	- - Unless required by applicable law or agreed to in writing, software - 
	distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 
	WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the 
	License for the specific language governing permissions and - limitations 
	under the License. -->
<!DOCTYPE mycat:rule SYSTEM "rule.dtd">
<mycat:rule xmlns:mycat="http://io.mycat/">
	<tableRule name="rule1">
		<rule>
			<columns>id</columns>
			<algorithm>func1</algorithm>
		</rule>
	</tableRule>

	<tableRule name="gd-hash">
		<rule>
			<columns>orderId</columns>
			<algorithm>gd-hash-func</algorithm>
		</rule>
	</tableRule>

	<tableRule name="sharding-by-date">
		<rule>
			<columns>createTime</columns>
			<algorithm>partbyday</algorithm>
		</rule>
	</tableRule>
	<tableRule name="sharding-by-date-day">
		<rule>
			<columns>createTime</columns>
			<algorithm>partbyday</algorithm>
		</rule>
	</tableRule>

	<tableRule name="rule2">
		<rule>
			<columns>user_id</columns>
			<algorithm>func1</algorithm>
		</rule>
	</tableRule>

	<!--
	分片枚举
	通过在配置文件中配置可能的枚举id,自己配置分片,本规则适用于特定的场景,比如有些业务需要按照省份或区县来做保存,而全国省份区县固定的
	-->
	<tableRule name="sharding-by-intfile">
		<rule>
			<!--根据这个字段进行分片-->
			<columns>province</columns>
			<algorithm>hash-int</algorithm>
		</rule>
	</tableRule>
	<tableRule name="auto-sharding-long">
		<rule>
			<columns>orderId</columns>
			<algorithm>rang-long</algorithm>
		</rule>
	</tableRule>
	<tableRule name="mod-long">
		<rule>
			<columns>order_id</columns>
			<algorithm>mod-long</algorithm>
		</rule>
	</tableRule>
	<tableRule name="sharding-by-murmur">
		<rule>
			<columns>orderId</columns>
			<algorithm>murmur</algorithm>
		</rule>
	</tableRule>
	<tableRule name="crc32slot">
		<rule>
			<columns>id</columns>
			<algorithm>crc32slot</algorithm>
		</rule>
	</tableRule>
	<tableRule name="sharding-by-month">
		<rule>
			<columns>create_time</columns>
			<algorithm>partbymonth</algorithm>
		</rule>
	</tableRule>
	<tableRule name="latest-month-calldate">
		<rule>
			<columns>calldate</columns>
			<algorithm>latestMonth</algorithm>
		</rule>
	</tableRule>

	<tableRule name="auto-sharding-rang-mod">
		<rule>
			<columns>id</columns>
			<algorithm>rang-mod</algorithm>
		</rule>
	</tableRule>

	<tableRule name="jch">
		<rule>
			<columns>orderId</columns>
			<algorithm>jump-consistent-hash</algorithm>
		</rule>
	</tableRule>

	<tableRule name="jch1">
		<rule>
			<columns>orderDetailId</columns>
			<algorithm>jump-consistent-hash</algorithm>
		</rule>
	</tableRule>

	<function name="murmur"
			  class="io.mycat.route.function.PartitionByMurmurHash">
		<property name="seed">0</property><!-- 默认是0 -->
		<property name="count">2</property><!-- 要分片的数据库节点数量,必须指定,否则没法分片 -->
		<property name="virtualBucketTimes">160</property><!-- 一个实际的数据库节点被映射为这么多虚拟节点,默认是160倍,也就是虚拟节点数是物理节点数的160倍 -->
		<!-- <property name="weightMapFile">weightMapFile</property> 节点的权重,没有指定权重的节点默认是1。以properties文件的格式填写,以从0开始到count-1的整数值也就是节点索引为key,以节点权重值为值。所有权重值必须是正整数,否则以1代替 -->
		<property name="bucketMapPath">H:\eclipse-for-xiangxue-2qi-workspace\MycatStudy\Mycat-Server-Mycat-server-1675-release\src\main\resources</property>
		<!-- 用于测试时观察各物理节点与虚拟节点的分布情况,如果指定了这个属性,会把虚拟节点的murmur hash值与物理节点的映射按行输出到这个文件,没有默认值,如果不指定,就不会输出任何东西 -->
	</function>

	<function name="crc32slot"
			  class="io.mycat.route.function.PartitionByCRC32PreSlot">
		<property name="count">2</property><!-- 要分片的数据库节点数量,必须指定,否则没法分片 -->
	</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>
	<function name="rang-long"
			  class="io.mycat.route.function.AutoPartitionByLong">
		<property name="mapFile">autopartition-long.txt</property>
	</function>
	<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
		<!-- how many data nodes -->
		<property name="count">3</property>
	</function>

	<function name="gd-hash-func" class="io.mycat.route.function.PartitionByLong">
		<!--下面的配置,表示,一共分了两个分片段。即1个长度为256,一个长度为768-->
		<!--为分片个数列表-->
		<property name="partitionCount">1,1</property>
		<!--分片对应的长度-->
		<property name="partitionLength">256,768</property>
	</function>


	<function name="func1" class="io.mycat.route.function.PartitionByLong">
		<property name="partitionCount">8</property>
		<property name="partitionLength">128</property>
	</function>
	<function name="latestMonth"
			  class="io.mycat.route.function.LatestMonthPartion">
		<property name="splitOneDay">24</property>
	</function>
	<function name="partbymonth"
			  class="io.mycat.route.function.PartitionByMonth">
		<property name="dateFormat">yyyy-MM-dd</property>
		<property name="sBeginDate">2015-01-01</property>
	</function>


	<function name="partbyday"
			  class="io.mycat.route.function.PartitionByDate">
		<property name="dateFormat">yyyy-MM-dd</property>
		<property name="sNaturalDay">0</property>
		<property name="sBeginDate">2020-05-01</property>
		<!--
				它的作用是指,如果分片的节点数少于(结束日期-开始日期)/10 个节点的话,那么就在真实的分片节点之前来回的切换。
		         如果不指定的话,就不会来回切换,而是一直往前走,假如真实的只有两个节点,那么可能算出来的是第3个节点,而节点3是不存在的
		-->
		<property name="sEndDate">2020-05-20</property>
		<!--表示每满10天换个分片区。即2020-05-01到2020-05-10对应0分片段,2020-05-11到2020-05-20对应1分片段-->
		<!--在几个指定的dn节点之间来回切换-->
		<property name="sPartionDay">10</property>
	</function>

	<function name="rang-mod" class="io.mycat.route.function.PartitionByRangeMod">
		<property name="mapFile">partition-range-mod.txt</property>
	</function>

	<function name="jump-consistent-hash" class="io.mycat.route.function.PartitionByJumpConsistentHash">
		<property name="totalBuckets">2</property>
	</function>
</mycat:rule>

      用到的数据库脚本如下

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for t_order
-- ----------------------------
DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order` (
  `orderId` bigint(20) NOT NULL,
  `orderName` varchar(255) NOT NULL,
  `orderType` varchar(255) CHARACTER SET utf8 NOT NULL,
  `createTime` datetime DEFAULT NULL,
  PRIMARY KEY (`orderId`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

-- ----------------------------
-- Table structure for t_order_detail
-- ----------------------------
DROP TABLE IF EXISTS `t_order_detail`;
CREATE TABLE `t_order_detail` (
  `orderDetailId` bigint(20) NOT NULL,
  `orderId` bigint(20) NOT NULL,
  `orderDetailName` varchar(255) NOT NULL,
  `createTime` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`orderDetailId`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

-- ----------------------------
-- Table structure for t_order_gd_hash
-- ----------------------------
DROP TABLE IF EXISTS `t_order_gd_hash`;
CREATE TABLE `t_order_gd_hash` (
  `orderId` bigint(20) NOT NULL,
  `orderName` varchar(255) NOT NULL,
  `createTime` datetime DEFAULT NULL,
  PRIMARY KEY (`orderId`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

-- ----------------------------
-- Table structure for t_order_murmur_hash
-- ----------------------------
DROP TABLE IF EXISTS `t_order_murmur_hash`;
CREATE TABLE `t_order_murmur_hash` (
  `orderId` bigint(20) NOT NULL,
  `orderName` varchar(255) NOT NULL,
  `createTime` datetime DEFAULT NULL,
  PRIMARY KEY (`orderId`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

-- ----------------------------
-- Table structure for t_order_range
-- ----------------------------
DROP TABLE IF EXISTS `t_order_range`;
CREATE TABLE `t_order_range` (
  `orderId` bigint(20) NOT NULL,
  `orderName` varchar(255) NOT NULL,
  `createTime` datetime DEFAULT NULL,
  PRIMARY KEY (`orderId`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

-- ----------------------------
-- Table structure for t_order_sharing_by_intfile
-- ----------------------------
DROP TABLE IF EXISTS `t_order_sharing_by_intfile`;
CREATE TABLE `t_order_sharing_by_intfile` (
  `orderId` bigint(20) NOT NULL,
  `orderName` varchar(255) NOT NULL,
  `createTime` datetime DEFAULT NULL,
  `province` varchar(255) NOT NULL,
  PRIMARY KEY (`orderId`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

-- ----------------------------
-- Table structure for t_order_time_day
-- ----------------------------
DROP TABLE IF EXISTS `t_order_time_day`;
CREATE TABLE `t_order_time_day` (
  `orderId` bigint(20) NOT NULL,
  `orderName` varchar(255) NOT NULL,
  `createTime` datetime DEFAULT NULL,
  PRIMARY KEY (`orderId`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

-- ----------------------------
-- Table structure for t_order_type
-- ----------------------------
DROP TABLE IF EXISTS `t_order_type`;
CREATE TABLE `t_order_type` (
  `orderType` varchar(255) NOT NULL,
  `orderTypeName` varchar(255) CHARACTER SET utf8 NOT NULL,
  PRIMARY KEY (`orderType`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

      1) 枚举分片

            在schema文件中的schema标签中加入如下内容

<table dataNode="dn105,dn110"  primaryKey="orderId"  name="t_order_sharing_by_intfile" rule="sharding-by-intfile"></table>

            其中,rule属性的配置在上面rule.xml文件中有说明。可以参考

            然后,进入resources目录下,在“partition-hash-int.txt”中配置如下面的格式所示的内容

hunan=0
hubei=1
beijing=0
shanghai=1
chognqing=1
sichuan=1
xizang=0

             其中,0、1是表示在schema文件中table标签中配置的“dataNode”的属性值所对应的下标,即0对应dn105节点,1对应        110节点

             这样配置完成后,数据就会遵循这个规则被存在不同的库的表中。

             应用场景:通过在配置文件中配置可能的枚举id,自己配置分片,本规则适用于特定的场景,比如有些业务需要按照省

      份或区县来做保存,不同的省份存到不同的数据节点。

             使用这种方式,会带来一个问题,由于数据是按地域划分的,那么肯定会出现某些结果上的数据特别多。比如上海的订单总      体来说肯定要比西藏的多,那么上海的数据库节点肯定要添加啊,但是如上面的配置文件里面显示的,一个地域只能对应一个节        点,针对这种情况,有什么好的解决方案呢?无非还是使用mycat,如下图

              

     2) 固定hash分片

             这个分片,不是那么直观,它的大概原理——类似于十进制的求模运算,区别在于是二进制的操作,是取id的二进制低10位,

      即id二进制&1111111111。 此算法的优点在于如果按照10进制取模运算,在连续插入1-10时候1-10会被分到1-10个分片,增大

     了插入的事务控制难度,而此算法根据二进制则可能会分到连续的分片,减少插入事务事务控制难度

                                                 

             以上分为三个分区:0-255,256-511,512-1023 故user_id的低10位二进制&1111111111后根据以上范围落入指定的区域。

      例如: 1023的二进制&1111111111运算后为1023,故落入第三个分区 1024的二进制&1111111111运算后为0,故落入第一个

      分区 266 的二进制&1111111111运算后为266,故落入第二个分区内。

             在schema文件中的schema标签中加入如下内容

<table dataNode="dn105,dn110"  primaryKey="orderId"  name="t_order_gd_hash" rule="gd-hash"></table>

             其中,rule属性的配置在上面rule.xml文件中有说明。可以参考

             应用场景:这中分片方式使用的场景不多,如机器性能有高低之分的时候,可以考虑这种方式。

             它的优点:减少了分布式事务,数据不会散列。

 

     3) 字段范围分片 

             在schema文件中的schema标签中加入如下内容

<table dataNode="dn105,dn110"  primaryKey="orderId"  name="t_order_range" rule="auto-sharding-long"></table>

             其中,rule属性的配置在上面rule.xml文件中有说明。可以参考

             进入resources目录下,在“autopartition-long.txt”文件中做如下配置

0-1000=0
1001-2000=1

             其中,0、1是表示在schema文件中table标签中配置的“dataNode”的属性值所对应的下标,即0对应dn105节点,1对应        110节点。

             注意,如果被分片的字段的值不在这个配置里面指定的范围中,则在分片的时候,会直接抛出异常。

             应用场景:此分片适用于,提前规划好分片字段某个范围属于哪个分片。

 

      4) 按天分片

            在schema文件中的schema标签中加入如下内容

<table dataNode="dn105,dn110"  primaryKey="orderId"  name="t_order_time_day" rule="sharding-by-date-day"></table>

           其中,rule属性的配置在上面rule.xml文件中有说明。可以参考

           这种分片方式,在实际使用中,比较常见

           应用场景:此规则为按天分片

      5) 一致性hash

           前面四种分片方式,有一个共同的问题,就是会出现数据的分布不均匀

           在schema文件中的schema标签中加入如下内容

<table dataNode="dn105,dn110"  primaryKey="orderId"  name="t_order_murmur_hash" rule="sharding-by-murmur"></table>

           其中,rule属性的配置在上面rule.xml文件中有说明。可以参考

           应用场景:数据均匀分布,不出现数据倾斜的情况。

           原理如下图

                                              

   6)全局表

   在mycat中把一个表做成全局表,需要满足几个特点:

  • 数据量少
  • 每一个分片都需要用到
  • 往往是系统的字典表

  为了避免跨库join,所以在保存数据时候,自动地在每一个分片节点里面都保存了一份

  在schema文件中的schema标签中加入如下内容

<table dataNode="dn105,dn110" primaryKey="orderType" name="t_order_type"   type="global"></table>

   在查询全局表中内容的时候,只会随机在某一个节点上这个全局表中查询。

   7)ER分片表

   在mycat  ER分片表,需要满足几个特点:

  • 表和表之间存在主从关系
  • 存在外键关联

   比如:订单表t_order和订单明细表t_order_detail,这种就需要把存在关联关系的id存储到 同一个分片节点里面。

   在schema文件中的schema标签中加入如下内容

<table name="t_order" dataNode="dn105,dn110"  primaryKey="orderId" rule="jch">
	<childTable name="t_order_detail" primaryKey="orderDetailId" joinKey="orderId" parentKey="orderId"/>
</table>

   8)mycat中如何解决跨库join

   为了解决跨库join,mycat中利用Share join方式进行跨库JOIN,原理就是解析SQL语句,拆分成单表的SQL语句执行,然后把     各个节点的数据汇集。如下

/*!mycat:catlet=io.mycat.catlets.ShareJoin */select b.* from t_order a join t_order_detail b on a.orderId=b.orderId;

   9)如何进行批量插入

   在使用mycat后,面对批量插入数据时,其实和普通情况下没有区别。以mycat+mybatis为例,写法如下:

   @Insert({"<script>",
      //"/*!mycat:catlet=io.mycat.route.sequence.BatchInsertSequence */",
            "insert into t_order(order_content) values",
            "<foreach collection='list' item='item' index='index' separator=','>",
            "(#{item.order_content})",
            "</foreach>",
            "</script>"})
    void addT_OrderByBatch(List<TOrder> orders);

    需要注意的是,在Mycat中,分片的键只支持一个

三、命令行监控

        mycat有两个端口 8066是数据端口,9066是管理端口。

        登录9066管理端口:

mysql -uenjoy -p123456 -P9066 

        查询所有指令

show @@help; 

        加载配置文件,无需启动mycat(注意会阻塞mycat

reload @@config 

        查看数据库

show @@database; 

        查看分片节点

show @@datanode;

        查找对应的schema下面的dataNode列表

show @@datanode where schema = 逻辑库名称;

        报告心跳状态

show @@heartbeat;

        获取Mycat的前端连接状态,即应用与mycat的连接

 show @@connection;

        查看数据源状态,如果配置了主从,或者多主可以切换   

show @@datasource;

        切换数据源

switch @@datasource name:index;
name:schema中配置的dataHost 中name
index:schema中配置的dataHost 的writeHost index 位标,即按照配置顺序从上到下的一次顺 序,从0开始。
切换数据源时,会将原数据源所有的连接池中连接关闭,并且从新数据源创建新连接,此时mycat服务不可用
dnindex.properties 文件在记录了当前的活跃writer。

        注:reload @@config, switch @@datasource name:index , 这两个命令再进行处理时,mycat服务不可用,谨慎处理, 

    防止正在提交的事务出错

四、Mycat数据扩容

        我们知道,当我们使用一个HashMap来存储数据时,它是有初始容量大小的,当存储的数据的总量到达总容量的3/4时,就会触发扩容,那么就免不了再hash的过程,扩容后,原有存在的数据的位置就会发生改变。其实Mycat扩容的原理跟HashMap的扩容的原理差不多,都是对数据进行了再分配。

        下面就介绍针对Mycat如何做数据的扩容

 1、mycat安装   

       这个安装的过程,还是很简单的,首先自己先去下载好mycat的安装包,如我这里是下载的版本为Mycat-server-1.6.7.5-      release-20200422133810-linux.tar.gz,下载好后,进行解压,解压后,进入mycat的conf目录。

       schema.xml文件内容如下所示

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
        <!--逻辑库,跟数据库的database的概念相同-->
        <!--schema上面的dataNode是逻辑库下面的表的默认数据节点
                        sqlMaxLimit:效果类似于sql语句里面加上limit,不过如果schema为非分片库则该属性不生效
        -->
        <schema name="enjoydb" checkSQLschema="true" dataNode="dn105" sqlMaxLimit="100">
                <!--mycat中的逻辑表-->
                <!--
                                name:逻辑表的名称,名称必须唯一
                                dataNode:值必须跟dataNode标签中的name对应,如果值过多可以用 dataNode="dn$0-99,cn$100-199"
                                rule:分片规则配置,定义在rule.xml中,必须与tableRule中的name对应
                                ruleRequired:该属性用于指定表是否绑定分片规则,如果配置为true,但没有配置具体rule的话 ,程序会报错
                                primaryKey:该逻辑表对应真实表的主键,例如:分片的规则是使用非主键进行分片的,那么在使用主键查询的时候,就会发送查询语句到所有配置的DN上,如果使用该属性配置真实表的主键。难么MyCat会缓存主键与具体DN的信息,那么再次使用非主键进行查询的时候就不会进行广播式的查询,就会直接发送语句给具体的DN,但是尽管配置该属性,如果缓存并没有命中的话,还是会发送语句给具体的DN,来获得数据
                                type:全局表:global  每一个dn都会保存一份全局表,普通表:不指定该值为globla的所有表
                                autoIncrement:autoIncrement=“true”,默认是禁用的
                                needAddLimit:默认是true
                -->
                <!--注意:这里并没有指定分表,所以做表操作的时候,就是操作的真实的表本身-->
                <table name="t_order" dataNode="dn105,dn110"  primaryKey="orderId" rule="jch">
                        <childTable name="t_order_detail" primaryKey="orderDetailId" joinKey="orderId" parentKey="orderId"/>
                </table>
        </schema>

        <dataNode name="dn105" database="consult_rule" dataHost="host105"/>
        <dataNode name="dn110" database="consult_rule" dataHost="host110"/>
        <!--
                                maxCon:指定每个读写实例连接池的最大连接。也就是说,标签内嵌套的writeHost、readHost标签都会使用这个属性的值来实例化出连接池的最大连接数
                                minCon:指定每个读写实例连接池的最小连接,初始化连接池的大小
                                balance:
                                                1. balance="0", 不开启读写分离机制,所有读操作都发送到当前可用的writeHost上。
                                                2. balance="1",全部的readHost与stand by writeHost参与select语句的负载均衡,简单的说,当双主双从模式(M1->S1,M2->S2,并且M1与 M2互为主备),正常情况下,M2,S1,S2都参与select语句的负载均衡。
                                                3. balance="2",所有读操作都随机的在writeHost、readhost上分发。
                                                4. balance="3",所有读请求随机的分发到writeHost对应的readhost执行,writerHost不负担读压力,注意balance=3只在1.4及其以后版本有,1.3没有。
                                                        writeType:
                                                        负载均衡类型,目前的取值有3种:
                                                                        1. writeType="0", 所有写操作发送到配置的第一个writeHost,第一个挂了切到还生存的第二个writeHost,重新启动后以切换后的为准,切换记录在配置文件中:dnindex.properties .
                                                                        2. writeType="1",所有写操作都随机的发送到配置的writeHost。
                                                                        3. writeType="2",没实现
                                                                                switchType:
                                                                                  -1 表示不自动切换
                                                                                   1 默认值,自动切换
                                                                                   2 基于MySQL主从同步的状态决定是否切换
                                                                                          心跳语句为 show slave status
                                                                                   3 基于MySQL galary cluster的切换机制(适合集群)(1.4.1)
                                                                                          心跳语句为 show status like ‘wsrep%’
        -->


        <dataHost name="host105" maxCon="1000" minCon="10" balance="3"  writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1"  slaveThreshold="100">
                <!--heartbeat标签
                                MYSQL可以使用select user()
                                Oracle可以使用select 1 from dual
                -->
                <heartbeat>select user()</heartbeat>
                <connectionInitSql></connectionInitSql>
                <!-- can have multi write hosts -->
                <!--
                                如果writeHost指定的后端数据库宕机,那么这个writeHost绑定的所有readHost都将不可用。
                                另一方面,由于这个writeHost宕机系统会自动的检测到,并切换到备用的writeHost上去
                -->
                <writeHost host="hostM1" url="jdbc:mysql://192.168.1.105:3400"  user="root"  password="123456">
                        <readHost host="hostM2"  url="jdbc:mysql://192.168.1.105:3401"  user="root"  password="123456" />
                </writeHost>
        </dataHost>

        <dataHost name="host110" maxCon="1000" minCon="10" balance="3"  writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1"  slaveThreshold="100">
                <heartbeat>select user()</heartbeat>
                <connectionInitSql></connectionInitSql>
                <writeHost host="hostM1" url="jdbc:mysql://192.168.1.110:3400"  user="root"  password="123456">
                        <readHost host="hostM2"  url="jdbc:mysql://192.168.1.110:3401"  user="root"  password="123456" />
                </writeHost>
        </dataHost>
</mycat:schema>

       再新建一个名为newSchema.xml的文件,注意一定要是这个名称,内容如下

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
        <!--逻辑库,跟数据库的database的概念相同-->
        <!--schema上面的dataNode是逻辑库下面的表的默认数据节点
                        sqlMaxLimit:效果类似于sql语句里面加上limit,不过如果schema为非分片库则该属性不生效
        -->
        <schema name="enjoydb" checkSQLschema="true" dataNode="dn105" sqlMaxLimit="100">
                <!--mycat中的逻辑表-->
                <!--
                                name:逻辑表的名称,名称必须唯一
                                dataNode:值必须跟dataNode标签中的name对应,如果值过多可以用 dataNode="dn$0-99,cn$100-199"
                                rule:分片规则配置,定义在rule.xml中,必须与tableRule中的name对应
                                ruleRequired:该属性用于指定表是否绑定分片规则,如果配置为true,但没有配置具体rule的话 ,程序会报错
                                primaryKey:该逻辑表对应真实表的主键,例如:分片的规则是使用非主键进行分片的,那么在使用主键查询的时候,就会发送查询语句到所有配置的DN上,如果使用该属性配置真实表的主键。难么MyCat会缓存主键与具体DN的信息,那么再次使用非主键进行查询的时候就不会进行广播式的查询,就会直接发送语句给具体的DN,但是尽管配置该属性,如果缓存并没有命中的话,还是会发送语句给具体的DN,来获得数据
                                type:全局表:global  每一个dn都会保存一份全局表,普通表:不指定该值为globla的所有表
                                autoIncrement:autoIncrement=“true”,默认是禁用的
                                needAddLimit:默认是true
                -->
                <!--注意:这里并没有指定分表,所以做表操作的时候,就是操作的真实的表本身-->
                <table name="t_order" dataNode="dn105,dn110,dn110-1"  primaryKey="orderId" rule="jch">
                        <childTable name="t_order_detail" primaryKey="orderDetailId" joinKey="orderId" parentKey="orderId"/>
                </table>
        </schema>

        <dataNode name="dn105" database="consult_rule" dataHost="host105"/>
        <dataNode name="dn110" database="consult_rule" dataHost="host110"/>
        <dataNode name="dn110-1" database="consult_rule" dataHost="host110-1"/>
        <!--
                                maxCon:指定每个读写实例连接池的最大连接。也就是说,标签内嵌套的writeHost、readHost标签都会使用这个属性的值来实例化出连接池的最大连接数
                                minCon:指定每个读写实例连接池的最小连接,初始化连接池的大小
                                balance:
                                                1. balance="0", 不开启读写分离机制,所有读操作都发送到当前可用的writeHost上。
                                                2. balance="1",全部的readHost与stand by writeHost参与select语句的负载均衡,简单的说,当双主双从模式(M1->S1,M2->S2,并且M1与 M2互为主备),正常情况下,M2,S1,S2都参与select语句的负载均衡。
                                                3. balance="2",所有读操作都随机的在writeHost、readhost上分发。
                                                4. balance="3",所有读请求随机的分发到writeHost对应的readhost执行,writerHost不负担读压力,注意balance=3只在1.4及其以后版本有,1.3没有。
                                                        writeType:
                                                        负载均衡类型,目前的取值有3种:
                                                                        1. writeType="0", 所有写操作发送到配置的第一个writeHost,第一个挂了切到还生存的第二个writeHost,重新启动后以切换后的为准,切换记录在配置文件中:dnindex.properties .
                                                                        2. writeType="1",所有写操作都随机的发送到配置的writeHost。
                                                                        3. writeType="2",没实现
                                                                                switchType:
                                                                                  -1 表示不自动切换
                                                                                   1 默认值,自动切换
                                                                                   2 基于MySQL主从同步的状态决定是否切换
                                                                                          心跳语句为 show slave status
                                                                                   3 基于MySQL galary cluster的切换机制(适合集群)(1.4.1)
                                                                                          心跳语句为 show status like ‘wsrep%’
        -->


        <dataHost name="host105" maxCon="1000" minCon="10" balance="3"  writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1"  slaveThreshold="100">
                <!--heartbeat标签
                                MYSQL可以使用select user()
                                Oracle可以使用select 1 from dual
                -->
                <heartbeat>select user()</heartbeat>
                <connectionInitSql></connectionInitSql>
                <!-- can have multi write hosts -->
                <!--
                                如果writeHost指定的后端数据库宕机,那么这个writeHost绑定的所有readHost都将不可用。
                                另一方面,由于这个writeHost宕机系统会自动的检测到,并切换到备用的writeHost上去
                -->
                <writeHost host="hostM1" url="jdbc:mysql://192.168.1.105:3400"  user="root"  password="123456">
                        <readHost host="hostM2"  url="jdbc:mysql://192.168.1.105:3401"  user="root"  password="123456" />
                </writeHost>
        </dataHost>

        <dataHost name="host110" maxCon="1000" minCon="10" balance="3"  writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1"  slaveThreshold="100">
                <heartbeat>select user()</heartbeat>
                <connectionInitSql></connectionInitSql>
                <writeHost host="hostM1" url="jdbc:mysql://192.168.1.110:3400"  user="root"  password="123456">
                        <readHost host="hostM2"  url="jdbc:mysql://192.168.1.110:3401"  user="root"  password="123456" />
                </writeHost>
        </dataHost>
        <dataHost name="host110-1" maxCon="1000" minCon="10" balance="3"  writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1"  slaveThreshold="100">
                <heartbeat>select user()</heartbeat>
                <connectionInitSql></connectionInitSql>
                <writeHost host="hostM1" url="jdbc:mysql://192.168.1.110:3403"  user="root"  password="123456">
                </writeHost>
        </dataHost>
</mycat:schema>

           rule.xml文件内容如下

<?xml version="1.0" encoding="UTF-8"?>
<!-- - - Licensed under the Apache License, Version 2.0 (the "License"); 
        - you may not use this file except in compliance with the License. - You 
        may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 
        - - Unless required by applicable law or agreed to in writing, software - 
        distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 
        WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the 
        License for the specific language governing permissions and - limitations 
        under the License. -->
<!DOCTYPE mycat:rule SYSTEM "rule.dtd">
<mycat:rule xmlns:mycat="http://io.mycat/">
        <tableRule name="rule1">
                <rule>
                        <columns>id</columns>
                        <algorithm>func1</algorithm>
                </rule>
        </tableRule>

        <tableRule name="gd-hash">
                <rule>
                        <columns>orderId</columns>
                        <algorithm>gd-hash-func</algorithm>
                </rule>
        </tableRule>

        <tableRule name="sharding-by-date">
                <rule>
                        <columns>createTime</columns>
                        <algorithm>partbyday</algorithm>
                </rule>
        </tableRule>
        <tableRule name="sharding-by-date-day">
                <rule>
                        <columns>createTime</columns>
                        <algorithm>partbyday</algorithm>
                </rule>
        </tableRule>

        <tableRule name="rule2">
                <rule>
                        <columns>user_id</columns>
                        <algorithm>func1</algorithm>
                </rule>
        </tableRule>

        <!--
        分片枚举
        通过在配置文件中配置可能的枚举id,自己配置分片,本规则适用于特定的场景,比如有些业务需要按照省份或区县来做保存,而全国省份区县固定的
        -->
        <tableRule name="sharding-by-intfile">
                <rule>
                        <!--根据这个字段进行分片-->
                        <columns>province</columns>
                        <algorithm>hash-int</algorithm>
                </rule>
        </tableRule>
        <tableRule name="auto-sharding-long">
                <rule>
                        <columns>orderId</columns>
                        <algorithm>rang-long</algorithm>
                </rule>
        </tableRule>
        <tableRule name="mod-long">
                <rule>
                        <columns>order_id</columns>
                        <algorithm>mod-long</algorithm>
                </rule>
        </tableRule>
        <tableRule name="sharding-by-murmur">
                <rule>
                        <columns>orderId</columns>
                        <algorithm>murmur</algorithm>
                </rule>
        </tableRule>
        <tableRule name="crc32slot">
                <rule>
                        <columns>id</columns>
                        <algorithm>crc32slot</algorithm>
                </rule>
        </tableRule>
        <tableRule name="sharding-by-month">
                <rule>
                        <columns>create_time</columns>
                        <algorithm>partbymonth</algorithm>
                </rule>
        </tableRule>
        <tableRule name="latest-month-calldate">
                <rule>
                        <columns>calldate</columns>
                        <algorithm>latestMonth</algorithm>
                </rule>
        </tableRule>

        <tableRule name="auto-sharding-rang-mod">
                <rule>
                        <columns>id</columns>
                        <algorithm>rang-mod</algorithm>
                </rule>
        </tableRule>

        <tableRule name="jch">
                <rule>
                        <columns>orderId</columns>
                        <algorithm>murmur</algorithm>
                </rule>
        </tableRule>

        <tableRule name="jch1">
                <rule>
                        <columns>orderDetailId</columns>
                        <algorithm>murmur</algorithm>
                </rule>
        </tableRule>

        <function name="murmur"
                          class="io.mycat.route.function.PartitionByMurmurHash">
                <property name="seed">0</property><!-- 默认是0 -->
                <property name="count">2</property><!-- 要分片的数据库节点数量,必须指定,否则没法分片 -->
                <property name="virtualBucketTimes">160</property><!-- 一个实际的数据库节点被映射为这么多虚拟节点,默认是160倍,也就是虚拟节点数是物理节点数的160倍 -->
                <!-- <property name="weightMapFile">weightMapFile</property> 节点的权重,没有指定权重的节点默认是1。以properties文件的格式填写,以从0开始到count-1的整数值也就是节点索引为key,以节点权重值为值。所有权重值必须是正整数,否则以1代替 -->
                <property name="bucketMapPath">H:\eclipse-for-xiangxue-2qi-workspace\MycatStudy\Mycat-Server-Mycat-server-1675-release\src\main\resources</property>
                <!-- 用于测试时观察各物理节点与虚拟节点的分布情况,如果指定了这个属性,会把虚拟节点的murmur hash值与物理节点的映射按行输出到这个文件,没有默认值,如果不指定,就不会输出任何东西 -->
        </function>

        <function name="crc32slot"
                          class="io.mycat.route.function.PartitionByCRC32PreSlot">
                <property name="count">2</property><!-- 要分片的数据库节点数量,必须指定,否则没法分片 -->
        </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>
        <function name="rang-long"
                          class="io.mycat.route.function.AutoPartitionByLong">
                <property name="mapFile">autopartition-long.txt</property>
        </function>
        <function name="mod-long" class="io.mycat.route.function.PartitionByMod">
                <!-- how many data nodes -->
                <property name="count">3</property>
        </function>

        <function name="gd-hash-func" class="io.mycat.route.function.PartitionByLong">
                <!--下面的配置,表示,一共分了两个分片段。即1个长度为256,一个长度为768-->
                <!--为分片个数列表-->
                <property name="partitionCount">1,1</property>
                <!--分片对应的长度-->
                <property name="partitionLength">256,768</property>
        </function>


        <function name="func1" class="io.mycat.route.function.PartitionByLong">
                <property name="partitionCount">8</property>
                <property name="partitionLength">128</property>
        </function>
        <function name="latestMonth"
                          class="io.mycat.route.function.LatestMonthPartion">
                <property name="splitOneDay">24</property>
        </function>
        <function name="partbymonth"
                          class="io.mycat.route.function.PartitionByMonth">
                <property name="dateFormat">yyyy-MM-dd</property>
                <property name="sBeginDate">2015-01-01</property>
        </function>


        <function name="partbyday"
                          class="io.mycat.route.function.PartitionByDate">
                <property name="dateFormat">yyyy-MM-dd</property>
                <property name="sNaturalDay">0</property>
                <property name="sBeginDate">2020-05-01</property>
                <!--这个字段从测试结果上看,似乎没有起到什么作用-->
                <property name="sEndDate">2020-05-20</property>
                <!--表示每满10天换个分片区。即2020-05-01到2020-05-10对应0分片段,2020-05-11到2020-05-20对应1分片段-->
                <!--在几个指定的dn节点之间来回切换-->
                <property name="sPartionDay">10</property>
        </function>

        <function name="rang-mod" class="io.mycat.route.function.PartitionByRangeMod">
                <property name="mapFile">partition-range-mod.txt</property>
        </function>

        <function name="jump-consistent-hash" class="io.mycat.route.function.PartitionByJumpConsistentHash">
                <property name="totalBuckets">2</property>
        </function>

            新建一个名称为newRule.xml的文件,注意,一定要是这个名称,文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!-- - - Licensed under the Apache License, Version 2.0 (the "License"); 
        - you may not use this file except in compliance with the License. - You 
        may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 
        - - Unless required by applicable law or agreed to in writing, software - 
        distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 
        WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the 
        License for the specific language governing permissions and - limitations 
        under the License. -->
<!DOCTYPE mycat:rule SYSTEM "rule.dtd">
<mycat:rule xmlns:mycat="http://io.mycat/">
        <tableRule name="rule1">
                <rule>
                        <columns>id</columns>
                        <algorithm>func1</algorithm>
                </rule>
        </tableRule>

        <tableRule name="gd-hash">
                <rule>
                        <columns>orderId</columns>
                        <algorithm>gd-hash-func</algorithm>
                </rule>
        </tableRule>

        <tableRule name="sharding-by-date">
                <rule>
                        <columns>createTime</columns>
                        <algorithm>partbyday</algorithm>
                </rule>
        </tableRule>
        <tableRule name="sharding-by-date-day">
                <rule>
                        <columns>createTime</columns>
                        <algorithm>partbyday</algorithm>
                </rule>
        </tableRule>

        <tableRule name="rule2">
                <rule>
                        <columns>user_id</columns>
                        <algorithm>func1</algorithm>
                </rule>
        </tableRule>

        <!--
        分片枚举
        通过在配置文件中配置可能的枚举id,自己配置分片,本规则适用于特定的场景,比如有些业务需要按照省份或区县来做保存,而全国省份区县固定的
        -->
        <tableRule name="sharding-by-intfile">
                <rule>
                        <!--根据这个字段进行分片-->
                        <columns>province</columns>
                        <algorithm>hash-int</algorithm>
                </rule>
        </tableRule>
        <tableRule name="auto-sharding-long">
                <rule>
                        <columns>orderId</columns>
                        <algorithm>rang-long</algorithm>
                </rule>
        </tableRule>
        <tableRule name="mod-long">
                <rule>
                        <columns>order_id</columns>
                        <algorithm>mod-long</algorithm>
                </rule>
        </tableRule>
        <tableRule name="sharding-by-murmur">
                <rule>
                        <columns>orderId</columns>
                        <algorithm>murmur</algorithm>
                </rule>
        </tableRule>
        <tableRule name="crc32slot">
                <rule>
                        <columns>id</columns>
                        <algorithm>crc32slot</algorithm>
                </rule>
        </tableRule>
        <tableRule name="sharding-by-month">
                <rule>
                        <columns>create_time</columns>
                        <algorithm>partbymonth</algorithm>
                </rule>
        </tableRule>
        <tableRule name="latest-month-calldate">
                <rule>
                        <columns>calldate</columns>
                        <algorithm>latestMonth</algorithm>
                </rule>
        </tableRule>

        <tableRule name="auto-sharding-rang-mod">
                <rule>
                        <columns>id</columns>
                        <algorithm>rang-mod</algorithm>
                </rule>
        </tableRule>

        <tableRule name="jch">
                <rule>
                        <columns>orderId</columns>
                        <algorithm>murmur</algorithm>
                </rule>
        </tableRule>

        <tableRule name="jch1">
                <rule>
                        <columns>orderDetailId</columns>
                        <algorithm>murmur</algorithm>
                </rule>
        </tableRule>

        <function name="murmur"
                          class="io.mycat.route.function.PartitionByMurmurHash">
                <property name="seed">0</property><!-- 默认是0 -->
                <property name="count">3</property><!-- 要分片的数据库节点数量,必须指定,否则没法分片 -->
                <property name="virtualBucketTimes">160</property><!-- 一个实际的数据库节点被映射为这么多虚拟节点,默认是160倍,也就是虚拟节点数是物理节点数的160倍 -->
                <!-- <property name="weightMapFile">weightMapFile</property> 节点的权重,没有指定权重的节点默认是1。以properties文件的格式填写,以从0开始到count-1的整数值也就是节点索引为key,以节点权重值为值。所有权重值必须是正整数,否则以1代替 -->
                <property name="bucketMapPath">/usr/soft/mycat/conf</property>
                <!-- 用于测试时观察各物理节点与虚拟节点的分布情况,如果指定了这个属性,会把虚拟节点的murmur hash值与物理节点的映射按行输出到这个文件,没有默认值,如果不指定,就不会输出任何东西 -->
        </function>

        <function name="crc32slot"
                          class="io.mycat.route.function.PartitionByCRC32PreSlot">
                <property name="count">2</property><!-- 要分片的数据库节点数量,必须指定,否则没法分片 -->
        </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>
        <function name="rang-long"
                          class="io.mycat.route.function.AutoPartitionByLong">
                <property name="mapFile">autopartition-long.txt</property>
        </function>
        <function name="mod-long" class="io.mycat.route.function.PartitionByMod">
                <!-- how many data nodes -->
                <property name="count">3</property>
        </function>

        <function name="gd-hash-func" class="io.mycat.route.function.PartitionByLong">
                <!--下面的配置,表示,一共分了两个分片段。即1个长度为256,一个长度为768-->
                <!--为分片个数列表-->
                <property name="partitionCount">1,1</property>
                <!--分片对应的长度-->
                <property name="partitionLength">256,768</property>
        </function>


        <function name="func1" class="io.mycat.route.function.PartitionByLong">
                <property name="partitionCount">8</property>
                <property name="partitionLength">128</property>
        </function>
        <function name="latestMonth"
                          class="io.mycat.route.function.LatestMonthPartion">
                <property name="splitOneDay">24</property>
        </function>
        <function name="partbymonth"
                          class="io.mycat.route.function.PartitionByMonth">
                <property name="dateFormat">yyyy-MM-dd</property>
                <property name="sBeginDate">2015-01-01</property>
        </function>


        <function name="partbyday"
                          class="io.mycat.route.function.PartitionByDate">
                <property name="dateFormat">yyyy-MM-dd</property>
                <property name="sNaturalDay">0</property>
                <property name="sBeginDate">2020-05-01</property>
                <!--这个字段从测试结果上看,似乎没有起到什么作用-->
                <property name="sEndDate">2020-05-20</property>
                <!--表示每满10天换个分片区。即2020-05-01到2020-05-10对应0分片段,2020-05-11到2020-05-20对应1分片段-->
                <!--在几个指定的dn节点之间来回切换-->
                <property name="sPartionDay">10</property>
        </function>

        <function name="rang-mod" class="io.mycat.route.function.PartitionByRangeMod">
                <property name="mapFile">partition-range-mod.txt</property>
        </function>

        <function name="jump-consistent-hash" class="io.mycat.route.function.PartitionByJumpConsistentHash">
                <property name="totalBuckets">2</property>
        </function>
</mycat:rule>

          在server.xml文件,加入以下内容

        <user name="enjoy">
                <property name="password">123456</property>
                <property name="schemas">enjoydb</property>
        </user>

          在migrateTables.properties文件中加入以下内容

## 要进行数据迁移的表
enjoydb=t_order,t_order_detail

          这里说到的表的脚本还是用的上面提到的数据库脚本

          修改dataMigrate.sh文件。该文件类型是dos的,需要修改为unix类型 查看.sh文件类型:

          在vim编辑模式下,输入命令   

    :set ff 

    :set ff=unix  

         然后,在这个文件中加入一行内容,如下

RUN_CMD="$RUN_CMD -mysqlBin=/usr/bin"

         复制mysql数据的mysql和mysqldump两个执行脚本到/usr/bin目录下。此处一定不要少。

         上面这些准备工作做完成之后,需要在mycat目录下新建一个logs目录,并在logs目录下新建一个mycat.pid文件。

         接下来,进入mycat的bin目录,启动mycat,执行脚本

    ./mycat start

         然后执行 ./dataMigrate.sh 。执行后,会出现下面这个现象,如图

        这里,需要安装这个软件

    yum install  libatomic.so.1

        如果安装完成后,还没有解决,则可以下一个Mysql安装包解压,然后,把dataMigrate.sh文件中的RUN_CMD="$RUN_CMD -mysqlBin的值改为你刚解压的mysql的bin目录即可。

        执行成功的效果,如下

                      

      需要注意事项:

  • jdk不能用openJdk,需要自己安装,配置环境变量
  • 之前的老的schema.xml和rule.xml老配置不能动
  • 新增迁移后的节点新配置newSchema.xml和newRule.xml
  • mycat的bug。在migrateTables.properties中逻辑库的名称不能既有大些又有小写,如:myDB,必须全小写或全大写。
  • 迁移类:DataMigrator

     当迁移完成后,需要把原来的schema.xml和rule.xml两个文件删除掉,然后把newSchema.xml文件名改成schema.xml,把newRule.xml文件名改成rule.xml,重新启动mycat,这样的话,mycat就会以迁移后的规则,进行数据的分片与路由。

     mycat在对mysql中的数据进行迁移,目的地可以是oracle、sqlserver、postgresql、mongo等数据库。如果感觉兴趣的话,可以自己去尝试,这里就不做说明了。

五、mycat控制台

    1、安装

      下载mycat-web,我这里下载的是Mycat-web-1.0-SNAPSHOT-20170102153329-linux.tar.gz版本。

      下载完成后解压,进入mycat-web/WEB-INF/classes目录,编辑mycat.properties

show.period=3000000
zookeeper=192.168.1.105:2181

mycat_warn_mail=        [{"cc"\:"sohudo@mycat.io","index"\:1,"mangerPort"\:"465","smtpHost"\:"smtp.139.com","smtpPassword"\:"123456","smtpProtocol"\:"smtp","smtpUser"\:"agile_
louie@139.com","to"\:"9183838@qq.com"}]
##sql\u4E0A\u7EBF\u76F8\u5173\u914D\u7F6E
sqlonline.server=192.168.1.105
sqlonline.user=enjoy
sqlonline.passwd=123456

         中zookeeper的地址要指定,sqlonline等相关属性是你连接mycat用的。

         然后,进入mycat-web目录下,使用jar命令启动

                   java -jar start.jar 

        启动完成后,访问http://192.168.1.105:8082/mycat/,即可看到页面,如图

六、Mycat分布式事务

      一说到分布式事务,就是一个很大的话题,在Mycat中,是采用XA协议来做的分布式事务。

      至于,XA协议可以参考https://blog.csdn.net/weixin_32822759/article/details/106585407,我对XA协议的一点认识。

      正是由于Mycat已经基于XA协议实现了分布事务,所以我们在使用Mycat作数据源,使用事务时,还是和普通本地事务一样,直接使用@Transactional即可。

      那么在这里,思考一下,为什么只需要使用@Transaction即可呢?

      其实原因很简单,因为在使用mycat的情况下,应用程序实际连接是Mycat,而非数据库。而在spring的事务源码中,有这样一句代码 con.setAutoCommit(false);如下图

@Override
	protected void doBegin(Object transaction, TransactionDefinition definition) {
		DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
		Connection con = null;

		try {
			//如果没有数据库连接
			if (!txObject.hasConnectionHolder() ||
					txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
				//从连接池里面获取连接
				Connection newCon = obtainDataSource().getConnection();
				if (logger.isDebugEnabled()) {
					logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
				}
				//把连接包装成ConnectionHolder,然后设置到事务对象中
				txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
			}

			txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
			con = txObject.getConnectionHolder().getConnection();

			//从数据库连接中获取隔离级别
			Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
			txObject.setPreviousIsolationLevel(previousIsolationLevel);

			// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
			// so we don't want to do it unnecessarily (for example if we've explicitly
			// configured the connection pool to set it already).
			if (con.getAutoCommit()) {
				txObject.setMustRestoreAutoCommit(true);
				if (logger.isDebugEnabled()) {
					logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
				}
				//关闭连接的自动提交,其实这步就是开启了事务
				con.setAutoCommit(false);
			}

			//设置只读事务 从这一点设置的时间点开始(时间点a)到这个事务结束的过程中,其他事务所提交的数据,该事务将看不见!
			//设置只读事务就是告诉数据库,我这个事务内没有新增,修改,删除操作只有查询操作,不需要数据库锁等操作,减少数据库压力
			prepareTransactionalConnection(con, definition);

			//自己提交关闭了,就说明已经开启事务了,事务是活动的
			txObject.getConnectionHolder().setTransactionActive(true);

			int timeout = determineTimeout(definition);
			if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
				txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
			}

			// Bind the connection holder to the thread.
			if (txObject.isNewConnectionHolder()) {
				//如果是新创建的事务,则建立当前线程和数据库连接的关系
				TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
			}
		}

		catch (Throwable ex) {
			if (txObject.isNewConnectionHolder()) {
				DataSourceUtils.releaseConnection(con, obtainDataSource());
				txObject.setConnectionHolder(null, false);
			}
			throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
		}
	}

        这里的连接此时对应的是Mycat的连接,当执行这一条语句后,Mycat就会向它所管理的一些数据库节点发送关闭自动提交的指令。  然后,mycat再向数据发送相关的XA语句了,从而完成事务的操作。在这里Mycat就充当了TM的角色了

这里需要指出的是,在mycat中是采用XA弱事务的。也就是说,Mycat中并不会像atomikos那样会写prepare日志,因为在Mycat看来,它认为如果第一阶段的都成功了,那么第二阶段出问题的可能性是很低的。

        

七、其它补充点

     在使用mycat的过程中,如果遇到了表关联查询的话,则需要注意,

  • 目前mycat是不支持跨库三张以上的表的关联查询。而mycat在跨库查询时,查询速度本身就很慢。
  • 在mycat进行扩容的时候,最好是将mycat停止掉,再运行dataMigrate.sh。但是如果不想停止,也是可以做到的,就是使用前面提到的,使用锁表的方式,待数据迁移完成后,再将表解锁。这样做的目的,就是为了,在迁移的过程中,避免会数据更新到库中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值