Mycat中间件
我们知道水平分表会给业务层带来很多麻烦,例如
有一个记录获奖人员的表,每年都会有获奖新人录入到这个表中
我们以时间为维度进行表的切分,形式如下:
t_archieve_2016
t_archieve_2017
t_archieve_2018
...
如果想要进行新增,我们要获取当前的年份,根据当前年份找到对应的表进行插入
如果想根据id查询,我们就要进行union联合查询。
如果上面的表不是在同一个节点而是分布在不同的节点,那么业务层的判断会更多。
为了解决这个问题,Mycat就出现了。有了Mycat之后,Mycat连接数据库服务(Mycat可能是连一个节点的数据库也可能是连多个节点的数据库),而业务层直接连接Mycat服务而不直接连接mysql服务。无论是分库还是分表,无论分库分表是否在同一个节点,Mycat提供给业务层的都只是一个正常的库和一张正常的表。查询语句该找哪个节点的哪个表是Mycat该做的事,不是业务层该关心的事。这样一来业务层的逻辑就会减少很多判断,简单很多。
由于mycat是处于Web应用和MySQL之间的一个桥梁角色,所以mycat是一种中间件。中间件连接着Web应用和MySQL,Web应用和MySQL不直接连接。
Mycat使用的也是mysql的协议,换句话说就是,任何mysql客户端的命令,语法对Mycat都是有效的。我们可以用mysql -uroot -p来连接Mycat客户端,可以用select语句在mycat查询等。
Mycat的安装
Mycat是基于Java实现的,所以要先安装Java。
到官网下载Java SE 的rpm包。
官网下载mycat
# 安装Java
rpm -ivh jdk-13.0.2_linux-x64_bin.rpm
# 安装mycat
tar -xzf Mycat-server-1.6.7.4-release-20200105164103-linux.tar.gz
mv mycat /usr/local
mycat命令是/usr/local/mycat/bin/mycat,将其写入环境变量中
# 启动
mycat console # 前台运行
* 启动时报错说
Unrecognized VM option 'AggressiveOpts'
Error: Could not create the Java Virtual Machine.
关键是第一句,说虚拟机识别不了AggressiveOpts参数。
第二句说启动不了Java虚拟机。
# 修改mycat配置文件
vi /usr/local/mycat/conf/wrapper.conf
找到
wrapper.java.additional.3=-XX:+AggressiveOpts
将其注释即可。
* 作者遇到的启动报错是:
Invalid maximum heep size: -Xmx4G
Error: Could not create the Java Virtual Machine.
是配给Java的内存过高,我的服务器的内存不够
# 修改mycat配置文件
vi /usr/local/mycat/conf/wrapper.conf
找到
wrapper.java.additional.4=-XX:MaxDirectMemorySize=2G
wrapper.java.additional.9=-Xmx4G
wrapper.java.additional.10=-Xms1G
将2G、4G、1G改为512M即可
# 将所有的真实节点的MySQL设置为忽略表名大小写,否则后面会有很多问题,只需在mysql配置文件的[mysqld]加一条
lower_case_table_names=1
记得重启
可以通过 show variables like "%case%"; 查看是否开启忽略大小写
忽略表名大小写其实是将你在sql语句中的表名转为小写。
所以如果你的表名一开始是大写的,之后才开启忽略大小写,那么无论你的sql语句里的表名是大写还是小写,都会提示报错说找不到这个表,因为他都转成小写了,而你的表名是大写的。
如果MySQL没有开启忽略大小写,在mycat中创建表的时候,即使你的sql是小写的表名,在mysql真实节点中创建的都是大写的表名。
# 启动
mycat console
# 启动成功后,mycat监听了8066端口,此时可以在本地连接mycat(要开放8066端口的防火墙);我是在204.175.124.51这个机器上安装的mycat
mysql -h204.175.124.51 -uroot -P8066 -p123456
此时连的是mycat而不是mysql,mysql是3306端口。
mycat连接的默认用户和密码是 root、123456,其配置在/usr/local/mycat/conf/server.xml
mycat中有一个初始的库默认是TESTDB这个库(是一个虚拟库,真实不存在这样的库),其配置在/usr/local/mycat/conf/schema.xml
这两个配置文件至关重要
mycat初认识:
现在我们要解决3个问题:
1.root/123456用户是怎么生成的
2.TESTDB这个库和里面的表是怎么生成的
第一个问题:Mycat中的用户如何生成
Mycat中的用户是在server.xml这个配置文件中添加并通过重启mycat服务生成的。
先来看一下server.xml这个文件里面关于用户的配置如下
<user name="root" defaultAccount="true">
<property name="password">123456</property>
<property name="schemas">TESTDB</property>
<property name="defaultSchema">TESTDB</property>
<!--No MyCAT Database selected 错误前会尝试使用该schema作为schema,不设置则为null,报错 -->
<!-- 表级 DML 权限设置 -->
<!--
<privileges check="false">
<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">TESTDB</property>
<property name="readOnly">true</property>
<property name="defaultSchema">TESTDB</property>
</user>
这里设置了两个用户并赋予他们权限:
用户 root 密码 123456 该用户能对表TESTDB有增删改查操作
用户 user 密码 user 该用户只能对TESTDB进行读操作
我们可以在下面继续添加用户:
<user name="hello">
<property name="password">hello</property>
<property name="schemas">TESTDB,db2</property> <!-- 指定能操作的数据库,这里可以通过逗号隔开指定操作多个数据库 -->
</user>
这是最简单的添加mycat用户的方法。这里是在mycat添加用户,但并没有对MySQL添加用户。一般来说,添加用户是为了管理schema(虚拟表),每个schema都需要在server.xml中定义(创建)一个用户来对该schema进行管理,此时只有该用户可以对该schema进行操作。
第二个问题:mycat中的库和表怎么生成的虚拟表是在schema.xml这个配置文件中指定,然后通过重启mycat而生成的。
<schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100" randomDataNode="dn1">
<!-- auto sharding by id (long) -->
<!--splitTableNames 启用<table name 属性使用逗号分割配置多个表,即多个表使用这个配置-->
<table name="travelrecord,address" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" splitTableNames ="true"/>
<!-- <table name="oc_call" primaryKey="ID" dataNode="dn1$0-743" rule="latest-month-calldate"
/> -->
</schema>
<dataNode name="dn1" dataHost="localhost1" database="db1" />
<dataNode name="dn2" dataHost="localhost1" database="db2" />
<dataNode name="dn3" dataHost="localhost1" database="db3" />
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<!-- can have multi write hosts -->
<writeHost host="hostM1" url="localhost:3306" user="root"
password="123456">
</writeHost>
<!-- <writeHost host="hostM2" url="localhost:3316" user="root" password="123456"/> -->
</dataHost>
-----------------------------------
<schema>指定了TESTDB这个虚拟库,里面<table>指定TESTDB这个虚拟库中要生成哪些虚拟表。这里指定生成 travelrecord 和 address这两个虚拟表。<table>的dataNode属性表示,这两个虚拟表要和哪些节点的真实物理表进行关联。
-----------------------------------
<dataHost>用于指定mycat要连接的Mysql的节点信息,包括节点IP,用户和密码。
<heartbeat>用于隔一段时间探测所连接的MySQL节点是否已断掉。
<writeHost>指定写节点
<readHost>指定读节点
------------------------------------
<dataNode>指定分片节点,即真实的数据库是哪个节点(dataHost属性)的哪个库(database属性)。
注意点:
虚拟表和真实物理表的表名相同。
可以在mycat登录的客户端中创建真实表,其对应的所有真实节点都会创建这个表。
下面做一个小实例:
mycat下创建一个shop虚拟数据库,该库下面创建product虚拟表,对应本机节点的shop1,shop2和shop3这3个库(即Mycat和Mysql都在一台主机上)。product表结构如下:
create table product (
id int primary key auto_increment,
name varchar(255),
price decimal(10,2)
)engine=innodb;
下面是实操的步骤:1.修改schema.xml
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="shop" checkSQLschema="true" sqlMaxLimit="100" randomDataNode="dn1">
<!-- auto sharding by id (long) -->
<!--splitTableNames 启用<table name 属性使用逗号分割配置多个表,即多个表使用这个配置-->
<table name="product" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" splitTableNames ="true"/>
<!-- <table name="oc_call" primaryKey="ID" dataNode="dn1$0-743" rule="latest-month-calldate"
/> -->
</schema>
<!-- <dataNode name="dn1$0-743" dataHost="localhost1" database="db$0-743"
/> -->
<dataNode name="dn1" dataHost="h1" database="shop1" />
<dataNode name="dn2" dataHost="h1" database="shop2" />
<dataNode name="dn3" dataHost="h1" database="shop3" />
<dataHost name="h1" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<!-- can have multi write hosts -->
<writeHost host="hostM1" url="localhost:3306" user="root"
password="xxxx">
<readHost host="hostM2" url="localhost:3306" user="root" password="xxxx"></readHost>
</writeHost>
<!-- <writeHost host="hostM2" url="localhost:3316" user="root" password="123456"/> -->
</dataHost>
</mycat:schema>
定义了写节点和读节点,这两个节点是同一个节点同一个MySQL服务。
如果不创建读节点<readHost>,那么你在mycat中只能写入,不能查询。
writeHost和readHost使用的用户是MySQL真实用户而非mycat的登录用户。
2.为shop这个虚拟库创建一个用户,该用户专门用来操作shop虚拟库
在server.xml中,添加:
<user name="zbp">
<property name="password">123456</property>
<property name="schemas">shop</property>
</user>
PS:已知server.xml定义了一个root/123456的用户,该用户专门用来操作TESTDB虚拟库。
但是如果schema.xml中没有定义TESTDB这个虚拟库,那么启动mycat时就会报错。
3.重启mycat服务。4.在本地连接203.195.174.151的mycat服务,并创建product真实物理表。
mysql -h203.195.174.151 -uzbp -P8066 -p123456
show databases;
+----------+
| DATABASE |
+----------+
| shop |
+----------+
use shop;
show tables;
+----------------+
| Tables in shop |
+----------------+
| product |
+----------------+
# 此时在MySQL中还没有创建shop1,shop2,shop3这3个真实的库,但是在mycat中依然能看到shop虚拟库和product虚拟表
但是如果对product虚拟表查询,会报错说不存在这个表。
# 现在在mysql中创建shop1,shop2,shop3这3个库
create database shop1;
create database shop2;
create database shop3;
# 在本地mycat客户端的shop虚拟表中创建product表:
create table product (
id int primary key auto_increment,
name varchar(255),
price decimal(10,2)
)engine=innodb;
# 此时会同时在shop1~3都创建这个product表。
现在插入一条数据
insert into product values (null,"A",12); # 报错:ERROR 1064 (HY000): partition table, insert must provide ColumnList
# 意思是插入时要指定所有的字段名
insert into product (`id`,`name`,`price`) values (null,"A",12); # 报错:ERROR 1064 (HY000): columnValue:NULL Please eliminate any quote and non number within it.
# 意思时不能用null占位
insert into product (`id`,`name`,`price`) values (1,"A",12); #插入成功
现在这条数据并非同时插入shop1~3,而是只插入了shop1;这里涉及到了mycat的分片规则,后面会详细介绍。
如果想查看到底插入了哪个库,可以
explain insert into product (`id`,`name`,`price`) values (1,"A",12); #来查看
==============================================
Mycat表切分规则
在上面的例子中,插入的数据是插入到shop1这个库。但是随着数据量增大,数据也会插入到shop2和shop3这两个库,这要归因于在定义schema.xml的<table>标签中的rule="auto-sharding-long"这个属性。
该属性指定该product表使用auto-sharding-long(范围约定)这个分片规则来分片。
下面我们先看一下有哪些数据会写到shop1,哪些数据会写到shop2和shop3:
explain insert into product (id,name,price) values (10000000,"asdf",1000);
+-----------+-------------------------------------------------------------------------+
| DATA_NODE | SQL |
+-----------+-------------------------------------------------------------------------+
| dn2 | INSERT INTO product (id, name, price) VALUES ('10000000', 'asdf', 1000) |
+-----------+-------------------------------------------------------------------------+
explain insert into product (id,name,price) values (15000000,"asdf",1000);
+-----------+-------------------------------------------------------------------------+
| DATA_NODE | SQL |
+-----------+-------------------------------------------------------------------------+
| dn3 | INSERT INTO product (id, name, price) VALUES ('15000000', 'asdf', 1000) |
+-----------+-------------------------------------------------------------------------+
发现当id为500万到1000万就会插入节点2,即shop2;当id为1000万到1500万就会插入shop3;
超过1500万就会报错。
所以这里是根据id的大小范围来划分节点的,在mycat中用了range分区的技术。
这里的划分规则在schema.xml的<table>标签定义虚拟表的时候就定义好了的。
<table name="product" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" splitTableNames ="true"/>
这里的规则是rule="auto-sharding-long",即按范围约定分片(分区)。
而规则是在rule.xml定义的
<tableRule name="auto-sharding-long">
<rule>
<columns>id</columns>
<algorithm>rang-long</algorithm>
</rule>
</tableRule>
上面的配置意思是对id字段进行分区,分区的算法是range-long
而range-long算法也可以在rule.xml找到。
<function name="rang-long"
class="io.mycat.route.function.AutoPartitionByLong">
<property name="mapFile">autopartition-long.txt</property>
</function>
表示这个算法是写在 AutoPartitionByLong 这个类文件中的,这个类是写在mycat的java源码中。
看不懂java源码没关系,重点看mapFile地图文件,该文件是具体的分区规则,在这里是autopartition-long.txt文件,该文件在mycat的配置目录中。
内容如下:
# range start-end ,data node index
# K=1000,M=10000.
0-500M=0
500M-1000M=1
1000M-1500M=2
意思是 分区字段(id)在0~500万内就写入第一个节点,500玩~1000万写入第二个节点,后面一样的意思。
所以即使我们不会Java,我们也可以在地图文件中编写规则来简单的制定自定义规则。
mycat有多少中规则可以查看文档。
下一节我们将介绍Mycat中的一些基本概念和schema.xml/server.xml/rule.xml这三个配置文件的标签和属性的含义。