MyCat基本使用

309 阅读17分钟

一、概述

官方文档,是中文的:www.mycat.io/document/my…

Mycat是一个开源数据库中间件,是一个实现了MySQL协议的的数据库中间件服务器,我们可以把它看作是一个数据库代理,用MySQL客户端工具和命令行访问Mycat,而Mycat再使用用MySQL原生(Native)协议与多个MySQL服务器通信,也可以用JDBC协议与大多数主流数据库服务器通信,包括SQL Server、Oracle、DB2、PostgreSQL等主流数据库,也支持MongoDB这种新型NoSQL方式的存储,未来还会支持更多类型的存储;

一般地,Mycat主要用于代理MySQL数据库,虽然它也支持去访问其他类型的数据库;Mycat的默认端口是8066,一般地,我们可以使用常见的对象映射框架比如MyBatis操作Mycat。

简单来说:

  • 一个彻底开源的,面向企业应用开发的大数据库集群
  • 支持事务、ACID、可以替代MySQL的加强版数据库
  • 一个可以视为MySQL集群的企业级数据库,用来替代昂贵的Oracle集群
  • 一个融合内存缓存技术、NoSQL技术、HDFS大数据的新型SQL Server
  • 结合传统数据库和新型分布式数据仓库的新一代企业级数据库产品
  • 一个新颖的数据库中间件产品

Mycat主要能做什么

(1)数据库的读写分离

通过Mycat可以自动实现写数据时操作主数据库,读数据时操作从数据库,这样能有效地减轻数据库压力,也能减轻IO压力。

实现读写分离,当主出现故障后,Mycat自动切换到另一个主上,进而提供高可用的数据库服务,当然我需要部署多主多从的模式

在这里插入图片描述 如果有了Mycat,客户端直接连接Mycat,可以实现读写分离,如果主出现问题,会自动切换到从服务器上

(2)数据库分库分表

分库分表指的是对数据库数据的拆分,分为两种:水平拆分和垂直拆分

  1. 水平切分(横向切分) 根据表中数据的逻辑关系,将同一个表中的数据按照某种条件拆分到多台数据库服务器上面
  2. 垂直切分(纵向切分) 按照不同的表来切分到不同的数据库服务器之上

MyCAT通过定义表的分片规则来实现分片,每个表格可以捆绑一个分片规则,每个分片规则指定一个分片字段并绑定一个函数,来实现动态分片算法(后面会讲到) 在这里插入图片描述

  • Schema:逻辑库,与MySQL中的Database(数据库)对应,一个逻辑库中定义了所包括的Table。

  • Table:逻辑表,即物理数据库中存储的某一张表,与传统数据库不同,这里的表格需要声明其所存储的逻辑数据节点DataNode。在此可以指定表的分片规则。

  • DataNode:MyCAT的逻辑数据节点,是存放table的具体物理节点,也称之为分片节点,通过DataSource来关联到后端某个具体数据库上

  • DataSource:定义某个物理库的访问地址,用于捆绑到Datanode上

性能有瓶颈了,可以读写分离 数据库容量有瓶颈了,可以分库分表

二、环境搭建

下载地址:dl.mycat.io/1.6-RELEASE…

将下载好的Mycat直接上传到Linux上

1. 解压

Java语言开发的,直接解压即可使用

tar -zxvf Mycat-server-1.6-RELEASE-20161028204710-linux.tar.gz -C /usr/local

2. 常用命令

  • Mycat启动 切换到mycat的bin路径下,执行

    ./mycat start
    
  • Mycat关闭 切换到mycat的bin路径下,执行

    ./mycat stop
    
  • Mycat命令行 登录mycat命令行,使用mysql的命令行工具来操作的,在mysql的bin路径下执行:

    • mycat默认数据访问端口是8066
    ./mysql -umycat -p -P8066 -h127.0.0.1
    

三、配置文件详解

1. server.xml

主要用于配置mycat需要的服务器信息,常用的配置有:

  • 配置序列生成方式
  • 配置mycat逻辑数据库
  • 配置mycat的访问账户和密码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:server SYSTEM "server.dtd">
<mycat:server xmlns:mycat="http://io.mycat/">
	<system>
	<property name="useSqlStat">0</property>  <!-- 1为开启实时统计、0为关闭 -->
	<property name="useGlobleTableCheck">0</property>  <!-- 1为开启全加班一致性检测、0为关闭 -->

		<property name="sequnceHandlerType">2</property><!-- 主键的生成策略 -->
      	<!--<property name="useCompression">1</property>--> <!--1为开启mysql压缩协议-->
     	<!--<property name="fakeMySQLVersion">5.6.20</property>--> <!--设置模拟的MySQL版本号-->
	  	<!--<property name="processorBufferChunk">40960</property> -->
	  	<!-- 
		    <property name="processors">1</property> 
		    <property name="processorExecutor">32</property> 
	   	-->
		<!--默认为type 0: DirectByteBufferPool | type 1 ByteBufferArena-->
		<property name="processorBufferPoolType">0</property>
		<!--默认是65535 64K 用于sql解析时最大文本长度 -->
		<!--<property name="maxStringLiteralLength">65535</property>-->
		<!--<property name="sequnceHandlerType">0</property>-->
		<!--<property name="backSocketNoDelay">1</property>-->
		<!--<property name="frontSocketNoDelay">1</property>-->
		<!--<property name="processorExecutor">16</property>-->
		
		<property name="serverPort">8066</property> <property name="managerPort">9066</property> <!--设置端口号-->
		<!--
			<property name="idleTimeout">300000</property> <property name="bindIp">0.0.0.0</property> 
			<property name="frontWriteQueueSize">4096</property> <property name="processors">32</property> -->
		<!--分布式事务开关,0为不过滤分布式事务,1为过滤分布式事务(如果分布式事务内只涉及全局表,则不过滤),2为不过滤分布式事务,但是记录分布式事务日志-->
		<property name="handleDistributedTransactions">0</property>
		
		<!--
			off heap for merge/order/group/limit      1开启   0关闭
		-->
		<property name="useOffHeapForMerge">1</property>

		<!--单位为m-->
		<property name="memoryPageSize">1m</property>

		<!--单位为k-->
		<property name="spillsFileBufferSize">1k</property>

		<property name="useStreamOutput">0</property>

		<!--单位为m-->
		<property name="systemReserveMemorySize">384m</property>

		<!--是否采用zookeeper协调切换  -->
		<property name="useZKSwitch">true</property>
	</system>
	
	<!-- 全局SQL防火墙设置 -->
	<firewall> 
	   <whitehost><!--白名单-->
	      <host host="127.0.0.1" user="mycat"/><!--允许ip为host的主机使用user进行访问-->
	   </whitehost>
       <blacklist check="false"><!--黑名单-->
       </blacklist>
	</firewall>

	<user name="root"><!--用户名-->
		<property name="password">123456</property><!--密码-->
		<property name="schemas">mycatdb</property><!--逻辑库的名字-->
		
		<!-- 表级 DML 权限设置 -->		
		<privileges check="false">
			<!--每位分别代表:增、删、改、查,1表示可用,0表示不可用-->
			<schema name="TESTDB" dml="0110" >
				<table name="tb01" dml="0000"></table>
				<table name="tb02" dml="1111"></table>
			</schema>
		</privileges>		
	</user>

	<user name="user"><!--用户名-->
		<property name="password">user</property>
		<property name="schemas">mycatdb</property>
		<property name="readOnly">true</property><!--只读-->
	</user>

</mycat:server>

(1)sequnceHandlerType

指定使用Mycat全局序列的类型:

  • 0为本地文件方式
  • 1为数据库方式
  • 2为时间戳序列方式

2. schema.xml

用于配置的逻辑数据库的映射、表、分片规则、数据结点及真实的数据库信息;常用的配置有:

  • 配置逻辑库映射
  • 配置垂直切分的表
  • 配置真实的数据库
  • 配置读写结点
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">

	<!--配置逻辑库
		name:逻辑库的名字,一个schema是一个逻辑库
		checkSQLschema:是否检查sql语句语法规则
		sqlMaxLimit:最大分页数量,每个查询语句最多返回的条数(100条已经很多了)
	-->
	<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100">
		<!-- 逻辑表,将三个分片节点dn1,dn2,dn3整合到同一个逻辑表中
			name:逻辑表的名字
			dataNode:该逻辑表下的分片节点
			rule:水平分表的主键规则
		-->
		<table name="travelrecord" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />

		<!-- global table is auto cloned to all defined data nodes ,so can join
			with any table whose sharding node is in the same data node -->
		<table name="company" primaryKey="ID" type="global" dataNode="dn1,dn2,dn3" />
		<table name="goods" primaryKey="ID" type="global" dataNode="dn1,dn2" />
		<!-- random sharding using mod sharind rule -->
		<table name="hotnews" primaryKey="ID" autoIncrement="true" dataNode="dn1,dn2,dn3"
			   rule="mod-long" />
		<!-- <table name="dual" primaryKey="ID" dataNode="dnx,dnoracle2" type="global"
			needAddLimit="false"/> <table name="worker" primaryKey="ID" dataNode="jdbc_dn1,jdbc_dn2,jdbc_dn3"
			rule="mod-long" /> -->
		<table name="employee" primaryKey="ID" dataNode="dn1,dn2"
			   rule="sharding-by-intfile" />
		<table name="customer" primaryKey="ID" dataNode="dn1,dn2"
			   rule="sharding-by-intfile">
			<childTable name="orders" primaryKey="ID" joinKey="customer_id"
						parentKey="id">
				<childTable name="order_items" joinKey="order_id"
							parentKey="id" />
			</childTable>
			<childTable name="customer_addr" primaryKey="ID" joinKey="customer_id"
						parentKey="id" />
		</table>
		<!-- <table name="oc_call" primaryKey="ID" dataNode="dn1$0-743" rule="latest-month-calldate"
			/> -->
	</schema>
	
	<!--定义Mycat中的数据节点/分片,一个dataNode标签就是一个独立的数据分片。通俗理解,一个分片就是一个物理数据库
		name:定义数据节点的名字,这个名字需要是唯一的
		dataHost:用于定义该分片属于哪个数据库实例的,属性值是引用dataHost标签上定义的name属性
		database:用于对应真实的数据库名,必须是真实存在的
	-->
	<!-- <dataNode name="dn1$0-743" dataHost="localhost1" database="db$0-743"/> -->
	<dataNode name="dn1" dataHost="localhost1" database="db1" />
	<dataNode name="dn2" dataHost="localhost1" database="db2" />
	<dataNode name="dn3" dataHost="localhost1" database="db3" />

	<!--定义具体的数据库实例、读写分离配置和心跳语句;
		name:物理库的名字
		maxCon/minCon:最大/最小连接数量
		balance:负载均衡策略,详见后面
		writeType:已过时,1.6版本就不用了
		dbType:数据库类型
		dbDriver:数据库驱动类型,只有dbType为mysql时,才可以使用native,表示原生驱动;
			其它数据库类型这里只能写jdbc
		switchType:故障切换类型,详见后面
		slaveThreshold:最多拥有多少个子节点
	-->
	<dataHost name="localhost1" maxCon="1000" minCon="10" balance="0"
			  writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
		<!--心跳机制,判断服务器是否正常运行,需要配合switchType一起进行配置-->
		<heartbeat>select user()</heartbeat>
		<!--写节点,主从复制中的主服务器-->
		<writeHost host="hostM1" url="localhost:3306" user="root"
				   password="123456">
			<!--从节点,主从复制中的从服务器-->
			<readHost host="hostS2" url="192.168.1.200:3306" user="root" password="xxx" />
		</writeHost>
		<writeHost host="hostS1" url="localhost:3316" user="root"
				   password="123456" />
	</dataHost>
</mycat:schema>

(1)dataHost配置详解

balance (dataHost 标签中)属性是指负载均衡类型,目前的取值有4种(推荐设置为1):

  • balance="0":不开启读写分离机制,所有读操作都发送到当前可用的writeHost上;
  • balance="1":全部的readHost与stand by writeHost参与select语句的负载均衡,简单的说,当双主双从模式(M1->S1,M2->S2,并且M1与 M2互为主备),正常情况下,M2、S1、S2都参与select语句的负载均衡。
  • balance="2":所有读操作都随机的在writeHost、readhost上分发
  • balance="3":所有读请求随机的分发到wiriterHost对应的readhost执行,writerHost不负担读压力

switchType属性,用于指定主服务器发生故障后的切换类型

  • -1:表示不自动切换
  • 1:默认值,自动切换(推荐)
  • 2:基于MySQL主从同步的状态决定是否切换
  • 3:基于MySQL galary cluster的切换机制(适合集群)(1.4.1)

通常情况下,我们MySQL采用双主双从的模式下,switchType为1即可。因为双主从模式下,主从同步关系很复杂,不能根据MySQL的状态来切换。只需要在一个主出问题后,切换到另外的主。

heartbeat标签

用于和后端数据库进行心跳检查的语句,检测MySQL数据库是否正常运行

  • 当switchType为1时,mysql心跳检查语句是select user()
  • 当switchType为2时,mysql心跳检查语句是show slave status
  • 当switchType为3时,mysql心跳检查语句是show status like 'wsrep%'

writeHost与readHost标签

这两个标签都指定后端数据库的相关配置给mycat,用于实例化后端连接池。唯一不同的是,writeHost指定写实例、readHost指定读实例,组合这些读写实例来满足系统的要求。

在一个dataHost内可以定义多个writeHost和readHost。但是,如果writeHost指定的后端数据库宕机,那么这个writeHost绑定的所有readHost都将不可用。另一方面,由于这个writeHost宕机系统会自动的检测到,并切换到备用的writeHost上去。

3. rule.xml

用于定义分片规则; 文件中有两种标签:

  • <tableRule>:定义分片规则
    • <columns>:根据哪个列(表字段)进行分片计算
    • <algorithm>:function标签中定义的分片函数
  • <function>:指定相应的类和参数
    • name属性:指定分片函数的名字
    • class属性:完成该功能对应的类

(1)分片枚举

通过在配置文件中配置可能的枚举 id,自己配置分片,本规则适用于特定的场景,比如有些业务需要按照省份或区县来做保存,而全国省份区县固定的,这类业务使用本条规则,配置如下:

<tableRule name="sharding-by-intfile">
	<rule>
		<columns>user_id</columns>
		<algorithm>hash-int</algorithm>
	</rule>
</tableRule>
<function name="hash-int" class="io.mycat.route.function.PartitionByFileMap">
	<property name="mapFile">partition-hash-int.txt</property>
	<property name="type">0</property>
	<property name="defaultNode">0</property>
</function>

partition-hash-int.txt 配置:

10000=0
10010=1
DEFAULT_NODE=1

其中分片函数配置中:

  • mapFile:标识配置文件名称,配置文件使用的是键值对(类似properties文件)
  • type:默认值为 0,0 表示 Integer,非零表示 String
  • defaultNode:默认节点,小于 0 表示不设置默认节点,大于等于 0 表示设置默认节点 默认节点的作用:枚举分片时,如果碰到不识别的枚举值,就让它路由到默认节点,如果不配置默认节点(defaultNode 值小于 0 表示不配置默认节点),碰到不识别的枚举值就会报错,can’t find datanode for sharding column
  • 配置文件作用:
    • 枚举各个id,并指定对应的节点,所有的节点配置都是从 0 开始,及 0 代表节点 1

(2)固定分片 hash 算法

本条规则类似于十进制的求模运算,区别在于是二进制的操作,是取 id 的二进制低 10 位,即 id 二进制&1111111111。

此算法的优点在于如果按照 10 进制取模运算,在连续插入 1-10 时候 1-10 会被分到 1-10 个分片,增大了插入的事务控制难度,而此算法根据二进制则可能会分到连续的分片,减少插入事务事务控制难度。

<tableRule name="rule1">
	<rule>
		<columns>id</columns>
		<algorithm>func1</algorithm>
	</rule>
</tableRule>
<function name="func1" class="io.mycat.route.function.PartitionByLong">
	<property name="partitionCount">2,1</property>
	<property name="partitionLength">256,512</property>
</function>
  • partitionCount:分片个数列表

  • partitionLength:分片范围列表

  • 上面的配置表示:共分三个区,两个分区为25%(256/1024),一个分区为50%(512/1024),

    • 表字段id的hash值的二进制低10位,如果在0-255的范围内,被分到第一个分区,如果在256-511的范围内,被分到第二个分区,如果在252-1023的范围内,被分到第三个分区
  • 约 束:

    • 分区长度:默认为最大 2^n=1024,即最大支持 1024 分区
    • count,length 两个数组的长度必须是一致的。
    • 1024 = sum((count[i]*length[i]))

(3)范围约定

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

<tableRule name="auto-sharding-long">
	<rule>
		<columns>id</columns>
		<algorithm>rang-long</algorithm>
	</rule>
</tableRule>
<function name="rang-long" class="io.mycat.route.function.AutoPartitionByLong">
	<property name="mapFile">autopartition-long.txt</property>
	<property name="defaultNode">0</property>
</function>
  • mapFile:代表配置文件路径

  • defaultNode:超过范围后的默认节点。

  • 配置文件:所有的节点配置都是从 0 开始,及 0 代表节点 1,此配置非常简单,即预先制定可能的 id 范围到某个分片

    0-500M=0
    500M-1000M=1
    1000M-1500M=2
    

    0-10000000=0
    10000001-20000000=1
    

(4)求模法

此规则为对分片字段求摸运算。

<tableRule name="mod-long">
	<rule>
		<columns>id</columns>
		<algorithm>mod-long</algorithm>
	</rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
	<!-- how many data nodes -->
	<property name="count">3</property>
</function>

此种配置非常明确,即根据id与count(你的结点数)进行十进制求模运算,相比固定分片 hash,此种在批量插入时需要切换数据源,增大事务一致性难度。

(5)按日期(天)分片

<tableRule name="sharding-by-date">
	<rule>
		<columns>create_time</columns>
		<algorithm>sharding-by-date</algorithm>
	</rule>
</tableRule>
<function name="sharding-by-date" class="io.mycat.route.function.PartitionByDate">
	<property name="dateFormat">yyyy-MM-dd</property>
	<property name="sBeginDate">2014-01-01</property>
	<property name="sPartionDay">10</property>
</function>
  • dateFormat:日期格式
  • sBeginDate:开始日期
  • sPartionDay:分区天数(步长),上面的配置表示:从开始日期算起,分隔 10 天一个分区 例如:2014-01-01分区为0,2014-01-02分区为0,2014-01-10分区为0,2014-01-11分区为1……依此类推

(6)取模范围约束

此种规则是取模运算与范围约束的结合,主要为了后续数据迁移做准备,即可以自主决定取模后数据的节点分布。

<tableRule name="sharding-by-pattern">
	<rule>
		<columns>user_id</columns>
		<algorithm>sharding-by-pattern</algorithm>
	</rule>
</tableRule>
<function name="sharding-by-pattern" class="io.mycat.route.function.PartitionByPattern">
	<property name="patternValue">256</property>
	<property name="defaultNode">2</property>
	<property name="mapFile">partition-pattern.txt</property>
</function>

partition-pattern.txt:

###### first host configuration
1-32=0
33-64=1
65-96=2
97-128=3
######## second host configuration
129-160=4
161-192=5
193-224=6
225-256=7
0-0=7
  • patternValue:即求模基数
  • defaoultNode:默认节点,如果没有配置,则默认为第0个节点
  • mapFile 配置文件路径
    • 配置文件中,1-32 即代表 id%256 后分布的范围,如果在 1-32 则在分区 1,其他类推,如果 id 非数据,则会分配在 defaoultNode 默认节点

(7) 应用指定

此规则是在运行阶段有应用自主决定路由到那个分片。

<tableRule name="sharding-by-substring">
	<rule>
		<columns>user_id</columns>
		<algorithm>sharding-by-substring</algorithm>
	</rule>
</tableRule>
<function name="sharding-by-substring" class="io.mycat.route.function.PartitionDirectBySubString">
	<property name="startIndex">0</property><!-- zero-based -->
	<property name="size">2</property>
	<property name="partitionCount">8</property>
	<property name="defaultPartition">0</property>
</function>

此方法为直接根据字符子串(必须是数字)计算分区号(由应用传递参数,显式指定分区号)。 例如:id为05-100000002 在上面的配置中,根据 id ,从 startIndex=0 开始,截取 size=2 位字符串数字即 05,05 就是获取的分区,如果没传默认分配到 defaultPartition;如果截取出的字符超过了partitionCount,则分配到defaultPartition

(8)一致性 hash

一致性 hash 预算有效解决了分布式数据的扩容问题。

<tableRule name="sharding-by-murmur">
	<rule>
		<columns>user_id</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">/etc/mycat/bucketMapPath</property>
	用于测试时观察各物理节点与虚拟节点的分布情况,如果指定了这个属性,会把虚拟节点的 murmur hash 值与物理节
	点的映射按行输出到这个文件,没有默认值,如果不指定,就不会输出任何东西 -->
</function>

其它分片规则详见官方文档10.5节:www.mycat.io/document/my…

四、读写分离

先只做读写分离,不做分库分表,Mycat只是帮我们转发一下请求,读转发到从库,写转发到主库。

1. 配置server.xml

  1. 对于读写分离而言,是不需要考虑主键生成方式的,也就是不需要配置全局序列号的,故sequnceHandlerType参数设置为1

    <property name="sequnceHandlerType">1</property>
    
  2. 配置连接mycat的用户名和密码,以及对应的逻辑库

    <user name="mycat">
        <property name="password">123456</property>
        <property name="schemas">mycatdb</property>
    </user>
    

2. 配置schema.xml

  1. 由于只做读写分离,则schema标签里面不用配置table

    <schema name="mycatdb" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"/>
    
  2. 配置dataNode

    <dataNode name="dn1" dataHost="localhost1" database="test" />
    
  3. 配置dataHost,指定writeHost和readHost

  • 一主三从dataHost配置:

    <dataHost name="localhost1" maxCon="1000" minCon="10" balance="1"
    		  writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
    	
    	<heartbeat>select user()</heartbeat>
    	
    	<!--配置写数据库(主库) 一主三从的读写分离配置 -->
    	<writeHost host="hostM3307" url="localhost:3307" user="root" password="123456">
    		<!--配置写数据库下的读数据库(从库)-->
    		<readHost host="hostS3308" url="localhost:3308" user="root" password="123456" />
    		<readHost host="hostS3309" url="localhost:3309" user="root" password="123456" />
    		<readHost host="hostS3310" url="localhost:3310" user="root" password="123456" />
    	</writeHost>
    	
    </dataHost>
    
  • 双主双从dataHost配置:

    <dataHost name="localhost1" maxCon="1000" minCon="10" balance="1"
    		  writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
    	
    	<!--配置心跳语句 -->
    	<heartbeat>select user()</heartbeat>
    	
    	<!--配置写数据库(主库) 双主双从的读写分离配置 -->
    	<writeHost host="hostM3307" url="localhost:3307" user="root" password="123456">
    		<!--配置写数据库下的读数据库(从库)-->
    		<readHost host="hostS3308" url="localhost:3308" user="root" password="123456" />
    		<readHost host="hostS3309" url="localhost:3309" user="root" password="123456" />
    	</writeHost>
    	
    	<writeHost host="hostM3308" url="localhost:3308" user="root" password="123456">
    		<!--配置写数据库下的读数据库(从库)-->
    		<readHost host="hostS3307" url="localhost:3307" user="root" password="123456" />
    		<readHost host="hostS3310" url="localhost:3310" user="root" password="123456" />
    	</writeHost>
    	
    </dataHost>
    

五、分库分表

不管是何种方式的切分,主键生成必须交给MyCat实现

1. 水平

(1)配置server.xml

指定主键的生成策略

<!--配置数据库的主键怎么生成,0为本地文件方式,1为数据库方式,2为时间戳序列方式-->
<property name="sequnceHandlerType">0</property>

(2)配置schema.xml

指定逻辑库,分片结点,结点主机等

  • 要实现分库分表,那么就需要在<schema>标签下配置表了,现在是水平切分,表示要对哪张表进行切分
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="mycatdb" checkSQLschema="false" sqlMaxLimit="100">
    <!-- 要实现分库分表,那么就需要在<schema>标签下配置表了,现在是水平切分,表示要对哪张表进行切分 -->
    <table name="orders" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="mod-long" />
</schema>

<!--配置真实的数据库名称 test -->
<dataNode name="dn1" dataHost="localhost1" database="test01" />
<dataNode name="dn2" dataHost="localhost1" database="test02" />
<dataNode name="dn3" dataHost="localhost1" database="test03" />

<!--读写分离前面已经说过了,这里就只写了双主双从的情况-->
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1"
		  writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
    <heartbeat>select user()</heartbeat>
    <writeHost host="hostM3307" url="localhost:3307" user="root" password="123456">
        <readHost host="hostS3308" url="localhost:3308" user="root" password="123456" />
        <readHost host="hostS3309" url="localhost:3309" user="root" password="123456" />
    </writeHost>
    <writeHost host="hostM3308" url="localhost:3308" user="root" password="123456">
        <readHost host="hostS3307" url="localhost:3307" user="root" password="123456" />
        <readHost host="hostS3310" url="localhost:3310" user="root" password="123456" />
    </writeHost>
</dataHost>
</mycat:schema>

(2)配置rule.xml

根据实际情况选择水平拆分的规则,这里使用的是求模法:

<tableRule name="mod-long">
	<rule>
		<columns>id</columns>
		<algorithm>mod-long</algorithm>
	</rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
	<!-- how many data nodes -->
	<property name="count">3</property>
</function>

2. 垂直

垂直切分带来的价值:可以屏蔽掉多数据源的问题,只需要一个统一入口mycat就可以操作下面的多个数据库

(1)配置server.xml

指定主键生成策略

<!--配置数据库的主键怎么生成,0为本地文件方式,1为数据库方式,2为时间戳序列方式-->
<property name="sequnceHandlerType">0</property>

(2)配置schema.xml

也同样需要配置table标签,水平切分是要对哪张表进行切分就配置那张表;垂直切分需要将数据库中的所有表都配置上,指定不同的datanode

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">

	<schema name="mycatdb" checkSQLschema="false" sqlMaxLimit="100">
		
		<!-- 要实现分库分表,那么就需要在<schema>标签下配置表了,现在是垂直切分 -->
		
		<!--需求:整个P2P平台的数据库(p2p)进行垂直切分,分为前台数据库(p2p-web)、后台数据库(p2p-admin)、红包数据库(p2p-red)-->
		
		<!--P2P前台数据库-->
		<table name="orders" primaryKey="id" autoIncrement="true" dataNode="dn1"/>
		<table name="users" primaryKey="id" autoIncrement="true" dataNode="dn1"/>
		
		<!--P2P后台数据库-->
		<table name="products" primaryKey="id" autoIncrement="true" dataNode="dn2"/>
		<table name="news" primaryKey="id" autoIncrement="true" dataNode="dn2"/>
		
		<!--P2P红包数据库-->
		<table name="reds" primaryKey="id" autoIncrement="true" dataNode="dn3"/>
		<table name="redType" primaryKey="id" autoIncrement="true" dataNode="dn3"/>
	</schema>

	<!--配置真实的数据库名称 testdb01 -->
	<dataNode name="dn1" dataHost="localhost1" database="p2p-web" />
	<dataNode name="dn2" dataHost="localhost1" database="p2p-admin" />
	<dataNode name="dn3" dataHost="localhost1" database="p2p-red" />

	<!--配置具体的数据库连接信息、读写分离、心跳语句 -->
	<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1"
			  writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
		
		<!--配置心跳语句 -->
		<heartbeat>select user()</heartbeat>
		
		<!--配置写数据库(主库) 双主双从的读写分离配置 -->
		<writeHost host="hostM3307" url="localhost:3307" user="root" password="123456">
			<!--配置写数据库下的读数据库(从库)-->
			<readHost host="hostS3308" url="localhost:3308" user="root" password="123456" />
			<readHost host="hostS3309" url="localhost:3309" user="root" password="123456" />
		</writeHost>
		
		<writeHost host="hostM3308" url="localhost:3308" user="root" password="123456">
			<!--配置写数据库下的读数据库(从库)-->
			<readHost host="hostS3307" url="localhost:3307" user="root" password="123456" />
			<readHost host="hostS3310" url="localhost:3310" user="root" password="123456" />
		</writeHost>
		
	</dataHost>
</mycat:schema>

六、Mycat全局序列号

在实现分库分表的情况下,数据库自增主键已无法保证自增主键的全局唯一。为此,MyCat 提供了全局 sequence,并且提供了包含本地配置和数据库配置等多种实现方式。

1. 本地文件方式

在servler.xml文件中配置<property name="sequnceHandlerType">0</property>,即为本地文件方式,在conf/sequence_conf.properties中维护主键信息,每当插入数据时,从该文件中取出主键信息,并修改该文件的值。

conf/sequence_conf.properties文件默认提供的主键信息:

  • HISIDS:表示使用过的历史分段(一般无特殊需要可不配置)
  • MINID:表示最小 ID 值
  • MAXID:表示最大ID 值
  • CURID(currentId):当前主键id,下回插入时,插入的主键为:10001
#default global sequence
GLOBAL.HISIDS=
GLOBAL.MINID=10001
GLOBAL.MAXID=200000
GLOBAL.CURID=10000
  • 如果想要每个表生成的主键连续,可以在sequence_conf.properties配置当前表的生成值,一般将Global替换为自己对应表的前缀即可。

  • 取主键值的时候通过next value for MYCATSEQ_XXXX获取

例如插入语句:

insert into tbl_user (id, name) values (next value for MYCATSEQ_Global, '张三');
  • 缺点:当 MyCAT 重新发布后,配置文件中的 sequence 会恢复到初始值。
  • 优点:本地加载,读取速度较快。

2. 时间戳方式

在servler.xml文件中配置<property name="sequnceHandlerType">2</property>,即为时间戳方式

ID= 64 位二进制 (42(毫秒)+5(机器 ID)+5(业务编码)+12(重复累加) 换算成十进制为 18 位数的 long 类型,每毫秒可以并发 12 位二进制的累加。

这种方式,需要将主键设置为bigint或者varchar(长度一般20)类型,插入时,不指定主键,MyCat会自动生成主键

3. 数据库方式

在servler.xml文件中配置<property name="sequnceHandlerType">1</property>,表示使用数据库方式生成sequence

(1)原理

在数据库中建立一张表,存放 sequence 名称(name),sequence 当前值(current_value),步长(increment int 类型每次读取多少个 sequence,假设为 K)等信息;

这里是数据库方式生成主键ID,不是采用数据库的主键自增,而是mycat利用mysql数据库生成一个主键,使用的是数据库的函数,函数需要自定义

  1. 当初次使用该 sequence 时,根据传入的 sequence 名称,从数据库这张表中读取 current_value,和 increment 到 MyCat 中,并将数据库中的 current_value 设置为原 current_value 值+increment 值。
  2. MyCat 将读取到 current_value+increment 作为本次要使用的 sequence 值,下次使用时,自动加 1,当使用 increment 次后,执行步骤 1 相同的操作。
  3. MyCat 负责维护这张表,用到哪些 sequence,只需要在这张表中插入一条记录即可。若某次读取的 sequence 没有用完,系统就停掉了,则这次读取的 sequence 剩余值不会再使用。

(2)创建表与函数

在数据库中创建一张表,插入一条数据,创建三个函数:

# 创建存放 sequence 的表
DROP TABLE IF EXISTS MYCAT_SEQUENCE;

# name sequence 名称
# current_value 当前 value
# increment 增长步长。 可理解为 mycat 在数据库中一次读取多少个 sequence放到内存中。 当这些用完后, 下次再从数据库中读取。
CREATE TABLE MYCAT_SEQUENCE (name VARCHAR(50) NOT NULL,current_value INT NOT NULL,increment INT NOT NULL DEFAULT 1,
PRIMARY KEY(name)) ENGINE=InnoDB default charset=utf8;

# 插入一条 sequence
INSERT INTO MYCAT_SEQUENCE(name,current_value,increment) VALUES ("GLOBAL", 0, 100);

# 获取当前 sequence 的值 (返回当前值,增量)
DROP FUNCTION IF EXISTS mycat_seq_currval;
DELIMITER
CREATE FUNCTION mycat_seq_currval(seq_name VARCHAR(50)) RETURNS varchar(64) CHARSET utf8
DETERMINISTIC
BEGIN
DECLARE retval VARCHAR(64);
SET retval="-999999999,null";
SELECT concat(CAST(current_value AS CHAR),",",CAST(increment AS CHAR)) INTO retval FROM MYCAT_SEQUENCE WHERE name = seq_name;
RETURN retval;
END
DELIMITER;

# 设置 sequence 值
DROP FUNCTION IF EXISTS mycat_seq_setval;
DELIMITER
CREATE FUNCTION mycat_seq_setval(seq_name VARCHAR(50),value INTEGER) RETURNS varchar(64) CHARSET utf8
DETERMINISTIC
BEGIN
UPDATE MYCAT_SEQUENCE
SET current_value = value
WHERE name = seq_name;
RETURN mycat_seq_currval(seq_name);
END
DELIMITER;

#  获取下一个 sequence 值
DROP FUNCTION IF EXISTS mycat_seq_nextval;
DELIMITER
CREATE FUNCTION mycat_seq_nextval(seq_name VARCHAR(50)) RETURNS varchar(64) CHARSET utf8
DETERMINISTIC
BEGIN
UPDATE MYCAT_SEQUENCE
SET current_value = current_value + increment WHERE name = seq_name;
RETURN mycat_seq_currval(seq_name);
END
DELIMITER;

(3)配置在哪个节点上

MYCAT_SEQUENCE 表和以上的 3 个 function,需要放在同一个节点上。在 conf/sequence_db_conf.propertie 文件中 GLOBAL 参数指定存放的节点:

#sequence stored in datanode
GLOBAL=dn1
COMPANY=dn1
CUSTOMER=dn1
ORDERS=dn1

例如:上面的MYCAT_SEQUENCE 表和以上的 3 个 function 存放的数据库对应的节点为dn3,则 conf/sequence_db_conf.propertie 文件中的GLOBAL参数应改为dn3

如果执行的时候报:

sql you might want to use the less safe log_bin_trust_function_creators variable 

需要对数据库做如下设置:

  • windows 下 my.ini[mysqld]加上 log_bin_trust_function_creators=1
  • linux 下/etc/my.cnf 下 my.ini[mysqld]加上 log_bin_trust_function_creators=1

修改完后,即可在 mysql 数据库中执行上面的函数。

使用示例:

insert into table1(id,name) values(next value for MYCATSEQ_GLOBAL,‘test’);

八、java连接MyCat

  1. SpringBoot中,连接MyCat的参数指定(和连接MySQL类似,端口改为8066,用户名和密码改为MyCat中的):

    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    spring.datasource.url=jdbc:mysql://192.168.29.128:8066/mycat?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=CTT
    spring.datasource.username=mycat
    spring.datasource.password=123
    
  2. 在数据库的插入语句中,根据指定的主键生成规则,获取相应的主键进行插入