1 Mycat简介
1.1 Mycat
- Mycat是数据库中间件。
- 官网:http://mycat.org.cn/
1、数据库中间件
- 中间件:一类连接软件组件和应用的计算机软件,以便于软件各部件之间的沟通。
- 数据库中间件:连接Java应用程序和数据库的中间件。
2、数据库中间件对比
- Cobar:阿里团队开发,已多年无维护更新。
- Mycat:基于Cobar二次开发,开源社区维护。
- OneProxy:不开源的商业中间件。
- kingshard:Go语言开发,在不断完善。
- Vitess:YouTube生产在使用,不支持MySQL原生协议。
- Atlas:360团队基于MySQLProxy改写,高并发下不稳定。
- MaxScale:MariaDB研发的中间件。
- MySQLRoute:MySQL官方发布的中间件。
1.2 功能
1、读写分离
2、数据分片
- 垂直拆分(分库)、水平拆分(分表)、垂直+水平拆分(分库分表)。
3、多数据源整合
- MySQL、Oracle、Redis等。
1.3 原理
Mycat的原理中最重要的一个动词是“拦截”。
它拦截了用户发送过来的SQL语句:
- 对SQL语句做了一些特定的分析:如分片分析、路由分析、读写分离分析、缓存分析等。
- 将此SQL发往后端的真实数据库,并将返回的结果做适当的处理。
- 返回给用户。
这种方式把数据库的分布式从代码中解耦出来,程序员察觉不出来后台使用Mycat还是MySQL。
2 安装Mycat(Linux)
2.1 下载
- 百度云:https://pan.baidu.com/s/1dLfhYuLi4b5gLyiLBfPgJg
- 提取码:f1hp
2.2 上传服务器
- 将
Mycat-server-1.6-RELEASE-20161028204710-linux.tar.gz
上传至服务器/opt
目录。
2.3 安装
# 1、切换到/opt/目录 cd /opt # 2、解压文件 tar -zxvf Mycat-server-1.6-RELEASE-20161028204710-linux.tar.gz # 3、将解压后文件拷贝到/usr/local/目录 cp -r mycat /usr/local/ # 4、进入/usr/local/mycat/目录 cd /usr/local/mycat/
2.4 核心配置文件
1、schema.xml
schema.xml
:定义逻辑库、表、分片节点等内容。这里使用本机两个mysql服务(端口为3306和330)进行模拟(
database=testdb
指定为实际数据库):<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"> </schema> <dataNode name="dn1" dataHost="host1" database="testdb" /> <dataHost name="host1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="127.0.0.1:3306" user="root" password="root"> <!-- can have multi read hosts --> <readHost host="hostS1" url="127.0.0.1:3307" user="root" password="root" /> </writeHost> </dataHost> </mycat:schema>
2、rule.xml
rule.xml
:定义分片规则。
3、server.xml
server.xml
:定义用户以及系统相关变量,如端口等。<?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="u***pression">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> <!-- 修改此处 --> <user name="mycat"> <property name="password">123456</property> <property name="schemas">TESTDB</property> </user> <user name="user"> <property name="password">user</property> <property name="schemas">TESTDB</property> <property name="readOnly">true</property> </user> </mycat:server>
2.5 启动
- 切换到
bin
目录:
1、控制台启动
./mycat console
2、后台启动
./mycat start
2.6 登录
- 前提:已经启动Mycat服务。
1、登录后台管理窗口
此方式用于管理维护Mycat(运维人员):
mysql -umycat -p123456 -P 9066 -h 127.0.0.1
用户名、密码在
server.xml
中配置。端口:9066为维护端口。
主机:Mycat启动所在主机,当前模拟为本机。
2、登录数据窗口
此方式用于使用Mycat获取数据(开发人员):
mysql -umycat -p123456 -P 8066 -h 127.0.0.1
用户名、密码在
server.xml
中配置。端口:9066为维护端口。
主机:Mycat启动所在主机。
3 搭建读写分离
- 搭建MySQL主从复制。
- 搭建MyCat读写分离。
3.1 一主一从
1、MySQL的my.ini
配置文件
- MySQL主机:
[mysql] default-character-set=utf8 [mysqld] character-set-server=utf8 default-storage-engine=INNODB # 配置MySQL主机 server-id=1 # 主服务器唯一id log-bin=mysql-bin # 启动二进制日志 # 设置不要复制的数据库(可设置多个) binlog-ignore-db=mysql binlog-ignore-db=information_schema # 设置需要复制的数据库 binlog-do-db=testdb # 设置login格式 binlog_format=STATEMENT
- MySQL从机:
[mysql] default-character-set=utf8 [client] port=3307 [mysqld] port=3307 [mysqld] character-set-server=utf8 default-storage-engine=INNODB # 配置从机 server-id=2 # 从机id relay-log=mysql-relay # 启用中继日志
- 注意:由于此处实验直接复制mysql安装包,需要删除
mysql安装目录/data/auto.cnf
文件,去除旧的UUID信息。 - 配置完成,重启服务器。
2、MySQL主机授权
# 授权 GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%' IDENTIFIED BY '123123';
其他命令:
# 停止主机服务,重置主机 reset master; # 查看主机状态 SHOW MASTER STATUS;
3、MySQL从机连接
CHANGE MASTER TO MASTER_HOST='127.0.0.1', # Master服务器IP MASTER_PORT=3306, # Master服务器端口 MASTER_USER='slave', # Master服务器授权同步的用户名,或者为root用户名 MASTER_PASSWORD='123123', # Master授权同步的密码,或者为root密码 MASTER_LOG_FILE='mysql-bin.000001', # Master服务器产生的日志 MASTER_LOG_POS=1361; # 指定请求同步Master的bin-log的哪一行之后的内容
启动从机服务:
start slave;
其他命令:
# 停止从机服务 stop slave; # 查看从机状态:如果Slave_IO_Running和Slave_SQL_Running都为Yes,说明配置成功 SHOW SLAVE STATUS;
4、MyCat配置逻辑库:schema.xml
<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"> </schema> <dataNode name="dn1" dataHost="host1" database="testdb" /> <dataHost name="host1" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="127.0.0.1:3306" user="root" password="root"> <!-- can have multi read hosts --> <readHost host="hostS1" url="127.0.0.1:3307" user="root" password="root" /> </writeHost> </dataHost> </mycat:schema>
<dataHost balance="参数">
- 0:不开启读写分离机制,所有读操作都发送到当前可用的
writeHost
上。 - 1:全部的
readHost
与stand by writeHost
参与的select
语句的负载均衡。即,当双主双从模式(M1->S1,M2->S2,并且M1与M2互为主备),正常情况下,M2、S1、S2都参与select
语句的负载均衡。 - 2:所有读操作都随机的在
writeHost
、readHost
上分发。 - 3:所有读请求随机的分发到
readHost
上执行,writeHost
不负担读压力。
- 0:不开启读写分离机制,所有读操作都发送到当前可用的
- 一主一从设置为3,双主双从设置为1。
5、启动Mycat服务、登录后台管理
启动Mycat服务:
mycat console
登录后台管理:
mysql -umycat -p123456 -P 8066 -h 127.0.0.1
详细参看【安装Mycat(Linux)】。
3.2 双主双从
- 一个主机M1用于处理所有写请求,它的从机S1和另一台主机M2还有它的从机S2负责所有读请求。当M1主机宕机后,M2主机负责写请求,即M1、M2互为备机。
1、MySQL的my.ini
配置
M1:
[mysql] default-character-set=utf8 [mysqld] character-set-server=utf8 default-storage-engine=INNODB # 配置M1 # 主服务器唯一id server-id=1 # 启动二进制日志 log-bin=mysql-bin # 设置不要复制的数据库(可设置多个) binlog-ignore-db=mysql binlog-ignore-db=information_schema # 设置需要复制的数据库 binlog-do-db=testdb # 设置login格式 binlog_format=STATEMENT # 以下设置M1 # 在作为从数据库的时候,有写入操作也要更新二进制日志文件 log-slave-updates # 表示自增长字段每次递增的量,指自增字段的起始值 auto-increment-increment=2 # 表示自增长字段从哪个数开始,指字段一次递增多少 auto-increment-offset=1
M2:
[mysql] default-character-set=utf8 [client] port=3308 [mysqld] port=3308 [mysqld] character-set-server=utf8 default-storage-engine=INNODB # 配置M2 server-id=2 relay-log=mysql-relay # 启用中继日志 # 启动二进制日志 log-bin=mysql-bin # 设置不要复制的数据库(可设置多个) binlog-ignore-db=mysql binlog-ignore-db=information_schema # 设置需要复制的数据库 binlog-do-db=testdb # 设置login格式 binlog_format=STATEMENT # 以下设置M1 # 在作为从数据库的时候,有写入操作也要更新二进制日志文件 log-slave-updates # 表示自增长字段每次递增的量,指自增字段的起始值 auto-increment-increment=2 # 表示自增长字段从哪个数开始,指字段一次递增多少 auto-increment-offset=2
S1:
[mysql] default-character-set=utf8 [client] port=3307 [mysqld] port=3307 [mysqld] character-set-server=utf8 default-storage-engine=INNODB # 配置S1 server-id=3 # 从机id relay-log=mysql-relay # 启用中继日志
S2:
[mysql] default-character-set=utf8 [client] port=3309 [mysqld] port=3309 [mysqld] character-set-server=utf8 default-storage-engine=INNODB # 配置S2 server-id=4 # 从机id relay-log=mysql-relay # 启用中继日志
配置完重启MySQL服务。
2、M1、M2授权
分别在M1、M2的mysql连接中进行授权:
GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%' IDENTIFIED BY '123123';
查看Master状态:
show master status;
3、S1、S2连接
根据Master中的状态,填写连接信息。
S1:
CHANGE MASTER TO MASTER_HOST='127.0.0.1', # Master服务器IP MASTER_PORT=3306, # Master服务器端口 MASTER_USER='slave', # Master服务器授权同步的用户名,或者为root用户名 MASTER_PASSWORD='123123', # Master授权同步的密码,或者为root密码 MASTER_LOG_FILE='mysql-bin.000001', # Master服务器产生的日志 MASTER_LOG_POS=1361; # 指定请求同步Master的bin-log的哪一行之后的内容
S2:
CHANGE MASTER TO MASTER_HOST='127.0.0.1', # Master服务器IP MASTER_PORT=3308, # Master服务器端口 MASTER_USER='slave', # Master服务器授权同步的用户名,或者为root用户名 MASTER_PASSWORD='123123', # Master授权同步的密码,或者为root密码 MASTER_LOG_FILE='mysql-bin.000001', # Master服务器产生的日志 MASTER_LOG_POS=120; # 指定请求同步Master的bin-log的哪一行之后的内容
启动从机服务:
start slave;
查看连接信息,两个都为
Yes
表示配置成功:show slave status;
4、M1、M2互相连接
M1:
CHANGE MASTER TO MASTER_HOST='127.0.0.1', # Master服务器IP MASTER_PORT=3308, # Master服务器端口 MASTER_USER='slave', # Master服务器授权同步的用户名,或者为root用户名 MASTER_PASSWORD='123123', # Master授权同步的密码,或者为root密码 MASTER_LOG_FILE='mysql-bin.000001', # Master服务器产生的日志 MASTER_LOG_POS=120; # 指定请求同步Master的bin-log的哪一行之后的内容
M2:
CHANGE MASTER TO MASTER_HOST='127.0.0.1', # Master服务器IP MASTER_PORT=3306, # Master服务器端口 MASTER_USER='slave', # Master服务器授权同步的用户名,或者为root用户名 MASTER_PASSWORD='123123', # Master授权同步的密码,或者为root密码 MASTER_LOG_FILE='mysql-bin.000001', # Master服务器产生的日志 MASTER_LOG_POS=120; # 指定请求同步Master的bin-log的哪一行之后的内容
分别开启M1、M2的从机服务:
start slave;
分别查看连接状态,两个都为
Yes
表示配置成功:show slave status;
5、配置Mycat的schema.xml
<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"> </schema> <dataNode name="dn1" dataHost="host1" database="testdb" /> <dataHost name="host1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="127.0.0.1:3306" user="root" password="root"> <readHost host="hostS1" url="127.0.0.1:3307" user="root" password="root" /> </writeHost> <writeHost host="hostM2" url="127.0.0.1:3308" user="root" password="root"> <readHost host="hostS2" url="127.0.0.1:3309" user="root" password="root" /> </writeHost> </dataHost> </mycat:schema>
balance="1"
:全部的readHost
与stand by writeHost
参与select
语句的负载均衡。writeType="参数"
:- 0:所有写操作发送到配置的第一个
writeHost
,第一个挂了切换到还生存的第二个writeHost
。 - 1:所有写操作都随机的发送到配置的
writeHost
,1.5以后废弃不推荐。
- 0:所有写操作发送到配置的第一个
switchType="参数"
:- 1:默认值。重启后
writeHost
自动切换。 - -1:不自动切换。
- 2:基于MySQL主从同步的状态决定是否切换。
- 1:默认值。重启后
6、启动Mycat服务、登录后台管理
启动Mycat服务:
mycat console
登录后台管理:
mysql -umycat -p123456 -P 8066 -h 127.0.0.1
详细参看【安装Mycat(Linux)】。
7、测试
往测试表中添加端口信息:
insert into mytbl values (null, @@port);
从测试表中获取信息:
可以看到,三次分别从端口3309、3307、3308处获取。
4 垂直拆分——分库
- 一个数据库由很多表构成,每个表对应着不同的业务。
- 垂直拆分是指按照业务将表进行分类,分到不同的数据库上面,这样也就将数据(读写压力)分担到不同的库上面。
- 分库:将一个数据库中的不同表,拆分到不同的数据库中。
4.1 如何划分表
- 分库的原则:有紧密关联关系的表应该在一个库里,相互没有关联关系的表可以分到不同的库里。
4.2 配置分库
1、新增mysql服务
复制mysql安装目录,删除
/data/auto.cnf
文件,修改my.ini
:[mysql] default-character-set=utf8 [client] port=3310 [mysqld] port=3310 [mysqld] character-set-server=utf8 default-storage-engine=INNODB
进入
/bin/
目录,安装服务:mysqld -install mysql3310
启动
mysql3310
服务,发现报错:卸载服务:
sc delete mysql3310
配置环境变量
path
。重新安装服务,并重启,成功。
2、配置Mycat的schema.xml
- 在之前双主双从的基础之上,增加一个mysql服务,端口为3310,存放
customer
表。 - 根据
schema.xml
修改数据库,创建orders
数据库。
<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"> <table name="customer" dataNode="dn2"></table> </schema> <dataNode name="dn1" dataHost="host1" database="orders" /> <dataNode name="dn2" dataHost="host2" database="orders" /> <dataHost name="host1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="127.0.0.1:3306" user="root" password="root"> <readHost host="hostS1" url="127.0.0.1:3307" user="root" password="root" /> </writeHost> <writeHost host="hostM2" url="127.0.0.1:3308" user="root" password="root"> <readHost host="hostS2" url="127.0.0.1:3309" user="root" password="root" /> </writeHost> </dataHost> <dataHost name="host2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="127.0.0.1:3310" user="root" password="root"> </writeHost> </dataHost> </mycat:schema>
3、使用Mycat建库、建表
配置完成后,根据配置创建
orders
数据库。启动Mycat服务:
mycat console
登录Mycat管理窗口:
mysql -umycat -p123456 -P 8066 -h 127.0.0.1
选择数据库:
use TESTDB;
创建表:
create table customer ( id int auto_increment, name varchar(200), primary key(id) ); create table orders ( id int auto_increment, order_type int, customer_id int, amount decimal(10,2), primary key(id) ); create table orders_detail ( id int auto_increment, detail varchar(2000), order_id int, primary key(id) ); create table dict_order_type ( id int auto_increment, order_type varchar(200), primary key(id) );
执行成功后,发现只在
3310
端口上创建出customer
表,而在3306
端口上会创建其他三张表。由于之前配置主从复制时,配置了binlog-do-db=testdb
,因此orders
数据库不会进行主从复制。所以,此时的
schema.xml
不用配置主从信息,修改如下:<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"> <table name="customer" dataNode="dn2"></table> </schema> <dataNode name="dn1" dataHost="host1" database="orders" /> <dataNode name="dn2" dataHost="host2" database="orders" /> <dataHost name="host1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="127.0.0.1:3306" user="root" password="root"> </writeHost> </dataHost> <dataHost name="host2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="127.0.0.1:3310" user="root" password="root"> </writeHost> </dataHost> </mycat:schema>
5 水平拆分——分表
- 相对于垂直拆分,水平拆分不是将表做分类,而是按照某个字段的种类规则来分散到多个数据库之中,每个表中包含一部分数据。
- 简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中的某些行切分到一个数据库,而另外的某些行又切分到其他的数据库中。
5.1 配置分表
1、选择要拆分的表
- MySQL单表存储数据条数是有瓶颈的,单表达到1000万条数据就达到了瓶颈,会影响查询效率,需要进行水平拆分(分表)进行优化。
- 示例:
orders
、orders_detail
都已经达到了600万条数据,需要进行分表优化。
2、分表字段
- 以
orders
表为例,可以根据不同字段进行分表: id
(主键、或创建时间):查询订单注重实效,历史订单被查询的次数少,如此分片会造成一个节点访问多,一个访问少,不平均。customer_id
(客户id):根据客户id去分,两个节点访问访问平均,一个客户的所有订单都在同一个节点。
3、修改配置文件schema.xml
- 为
orders
表设置数据节点为dn1
、dn2
,并指定分片规则为mod_rule
(自定义的名字):
<table name="orders" dataNode="dn1,dn2" rule="mod_rule"></table>
- 完整
schema.xml
如下:
<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"> <table name="customer" dataNode="dn2"></table> <table name="orders" dataNode="dn1,dn2" rule="mod_rule"></table> </schema> <dataNode name="dn1" dataHost="host1" database="orders" /> <dataNode name="dn2" dataHost="host2" database="orders" /> <dataHost name="host1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="127.0.0.1:3306" user="root" password="root"> </writeHost> </dataHost> <dataHost name="host2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="127.0.0.1:3310" user="root" password="root"> </writeHost> </dataHost> </mycat:schema>
4、修改配置文件rule.xml
- 新增分片规则
mod_rule
,并指定规则适用字段为customer_id
。选择分片算法mod-long
(对字段求模运算,内置的算法),对customer_id
进行求模,根据结果分片:
<tableRule name="mod_rule"> <rule> <columns>customer_id</columns> <algorithm>mod-long</algorithm> </rule> </tableRule>
- 往下翻,修改
mod-long
参数:
<function name="mod-long" class="io.mycat.route.function.PartitionByMod"> <!-- how many data nodes --> <property name="count">2</property> </function>
- 在
3310
端口数据库上手动创建orders
表。
5、测试
重启Mycat服务、重新登录Mycat数据管理窗口。
通过Mycat,往
orders
表中插入数据(注意:由于进行分片,插入必须指定字段):
insert into orders (id, order_type, customer_id, amount) values (1, 101, 100, 100100), (2, 101, 100, 100100), (3, 101, 101, 100100), (4, 101, 101, 100100), (5, 101, 102, 100100), (6, 101, 103, 100100);
3306
:3310
:可见Mycat对插入数据进行了分片,即完成了分表配置。
使用Mycat查询
orders
:
- 查询结果的顺序是在
3306
和3310
数据库结果上拼接成的。 - 由此可见,Mycat会先根据分片查询不同数据库,再将查询结果合并,最后发送到应用。
- 要使结果按id排序(即与我们在单个数据库上查询一致),可使用
order by
:
5.2 Mycat的分片——join
order
订单表已经进行分表操作了,和它关联的orders_detail
订单详情表如何进行join
查询?- 我们也需要对
orders_detail
表进行分片操作。
1、ER表
- Mycat借鉴了NewSQL领域的新秀Foundation DB的设计思路。
- Foundation DB创新性提出了Table Group的概念,其将子表的存储位置依赖于主表,并且物理上紧邻存放,因此彻底解决了
join
的效率和性能问题。 - 根据这一思路,提出了E-R关系的数据分片策略,子表的记录与所关联的父表记录存放在同一个数据分片上。
- 修改
schema.xml
配置文件的表配置:
<table name="orders" dataNode="dn1,dn2" rule="mod_rule"> <childTable name="orders_detail" primaryKey="id" joinKey="order_id" parentKey="id"/> </table>
- 完整
schema.xml
如下:
<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"> <table name="customer" dataNode="dn2"></table> <table name="orders" dataNode="dn1,dn2" rule="mod_rule"> <childTable name="orders_detail" primaryKey="id" joinKey="order_id" parentKey="id"/> </table> </schema> <dataNode name="dn1" dataHost="host1" database="orders" /> <dataNode name="dn2" dataHost="host2" database="orders" /> <dataHost name="host1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="127.0.0.1:3306" user="root" password="root"> </writeHost> </dataHost> <dataHost name="host2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="127.0.0.1:3310" user="root" password="root"> </writeHost> </dataHost> </mycat:schema>
在
3310
端口数据库上手动创建orders_detail
表。重启Mycat服务器、数据控制窗口。
通过Mycat向
orders_detail
表插入数据(注意:子表不允许multi insert):
insert into orders_detail (id, detail, order_id) values (1, 'detail', 1); insert into orders_detail (id, detail, order_id) values (2, 'detail', 2); insert into orders_detail (id, detail, order_id) values (3, 'detail', 3); insert into orders_detail (id, detail, order_id) values (4, 'detail', 4); insert into orders_detail (id, detail, order_id) values (5, 'detail', 5); insert into orders_detail (id, detail, order_id) values (6, 'detail', 6);
3306
:
3310
:
可见,
order_id
根据order
表的id进行了分片,配置成功。通过Mycat,在
3306
、3310
数据库中运行join
语句(成功关联6条数据):
select o.*, od.detail from orders o inner join orders_detail od on o.id=od.order_id;
2、全局表
- 在分片的情况下,当业务表因为规模而进行分片以后,业务表与这些附属的字典表之间的关联,就成了比较棘手的问题。
- 考虑到字典表具有以下几个特性:
- 变动不频繁。
- 数据量总体变化不大。
- 数据规模不大,很少有超过数十万条记录。
- 因此,Mycat定义了一种特殊的表,称为“全局表”。全局表具有以下特点:
- 全局表的插入、更新操作会实时在所有节点上执行,保持各个分片的数据一致性。
- 全局表的查询,只从一个节点获取。
- 全局表可以跟任何一个表进行
join
操作。
- 将字典表或者符合字典表特性的一些表定义为全局表,从另外一个方面,很好的解决了数据
join
的难题。 - 通过全局表 + 基于E-R关系的分片策略,Mycat可以满足80%以上的企业应用开发。
- 修改
schema.xml
配置文件,新增全局表配置:
<table name="dict_order_type" dataNode="dn1,dn2" rype="global"></table>
- 全部
schema.xml
配置信息如下:
<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"> <table name="customer" dataNode="dn2"></table> <table name="orders" dataNode="dn1,dn2" rule="mod_rule"> <childTable name="orders_detail" primaryKey="id" joinKey="order_id" parentKey="id"/> </table> <table name="dict_order_type" dataNode="dn1,dn2" type="global"></table> </schema> <dataNode name="dn1" dataHost="host1" database="orders" /> <dataNode name="dn2" dataHost="host2" database="orders" /> <dataHost name="host1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="127.0.0.1:3306" user="root" password="root"> </writeHost> </dataHost> <dataHost name="host2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="127.0.0.1:3310" user="root" password="root"> </writeHost> </dataHost> </mycat:schema>
- 在
3310
端口数据库上手动创建dict_order_type
表。 - 重启Mycat服务器、数据控制窗口。
- 通过Mycat,向
dict_order_type
表插入数据:
insert into dict_order_type (id, order_type) values (101, 'type1'), (102, 'type2');
3306
、3310
中都有相同的数据,说明全局表配置成功:
5.3 常用分片规则
1、取模
- 对分片字段求模,是水平分表最常用的规则。
- 详细参看【5.1 配置分表】。
2、分片枚举
- 通过在配置文件中配置可能的枚举id,自己配置分片。
- 适用于特定的场景,如有些业务需要按照省份或区县来保存,而全国省份区县是固定的,这类业务使用分片枚举规则。
1)schema.xml
- 新增
<table>
,指定表信息,配置rule
分片规则:
<table name="orders_ware_info" dataNode="dn1,dn2" rule="sharding-by-intfile"></table>
2)rule.xml
- 修改
<tableRule name="sharding-by-intfile">
:
<tableRule name="sharding-by-intfile"> <rule> <columns>areacode</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">1</property> <property name="defaultNode">0</property> </function>
- 参数:
columns
:分片字段。algorithm
:分片函数。mapFile
:表示配置文件名称。type
:0(int型),非0(String型)。defaultNode
:默认节点。小于0表示不设置默认节点,大于等于0表示设置默认节点(设置默认节点如果碰到不识别的枚举值,就让它路由到默认节点。如果不设置,不识别时就会报错)。
3)partition-hash-int.txt
110=0 120=1
4)测试
- 重启Mycat,登录Mycat数据控制窗口。
- 通过Mycat,创建订单归属区域信息表
orders_ware_info
(结果:3306
、3310
数据库中都成功创建该表):
create table orders_ware_info ( id int auto_increment comment '编号', order_id int comment '订单编号', address varchar(200) comment '地址', areacode varchar(20) comment '区域编号', primary key (id) );
- 通过Mycat,向
orders_ware_info
表中插入数据(结果:3306
插入了北京,3310
插入了天津,说明配置成功):
insert into orders_ware_info (id, order_id, address, areacode) values (1, 1, '北京', '110'); insert into orders_ware_info (id, order_id, address, areacode) values (1, 1, '天津', '120');
3、范围约定
- 提前规划好分片字段某个范围属于哪个分片。
1)schema.xml
<table name="payment_info" dataNode="dn1,dn2" rule="auto-sharding-long"></table>
2)rule.xml
<tableRule name="auto-sharding-long"> <rule> <columns>order_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>
3)autopartition-long.txt
0-102-0 103-200=1
4)测试
- 重启Mycat,登录数据控制窗口。
- 通过Mycat,创建
payment_info
表(结果:3306
、3310
数据库中都创建出该表):
create table payment_info ( id int auto_increment, order_id int, payment_status int, primary key (id) );
- 通过Mycat,插入数据:
insert into payment_info (id, order_id, payment_status) values (1, 101, 0); insert into payment_info (id, order_id, payment_status) values (2, 102, 0); insert into payment_info (id, order_id, payment_status) values (3, 103, 0); insert into payment_info (id, order_id, payment_status) values (4, 104, 0);
3306
:
3310
:
4、按日期(天)分片
1)schema.xml
<table name="login_info" dataNode="dn1,dn2" rule="sharding_by_date"></table>
2)rule.xml
<tableRule name="sharding_by_date"> <rule> <columns>login_date</columns> <algorithm>shardingByDate</algorithm> </rule> </tableRule> <function name="shardingByDate" class="io.mycat.route.function.PartitionByDate"> <property name="dateFormat">yyyy-MM-dd</property> <property name="sBeginDate">2019-01-01</property> <property name="sEndDate">2019-01-04</property> <property name="sPartionDay">2</property> </function>
- 参数:
columns
:分片字段。algorithm
:分片函数。sBeginDate
:开始日期。sEndDate
:结束日期,代表数据达到了这个日期的分片后循环从开始分片插入。sPartionDay
:分区天数,即默认从开始日期算起,每隔2天一个分区。
3)测试
- 重启Mycat服务器,控制窗口。
- 通过Mycat,创建用户信息表
login_info
(结果:两个数据库都创建出该表):
create table login_info ( id int auto_increment, user_id int, login_date date, primary key (id) );
- 通过Mycat,插入数据:
insert into login_info (id, user_id, login_date) values (1, 101, '2019-01-01'); insert into login_info (id, user_id, login_date) values (2, 102, '2019-01-02'); insert into login_info (id, user_id, login_date) values (3, 103, '2019-01-03'); insert into login_info (id, user_id, login_date) values (4, 104, '2019-01-04'); insert into login_info (id, user_id, login_date) values (5, 105, '2019-01-05'); insert into login_info (id, user_id, login_date) values (6, 106, '2019-01-06');
3306
:
3310
:
5.4 全局序列
- 在实现分库分表的情况下,数据库自增主键已经无法保证自增主键的全局唯一。
- 为此,Mycat提供了全局sequence,并且提供了包含本底配置和数据库配置等多种实现方式。
1、本地文件
- 将sequence配置到文件中。
- 优点:本地加载,读取速度较快。
- 缺点:抗风险能力差,Mycat所在主机宕机后,无法读取本地文件。
2、数据库方式(多数使用)
- 利用数据库的一个表来进行计数累加。
- 但是并不是每次生成序列都读写数据库,这样效率太低。Mycat会预加载一部分号段到Mycat的内存中,这样大部分读写序列都是在内存中完成的。如果内存中的号段用完了,Mycat会再向数据库要一次。
- 如果Mycat崩溃了,那内存中的序列岂不是都没了?
- 是的。如果是这样,那么Mycat启动后会向数据库申请新的号段,原有号段会弃用。也就是说,如果Mycat重启,那么损失的是当前的号段没用完的号码,但是不会因此出现主键重复的问题。
1)创建序列脚本
- 在
3306
数据库上创建全局序列表:
create table mycat_sequence( name varchar(50) not null, current_value int not null, increment int not null default 100, primary key (name) ) engine=innodb;
- 在
3306
数据库上创建全局序列所需函数:
delimiter $$ create function mycat_seq_currval(seq_name varchar(50)) returns varchar(64) 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 ; delimiter $$ create function mycat_seq_setval(seq_name varchar(50), value integer) returns varchar(64) deterministic begin update mycat_sequence set current_value= value where name = seq_name; return mycat_seq_currval(seq_name); end $$ delimiter ; delimiter $$ create function mycat_seq_nextval(seq_name varchar(50)) returns varchar(64) deterministic begin update mycat_sequence set current_value=current_value+increment where name = seq_name; return mycat_seq_currval(seq_name); end $$ delimiter ;
- 在
3306
数据库上初始化序列表记录(当前序号为400000,每次Mycat从数据库中取100个值):
insert into mycat_sequence(name, current_value, increment) values ('orders', 400000, 100);
2)修改Mycat配置
- 修改Mycat的
sequence_db_conf.properties
,指定orders
的序列在dn1
,即3306
数据库中:
ORDERS=dn1
- 修改Mycat的
server.xml
,指定全局序列类型(0:本地文件,1:数据库方式,2:时间戳方式):
<property name="sequnceHandlerType">1</property>
3)测试
- 重启Mycat,使用Mycat插入数据(注意:
MYCATSEQ_ORDERS
需要大写):
insert into orders (id, amount, customer_id, order_type) values (next value for MYCATSEQ_ORDERS, 1000, 101, 102);
- 插入多次:
3、时间戳
- 全局序列ID=64位二进制(42(毫秒)+5(机器ID)+5(业务编码)+12(重复累加)),换算成十进制为18位数的long类型,每毫秒可以并发12位二进制的累加。
- 优点:配置简单。
- 缺点:18位id过长。
4、自主生成全局序列
- 在Java项目中自己生成全局序列:
- 根据业务逻辑组合。
- 利用Redis的单线程原子性
incr
来生成序列。
6 基于HA机制的Mycat高可用
- 在实际项目中,Mycat服务也需要考虑高可用性。如果Mycat所在服务器出现宕机,或者Mycat服务故障,需要有备机提供服务,需要考虑Mycat集群。
6.1 高可用方案
- 我们可以使用
HAProxy+Keepalived
配合两台Mycat搭建Mycat集群,实现高可用性。 HAProxy
实现了Mycat多节点的集群高可用和负载均衡,而HAProxy自身的高可用则可以通过Keepalived
来实现。
6.2 安装配置HAProxy
# 1、准备好HAProxy安装包,上传到/opt目录下 # 2、解压到/usr/local/src tar -zxvf haproxy-1.5.18.tar.gz -C /usr/local/src # 3、进入解压后目录 cd /usr/local/src/haproxy-1.5.18 # 4、查看内核版本 uname -r # 5、编译,以下为示例:TARGET为内核版本(3.10.0-514.el7时为linux310),PREFIX为HAProxy安装路径,ARCH为系统位数 make TARGET=linux310 PREFIX=/usr/local/haproxy ARCH=x86_64 # 6、编译完成后,进行安装 make install PREFIX=/usr/local/haproxy # 7、安装完成后,创建目录、HAProxy配置文件 mkdir -p /usr/data/haproxy/ vim /usr/local/haproxy/haproxy.conf # 8、向配置文件中插入配置信息,并保存
- 未完待续……
7 Mycat安全设置
7.1 权限配置
1、user标签权限控制
- 目前Mycat对于中间件的连接控制并没有做太复杂的控制,目前只做了中间件逻辑库级别的读写权限。
- 通过
server.xml
的user
标签进行配置:
<user name="mycat"> <property name="password">123456</property> <property name="schemas">TESTDB</property> </user> <user name="user"> <proprety name="password">user</proprety> <property name="schemas">TESTDB</property> <property name="readOnly">true</property> </user>
- 说明:
name
:应用连接中间件逻辑库的用户名。password
:该用户对应的密码。TESTDB
:应用当前连接的逻辑库中所对应的逻辑表。schemas
中可以配置一个或多个。readOnly
:应用连接中间件逻辑库所具有的权限,默认为false。
2、privileges标签权限控制
- 在
user
标签下的privileges
标签可以对逻辑库(schema)、表(table)进行精细化的DML权限控制。 privileges
标签下的check
属性,如为true开启权限检查,为false不开启,默认为false。- 由于Mycat一个用户的
schemas
属性可配置多个逻辑库(schema),所以privileges的下级节点schema节点同样可配置多个,对多库多表进行细粒度的DML权限控制。
<user name="mycat"> <property name="password">123456</property> <property name="schemas">TESTDB</property> <privileges check="true"> <schama name="TESTDB" dml="1111"> <table name="orders" dml="0000"></table> </schama> </privileges> </user>
dml
:- 0000——禁止insert、update、select、delete。
- 0010——禁止insert、update、delete,可以select。
- 1110——禁止update、select、delete,可以insert。
- 1111——都可以。
7.2 SQL拦截
server.xml
中的firewall
标签用来定义防火墙:firewall
下的whitehost
标签用来定义IP白名单,blacklist
用来定义SQL黑名单。
1、白名单
- 可以通过设置白名单,实现某主机某用户可以访问Mycat,而其他主机用户禁止访问。
<firewall> <whitehost> <host host="192.168.140.128" user="mycat"/> </whitehost> </firewall>
2、黑名单
- 可以通过设置黑名单,实现Mycat对具体SQL操作的拦截,如增删改查等操作的拦截。
<firewall> <blacklist check="true"> <property name="deleteAllow">false</property> </blacklist> </firewall>
- 配置项:
selectAllow
:是否允许执行select语句,默认true。deleteAllow
:是否允许执行delete语句,默认true。updateAllow
:是否允许执行update语句,默认true。insertAllow
:是否允许执行insert语句,默认true。createTableAllow
:是否允许创建表,默认true。setAllow
:是否允许使用set语句,默认true。alertTableAllow
:是否允许执行alter table语句,默认true。dropTableAllow
:是否允许删除表,默认true。commitAllow
:是否允许执行commit操作,默认true。rollbackAllow
:是否允许执行roll back操作,默认true。
8 Mycat监控工具
8.1 Mycat-web简介
- Mycat-web是Mycat可视化运维的管理和监控平台,弥补了Mycat在监控上的空白,帮Mycat分担统计任务和配置管理任务。
- Mycat-web引入了Zookeeper作为配置中心,可以管理多个节点。
- Mycat-web主要管理和监控Mycat的流量、连接、活动线程和内存等,具备IP白名单、邮件警告等模块,还可以统计SQL并分析慢SQL和高频SQL等,为优化SQL提供依据。
- 未完待续……