mycat介绍
-
什么是mycat
mycat是Java语言开发的目前比较主流的数据库中间件之一,是mycat团队在阿里开源的 Cobar 产品基础上而研发的,也一直在维护。mycat实现了 MySQL 协议的服务器,前端用户可以把它看作是一个数据库代理,用 MySQL 客户端工具和命令行访问,而其后端可以用 MySQL 原生协议与多个 MySQL 服务器通信,也可以用 JDBC 协议与大多数主流数据库服务器通信。
mycat不单只可以做MySQL的代理,它的后端可以支持MySQL、SQL Server、Oracle、DB2、PostgreSQL 等主流数据库,也支持 MongoDB 这种新型NoSQL 方式的存储,未来还会支持更多类型的存储。
-
mycat的实现原理
mycat主要是通过对SQL的拦截,然后经过一定规则的分片解析、路由分析、读写分离分析、缓存分析等,然后将SQL发给后端真实的数据块,并将返回的结果做适当处理返回给客户端。
-
mycat的应用场景
- 数据库读写分离,此时配置最为简单,支持读写分离,主从切换
- 数据库分库分表,这也是mycat的核心功能之一,可以解决数据库分库分表之后的数据插入和查询问题
- 多租户应用,每个应用一个数据库,但程序只需连接mycat,程序不改变,实现多租户化
- 海量数据的存储及实时查询,使用mycat是一种简单有效的方案
- 替代Hbase,分析大数据
mycat安装
安装包下载地址:http://dl.mycat.org.cn/
mycat有windows版和Linux版,这里只说Linux的安装
PS:使用mycat前必须安装Java的JDK环境,so easy,这里不讲了,一定记得提前安装!!!
wget http://dl.mycat.org.cn/1.6.7.6/20210730131311/Mycat-server-1.6.7.6-release-20210730131311-linux.tar.gz
tar -C /usr/local -zxvf Mycat-server-1.6.7.6-release-20210730131311-linux.tar.gz
cd /usr/local/mycat/
#./bin/mycat { console | start | stop | restart | status | dump }
./bin/mycat start
连接测试(mycat默认端口是8066,默认密码是123456)
root@f15fc642fedf:/# mysql -uroot -p123456 -P8066 -h192.168.241.140
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.6.29-mycat-1.6.7.6-release-20210730131311 MyCat Server (OpenCloudDB)
Copyright (c) 2000, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
mycat核心配置
mycat的配置文件在 conf 文件夹下,最核心的配置文件有三个:schema.xml、server.xml、rule.xml
一、schema.xml
该配置文件主要是用于配置逻辑库、逻辑表等相关信息
-
schema标签
定义mycat实例中的逻辑库,mycat可以有多个逻辑库,每个逻辑库都有自己的相关配置。可以使用schema标签来划分这些不同的逻辑库。如果不配置schema标签,所有表的配置会属于同一个默认的逻辑库。
逻辑库的概念和MySQL的database的概念一样,在查询两个不同逻辑库中的表的时候,需要切换到该逻辑库下进行查询。示例:
<schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100" randomDataNode="dn1"></schema>
-
name
:定义逻辑库名,必须唯一不能重复 -
checkSQLschema
:检查发给mycat的SQL是否含有库名。当该值为true时,例如执行语句select * from TESTDB.company
mycat会把语句修改为select * from company
去掉库名 TESTDB -
sqlMaxLimit
:限制返回结果集的行数。该值设置为某个数值时,每条执行的sql语句,如果没有加上limit参数,mycat会自动加上这个属性的值。不写的话,默认返回所有的值。需要注意的是,如果运行的schema为非拆分库的,那么该属性不会生效。需要自己sql语句加limit。 -
randomDataNode
:这是新版mycat新加入的属性,定义将一些随机语句发送到该数据节点中
-
-
table 标签
定义mycat中的逻辑表,所有需要拆分的表都需要在这个标签中定义。示例:
<table name="customer" primaryKey="id" dataNode="dn1,dn2" rule="sharding-by-intfile" autoIncrement="true" fetchStoreNodeByJdbc="true"> <childTable name="customer_addr" primaryKey="id" joinKey="customer_id" parentKey="id"> </childTable> </table>
-
name
:定义逻辑表名,同一个schema中逻辑表名必须唯一 -
primaryKey
:指定逻辑表中的主键,也是需要与物理表的主键一致 -
dataNode
:指定物理表所在数据节点的名称,该属性的值需要和dataNode标签中的name值相对应。配置多个以逗号隔开,或者使用dn1$0-100
方式 -
rule
:指定逻辑表使用的分片规则名称,规则名称在rule.xml中定义,必须与tableRule标签中name属性对应 -
autoIncrement
:是否自增,mycat提供了自增长主键功能,但是对应的mysql节点上数据表,没有auto_increment,那么在mycat层调用last_insert_id()也是不会返回结果的 -
fetchStoreNodeByJdbc
:是否启用ER表使用JDBC方式获取DataNode
2.1)childTable标签:用于定义 ER 分片的子表。通过标签上的属性与父表进行关联
-
name
:子表名称 -
primaryKey
:主键 -
joinKey
:子表中字段的名称 -
parentKey
:父表中字段名称
-
-
dataNode标签
定义mycat中的数据节点,也就是我们所说的数据分片。一个dataNode标签就是一个独立的数据分片。数据节点指向的是存储逻辑表的物理数据库。示例:
<dataNode name="dn1" dataHost="localhost1" database="db1" /> <dataNode name="dn2" dataHost="localhost1" database="db2" /> <dataNode name="dn3" dataHost="localhost1" database="db3" />
-
name
:定义数据节点的名字,这个名字需要唯一。上边在table标签上用这个名字来建立表与分片对应的关系 -
dataHost
:定义该分片属于哪个数据库实例,属性与dataHost标签上定义的name对应 -
database
:定义该分片属于数据库实例上的具体库
-
-
dataHost标签
这个标签直接定义了具体数据库实例,读写分离配置和心跳语句。示例:
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="jdbc:mysql://localhost:3306" user="root" password="root"> <readHost host="hostS1" url="jdbc:mysql://localhost:3306" user="root" password="123456"/> </writeHost> </dataHost>
name
:唯一标示dataHost标签,供上层使用,必须唯一maxCon
:指定每个读写实例连接池的最大连接minCon
:指定每个读写实例连接池的最小连接,初始化连接池的大小-
balance
:指定读写分离的负载均衡类型,目前有4种类型0:代表不开启读写分离机制,所有读操作都发送到当前可用的writeHost上
1:代表全部的readHost与stand by writeHost参与select语句的负载均衡,简单的说,当双主双从模式(M1-S1,M2-S2 并且M1 M2互为主备),正常情况下,M2,S1,S2都参与select语句的负载均衡。
2:所有读操作都随机的在writeHost、readHost上分发
3:所有读请求随机的分发到writeHost对应的readHost执行,writeHost不负担读压力(1.4 新增)
-
writeType
:指定写实例的负载均衡类型,目前有4种类型-1:表示不自动切换
0:所有写操作发送到配置的第一个writeHost,第一个挂了切到还生存的第二个writeHost。重新启动后以切换后的为准,切换记录在配置文件中:dnindex.properties
1:所有写操作都随机的发送到配置的writeHost,1.5 以后废弃不推荐使用
2:基于MySQL主从同步的状态决定是否切换(1.4 新增)
dbType
:指定后端连接的数据库类型dbDriver
:指定连接后端数据库使用的驱动,目前可选的值有 native 和 jdbc-
switchType
:指定主从切换的方式-1:表示不自动切换
1:默认值,自动切换
2:基于MySQL主从同步的状态决定是否切换,心跳检测语句为:
show slave status
3:基于MySQL galary cluster 的切换机制(适合集群,1.4.1新增),心跳检测语句为:
show status like 'wsrep%'
slaveThreshold
:定义主从复制延时阈值,当 Seconds_Behind_Master > slaveThreshold 时,读写分离筛选器会过滤掉此Slave机器,防止读到很久之前的旧数据。该属性是用于配合writeType
属性实现根据主从延时来进行主从切换的
4.1)heartbeat 标签:指明用于和后端数据库进行心跳检查的语句。例如,MySQL可以使用
select user()
,Oracle可以使用select 1 from dual
等4.2)writehost 标签及 readHost 标签:这两个标签属性相同,这里一起介绍
-
host
:标识不同实例名称,一般writeHost名称使用M1作为后缀,readHost则使用S1作为后缀 -
url
:配置数据库的连接地址,如果dbDriver属性是 native,则一般为address:port
这种形式。如果是jdbc或其他的,则需要特殊指定。 -
user
:配置数据库用户名 -
password
:配置数据库密码 -
weight
:配置某个数据库在 readHost 中作为读节点的权重 -
usingDecrypt
:指定是否对密码加密,默认为0, 若需要开启则配置为1
二、server.xml
该配置文件主要是用于配置系统参数、用户信息、访问权限及SQL防火墙和SQL拦截功能等
-
system标签
用于配置mycat的系统配置参数。常见的配置参数示例:
<system> <!-- mycat 服务连接端口 --> <property name="serverPort">8066</property> <!-- mycat 服务管理端口 --> <property name="managerPort">9066</property> <!-- mycat 服务监听的ip --> <property name="bindIp">0.0.0.0</property> <!-- 0为需要密码登陆、1为不需要密码登陆;默认为0,设置为1则需要指定默认账户--> <property name="nonePasswordLogin">0</property> <!-- 前端连接的写队列大小 --> <property name="frontWriteQueueSize">2048</property> <!-- 设置字符集编码 --> <property name="charset">utf8</property> <!-- mycat 的进程数量 --> <property name="processors">8</property> <!-- 闲置连接超时时间,单位:毫秒 --> <property name="idleTimeout">1800000</property> <!-- 默认最大返回的数据集大小 --> <property name="defaultMaxLimit">100</property> <!-- 允许的最大包大小 --> <property name="maxPacketSize">104857600</property> <!-- 0遇上没有实现的报文(Unknown command:),就会报错、1为忽略该报文,返回ok报文。 在某些mysql客户端存在客户端已经登录的时候还会继续发送登录报文,mycat会报错,该设置可以绕过这个错误--> <property name="ignoreUnknownCommand">0</property> <property name="useHandshakeV10">1</property> <property name="removeGraveAccent">1</property> <!-- 1为开启实时统计、0为关闭 --> <property name="useSqlStat">0</property> <!-- 1为开启全加班一致性检测、0为关闭 --> <property name="useGlobleTableCheck">0</property> <!-- SQL 执行超时 单位:秒--> <property name="sqlExecuteTimeout">300</property> <property name="sequnceHandlerType">1</property> <!--必须带有MYCATSEQ_或者 mycatseq_进入序列匹配流程 注意MYCATSEQ_有空格的情况--> <property name="sequnceHandlerPattern">(?:(\s*next\s+value\s+for\s*MYCATSEQ_(\w+))(,|\)|\s)*)+</property> <!-- 子查询中存在关联查询的情况下,检查关联字段中是否有分片字段 .默认 false --> <property name="subqueryRelationshipCheck">false</property> <property name="sequenceHanlderClass">io.mycat.route.sequence.handler.HttpIncrSequenceHandler</property> <!--默认为type 0: DirectByteBufferPool | type 1 ByteBufferArena | type 2 NettyBufferPool --> <property name="processorBufferPoolType">0</property> <!--分布式事务开关,0为不过滤分布式事务,1为过滤分布式事务(如果分布式事务内只涉及全局表,则不过滤),2为不过滤分布式事务,但是记录分布式事务日志--> <property name="handleDistributedTransactions">0</property> <!-- off heap for merge/order/group/limit 1开启;0关闭 --> <property name="useOffHeapForMerge">0</property> <!--是否采用zookeeper协调切换 --> <property name="useZKSwitch">false</property> <!--如果为 true的话 严格遵守隔离级别,不会在仅仅只有select语句的时候在事务中切换连接--> <property name="strictTxIsolation">false</property> <!-- Mycat连接数据库时使用的隔离级别 1 - 读未提交 2 - 读已提交 3 - 可重复读 4 - 串行化 --> <property name="txIsolation">2</property> <property name="useZKSwitch">true</property> <!--如果为0的话,涉及多个DataNode的catlet任务不会跨线程执行--> <property name="parallExecute">0</property> </system>
-
user标签
用于配置mycat的访问用户及权限。示例:
<!-- 用户名,defaultAccount属性是指定当前账户是否为默认账户 --> <user name="root" defaultAccount="true"> <!-- 密码 --> <property name="password">123456</property> <!-- 允许该用户访问的逻辑库 --> <property name="schemas">TESTDB</property> <!-- 可配置多个允许访问的逻辑库,使用逗号分隔 --> <!-- <property name="schemas">db1,db2</property> --> <!-- 是否只读 --> <property name="readOnly">false</property> <!-- 表级 DML 权限配置,check属性表示是否开启该配置 --> <privileges check="true"> <!-- 特别权限应用的逻辑库 --> <schema name="TESTDB" dml="0110"> <!-- 配置用户对该表的访问权限,dml属性用于指定权限位, 如果table标签没有配置该属性的话,默认取schema标签的dml属性值, 剩余没有配置的其他表默认也是取schema标签的dml属性值 --> <table name="tb01" dml="0000"></table> <table name="tb02" dml="1111"></table> </schema> </privileges> </user>
dml属性配置的数字是权限位,分别对应着 insert,update,select,delete 四种权限。例如,当dml的值为0110时,表示拥有 update 和 select 权限,不具有 insert 和 delete 权限。所以权限位为1时代表拥有对应的操作权限,为0时代表没有该操作权限。
三、rule.xml
该配置文件主要用于配置切分规则的。数据库水平拆分之后,就需要用这个配置文件定义分片算法规则
配置文件中主要有两种标签,tableRule 和 function ,这两个是一一对应的,示例:
<!-- name属性指定分片规则的名称,必须在 rule.xml 文件中是唯一的 -->
<tableRule name="mod-long">
<rule>
<!-- 指定使用表中的哪个列(字段)进行分片 -->
<columns>id</columns>
<!-- 指定表的分片算法,取值为<function>标签的name属性 -->
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
<!-- name属性指定分片算法的名称,同样需要是唯一的;class属性指定该算法的具体实现类 -->
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<!-- 要分片的数据库节点数量,必须指定,否则没法分片 -->
<property name="count">3</property>
</function>
mycat默认已经内置了很多分片算法,除了使用这些外也可以根据自己的实际需求自定义算法规则。
mycat内置的常用分片算法有:
- PartitionByMod:简单取模,直接通过列值进行取模得出分片位置。适用于整数类型的列,不能用于非整型的列,且不需要人工干预
- PartitionByHashMod:哈希取模,先将列值进行hash运算之后再取模得出分片位置。适用于非整型的列,且不需要人工干预
- PartitionByFileMap:分片枚举,根据枚举值对数据进行分片。适用于需要人工指定某些数据到哪个分片下。例如在异地多活的场景中通过地区id进行数据分片的场景。
- PartitionByPrefixPattern:字符串范围取模,根据长字符串的前面几位进行取模分片
四、加密明文密码(可选)
在mycat中,不论是 server.xml 配置文件中的mycat登录密码,还是 schema.xml 配置文件的writeHost和readHost标签的连接密码,都是以明文显示的,只要查看这两个配置文件,就可以知道密码,这种方式可能会存在安全隐患。
所以mycat提供了一个工具用于加密明文密码,该工具在一个jar包内,可使用如下命令对密码进行加密,执行成功后会得到一个加密后的字符串,然后替换配置文件中的明文密码即可
java -cp lib/Mycat-server-1.6.7.6-release.jar io.mycat.util.DecryptUtil 0:root:123456
命令解释:
0:user:password 是加密字符串,有两种格式,示例
-
0:root:123456
:代表对mycat用户名root明文密码123456的账户进行加密,修改配置文件示例<user name="root" defaultAccount="true"> <property name="usingDecrypt">1</property> <property name="password">d6D+pOmkuUoY09p4/aivwMsScLa7zfjIwAxvkEhr3v7en06mEXoX9DTTjQNug5CfvGf7Wy9oLcthYI3yLMSjIg==</property> <property name="schemas">TESTDB</property> </user>
-
1:hostM1:root:123456
:代表对 schema.xml 配置文件中dataHost标签下数据库连接加密,修改示例<writeHost host="hostM1" url="localhost:3306" user="root" password="BpkNIjF7LfzS1C76HT7B1bJgmGIDtPihqIvHBlC92L1IFqsMfoJEMk1EkxSzjasWB4GWoUcODYO4AaJstdAp5w==" usingDecrypt="1"> </writeHost>
实例演示
以商城为例,假使现在的需求是:一个订单表数据量太大,需要进行水平拆分。
假使解决方案是:使用三台服务器部署mysql,实现一主二从读写分离。数据库名为shop,订单表拆分成64张表,表名为orders0-63。使用雪花算法实现分布式唯一id,由于id是整型,所以切片规则选择PartitionByMod算法,也就是直接对id进行取模,这样可以保证数据分布均匀且易扩展,将算法中分片数设置为64
按照以上方案,直接贴出相关配置文件中的核心代码
schema.xml
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100" randomDataNode="dn1">
<table name="orders" primaryKey="id" subTables="orders_$0-63" dataNode="dn1" rule="mod-long" />
</schema>
<dataNode name="dn1" dataHost="shop" database="shop" />
<dataHost name="shop" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="192.168.241.140:3306" user="root" password="root">
<readHost host="hostS1" url="192.168.241.141:3306" user="root" password="root"/>
<readHost host="hostS2" url="192.168.241.142:3306" user="root" password="root"/>
</writeHost>
</dataHost>
</mycat:schema>
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">
<property name="count">64</property>
</function>
测试
mysql> explain insert into orders (id,no) values(1,'1111111');
+-----------+------------------------------------------------------------+
| DATA_NODE | SQL |
+-----------+------------------------------------------------------------+
| dn1 | INSERT INTO orders_1 (id, no) VALUES ('1', '1111111') |
+-----------+------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> insert into orders (id,no) values(1,'1111111');
Query OK, 1 row affected (0.01 sec)
mysql> explain select id,no from orders where id in (1);
+-----------+---------------------------------------------------------+
| DATA_NODE | SQL |
+-----------+---------------------------------------------------------+
| dn1 | SELECT id, no FROM orders_1 orders WHERE id IN (1) |
+-----------+---------------------------------------------------------+
1 row in set (0.00 sec)
mysql> select id,no from orders where id in (1);
+----+---------+
| id | no |
+----+---------+
| 1 | 1111111 |
+----+---------+
1 row in set (0.00 sec)
mysql> show variables like "server_id";
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| server_id | 2 |
+---------------+-------+
1 row in set (0.00 sec)
mysql> show variables like "server_id";
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| server_id | 3 |
+---------------+-------+
1 row in set (0.01 sec)
mysql>
通过上述可观察到,数据在插入和查询时mycat会自动取模计算找到对应的表。查询 server-id 参数发现在变,证明确实在两个从库中进行负载轮询。到此说明整个操作成功