Mycat最简单搭建1
简单理解为一个mysql中间件,它支持分流、基于心跳的自动故障切换,支持读写分离,支持MySQL主从,基于Nio管理线程的高并发…
详见官网:http://www.mycat.io/
一、为什么需要mysql集群?
一个庞大的分布式系统的性能瓶颈中,最脆弱的就是连接,一个是客户端与后端的连接,另一个是后端与数据库的连接,说白了就是发送端请求太多,接收端能够的接收和处理的请求并不多,在客户端与后端中可以利用类似nginx的负载均衡解决,而在后端与数据库中可以利用类似mycat的负载均衡实现mysql集群,提高mysql的总体性能。
二、准备
1. 两台服务器(最少),一主(master)一从(slave)。这里用vmware里面装的两个rhel 虚拟机演示,每台虚拟机的网络连接方式都设置为桥接模式,两台虚拟机的ip如下
主:redhat6.5 server1: 192.168.1.117
从:redhat6.5 server2: 192.168.1.228
- 虚拟机分别安装好mysql 5.7.22。
- jdk1.7以上,这里以jdk-7u67-linux-x64.tar.gz为例。
- mycat,以Mycat-server-1.6-RELEASE-20161028204710-linux.tar.gz为例。
三、搭建mycat+mysql集群的步骤
1 mysql主从配置
1.1 主虚拟机配置(192.168.1.117)
1.2 从虚拟机配置(192.168.1.228)
1.3 测试
2 配置mycat
2.1 安装jdk
2.2 安装mycat
解压mycat
cd /opt
[root@service1 ~]# cd /opt
[root@service1 opt]# tar -zxvf Mycat-server-1.6-RELEASE-20161028204710-linux.tar.gz
mycat/bin/wrapper-linux-ppc-64
mycat/bin/wrapper-linux-x86-64
....
mycat/logs/
mycat/catlet/
配置环境变量
[root@service1 opt]# vi /etc/profile
尾部添加:
export MYCAT_HOME=/opt/mycat
export PATH=$PATH:$MYCAT_HOME/bin
保存退出
使环境变量生效
[root@service1 opt]# source /etc/profile
3.配置server.xml
到 mycat 的 conf 目录下,修改 server.xml 文件添加 test 和user 用户,test 用户,密码也为 Qaz123$%^,user 用户,密码也为Qaz123$%^。
cd /opt/mycat/conf
vi server.xml
<user name="test">
<property name="password">Qaz123$%^</property>
<property name="schemas">TESTDB</property>
<!-- 表级 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">Qaz123$%^</property>
<property name="schemas">TESTDB</property>
<property name="readOnly">true</property>
</user>
4.配置schema.xml
设置读和写服务器的ip与端口,以及集群策略。这里的 schema 的 name 要与 server.xml 的 schema 保持一致, database 对应的是 mysql 里面已经存在的数据库,也就是说,mycat 的 TESTDB 代理了 主/从虚拟机的 mysql 的 test 数据库
[root@service1 conf]# vi 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="master" database="test1" />
<dataHost name="master" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>show slave status</heartbeat>
<!-- can have multi write hosts -->
<writeHost host="host2" url="192.168.1.117:3306" user="root" password="Qaz123$%^" >
<readHost host="host4" url="192.168.1.228:3306" user="root" password="Qaz123$%^" />
</writeHost>
</dataHost>
</mycat:schema>
show slave status 表示一种集群策略,只适用在一主一从的环境中,当主 down 掉, 从可以充当主和从
<writeHost host="MySQL_M1" url="192.168.237.15:3306" user="root" password="123456">
<readHost host="MySQL_S1" url="192.168.237.15:3308" user="root" password="123456" />
</writeHost>
<writeHost host="MySQL_M2" url="192.168.237.15:3307" user="root" password="123456">
dbDriver 属性
指定连接后端数据库使用的 Driver,目前可选的值有 native 和 JDBC。使用 native 的话,因为这个值执行的是二进制的 mysql 协议,所以可以使用 mysql 和 maridb。其他类型的数据库则需要使用 JDBC 驱动来支持。
从 1.6 版本开始支持 postgresql 的 native 原始协议。
如果使用 JDBC 的话需要将符合 JDBC 4 标准的驱动 JAR 包放到 MYCAT\lib 目录下,并检查驱动 JAR 包中包括如下目录结构的文件:META-INF\services\java.sql.Driver。在这个文件内写上具体的 Driver 类名,例如:com.mysql.jdbc.Driver。
balance属性
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 不负担读压力。注意 balance=3 只在 1.4 及其以后版本有,1.3 没有。
writeType 属性
负载均衡类型,目前的取值有 3 种:
writeType=”0”, 所有写操作发送到配置的第一个 writeHost,第一个挂了切到还生存的第二个writeHost, 重新启动后已切换后的为准,切换记录在配置文件中:dnindex.properties
writeType=”1”,所有写操作都随机的发送到配置的 writeHost
switchType 属性
-1 表示不自动切换
1 默认值,自动切换
2 基于 MySQL 主从同步的状态决定是否切换
心跳语句为 show slave status
根据主从延时切换技术
Mycat1.4开始支持 MySQL主从复制状态绑定的读写分离机制,让读更加安全可靠,配置如下: MyCAT心跳检查语句配置为 show slave status ,dataHost 上定义两个新属性: switchType=”2” 与slaveThreshold=”100”,此时意味着开启MySQL主从复制状态绑定的读写分离与切换机制,Mycat心跳机制通过检测 show slave status 中的 “Seconds_Behind_Master”, “Slave_IO_Running”, “Slave_SQL_Running” 三个字段来确定当前主从同步的状态以及Seconds_Behind_Master 主从复制时延, 当Seconds_Behind_Master>slaveThreshold 时,读写分离筛选器会过滤掉此Slave机器,防止读到很久之前的旧数据,而当主节点宕机后,切换逻辑会检查Slave上的Seconds_Behind_Master是否为0,为0时则表示主从同步,可以安全切换,否则不会切换。
5 启动mycat
cd /opt/mycat/bin
./mycat start
补充:mycat的常用命令
关闭
mycat stop
重启
mycat restart
查看mycat状态
mycat status
6 测试
现在 mycat 可以代理 mysql 了,用 navicat 连接 mycat(8066端口)
往数据库里面添加一条数据8
现在切换到117/228连接,看数据过来没
如果想看看读写分离的细节,可以到 logs 目录下查看日志
/opt/mycat/logs/
tail -f mycat.log
读写分离演示:
事先我们要打开MyCAT的debug模式。# vim log4j.xml,将info修改为debug
<Loggers>
<!--<AsyncLogger name="io.mycat" level="info" includeLocation="true" additivity="false">-->
<!--<AppenderRef ref="Console"/>-->
<!--<AppenderRef ref="RollingFile"/>-->
<!--</AsyncLogger>-->
<asyncRoot level="debug" includeLocation="true">
<AppenderRef ref="Console" />
<AppenderRef ref="RollingFile"/>
</asyncRoot>
</Loggers>
下面通过变换配置文件中的balance值,执行写操作:select * from travelrecord来演示各种读写分离的情况。
- balance=“0”,查看debug日志,走117
2018-09-14 16:24:12.188 DEBUG [$_NIOREACTOR-3-RW] (io.mycat.backend.datasource.PhysicalDBPool.getRWBanlanceCon(PhysicalDBPool.java:456)) - select read source host2 for dataHost:master
2018-09-14 16:24:12.188 DEBUG [$_NIOREACTOR-3-RW] (io.mycat.backend.mysql.nio.MySQLConnection.synAndDoExecute(MySQLConnection.java:448)) - con need syn ,total syn cmd 1 commands SET names gbk
;schema change:false con:MySQLConnection [id=8, lastTime=1536913452188, user=root, schema=test1, old shema=test1, borrowed=true, fromSlaveDB=false, threadId=311, charset=gbk, txIsolation=3, autocommit=true, attachment=dn1{select * from test1}, respHandler=SingleNodeHandler [node=dn1{select * from test1}, packetId=0], host=192.168.1.117, port=3306, statusSync=null, writeQueue=0, modifiedSQLExecuted=false]2018-09-14 16:24:12.189 DEBUG [$_NIOREACTOR-0-RW] (io.mycat.server.NonBlockingSession.releaseConnection(NonBlockingSession.java:341)) - release connection MySQLConnection [id=8, lastTime=1536
913452186, user=root, schema=test1, old shema=test1, borrowed=true, fromSlaveDB=false, threadId=311, charset=gbk, txIsolation=3, autocommit=true, attachment=dn1{select * from test1}, respHandler=SingleNodeHandler [node=dn1{select * from test1}, packetId=12], host=192.168.1.117, port=3306,
(2)balance=“2”,查看debug日志,走117 or 228
2018-09-14 16:34:06.791 DEBUG [$_NIOREACTOR-1-RW] (io.mycat.backend.datasource.PhysicalDBPool.getRWBanlanceCon(PhysicalDBPool.java:456)) - select read source host2 for dataHost:master
2018-09-14 16:34:06.792 DEBUG [$_NIOREACTOR-2-RW] (io.mycat.server.NonBlockingSession.releaseConnection(NonBlockingSession.java:341)) - release connection MySQLConnection [id=10, lastTime=153
6914046773, user=root, schema=test1, old shema=test1, borrowed=true, fromSlaveDB=false, threadId=328, charset=gbk, txIsolation=3, autocommit=true, attachment=dn1{select * from test1}, respHandler=SingleNodeHandler [node=dn1{select * from test1}, packetId=12], host=192.168.1.117, port=3306, statusSync=null, writeQueue=0, modifiedSQLExecuted=false]
2018-09-14 16:34:07.206 DEBUG [$_NIOREACTOR-1-RW] (io.mycat.backend.datasource.PhysicalDBPool.getRWBanlanceCon(PhysicalDBPool.java:456)) - select read source host4 for dataHost:master
2018-09-14 16:34:07.206 DEBUG [$_NIOREACTOR-1-RW] (io.mycat.backend.mysql.nio.MySQLConnection.synAndDoExecute(MySQLConnection.java:448)) - con need syn ,total syn cmd 1 commands SET names gbk
;schema change:false con:MySQLConnection [id=12, lastTime=1536914047206, user=root, schema=test1, old shema=test1, borrowed=true, fromSlaveDB=true, threadId=66, charset=gbk, txIsolation=3, autocommit=true, attachment=dn1{select * from test1}, respHandler=SingleNodeHandler [node=dn1{select * from test1}, packetId=0], host=192.168.1.228, port=3306, statusSync=null, writeQueue=0, modifiedSQLExecuted=false]
- balance=“3”,查看debug日志,走228
2018-09-14 16:42:33.258 DEBUG [$_NIOREACTOR-3-RW] (io.mycat.backend.datasource.PhysicalDBPool.getRWBanlanceCon(PhysicalDBPool.java:456)) - select read source host4 for dataHost:master
2018-09-14 16:42:33.259 DEBUG [$_NIOREACTOR-1-RW] (io.mycat.server.NonBlockingSession.releaseConnection(NonBlockingSession.java:341)) - release connection MySQLConnection [id=20, lastTime=153
6914553254, user=root, schema=test1, old shema=test1, borrowed=true, fromSlaveDB=true, threadId=82, charset=gbk, txIsolation=3, autocommit=true, attachment=dn1{select * from test1}, respHandler=SingleNodeHandler [node=dn1{select * from test1}, packetId=12], host=192.168.1.228, port=3306, statusSync=null, writeQueue=0, modifiedSQLExecuted=false]2018-09-14 16:42:33.260 DEBUG [$_NIOREACTOR-1-RW] (io.mycat.backend.datasource.PhysicalDatasource.releaseChannel(PhysicalDatasource.java:442)) - release channel MySQLConnection [id=20, lastTi
me=1536914553254, user=root, schema=test1, old shema=test1, borrowed=true, fromSlaveDB=true, threadId=82, charset=gbk, txIsolation=3, autocommit=true, attachment=null, respHandler=null, host=192.168.1.228, port=3306, statusSync=null, writeQueue=0, modifiedSQLExecuted=false]
(4)MyCAT插入测试数据:mysql> insert into test1 values(10,10);
查看debug日志,写操作走117
2018-09-14 16:45:02.082 DEBUG [$_NIOREACTOR-0-RW] (io.mycat.server.NonBlockingSession.releaseConnection(NonBlockingSession.java:341)) - release connection MySQLConnection [id=8, lastTime=1536
914702054, user=root, schema=test1, old shema=test1, borrowed=true, fromSlaveDB=false, threadId=348, charset=gbk, txIsolation=3, autocommit=true, attachment=dn1{insert into test1 values(10,10)}, respHandler=SingleNodeHandler [node=dn1{insert into test1 values(10,10)}, packetId=1], host=192.168.1.117, port=3306, statusSync=null, writeQueue=0, modifiedSQLExecuted=true]
------------------------------------------------主主问题----------------------------------------
为了区分是主库还是从库插入的数据,M1、M2设置不同自增步长,通过id的奇偶性来判断,避免通过查debug日志的方式来区分。
MySQL_M1:auto_increment_increment=2,auto_increment_offset=1
MySQL_M2:auto_increment_increment=2,auto_increment_offset=2
(1)M1、M2两个节点均正常的情况下,设想情况:MyCAT选择M1插入数据
插入两组测试数据:
mysql> insert into travelrecord(org_code,test_name) values("china86","tom");
mysql> insert into travelrecord(org_code,test_name) values("china86","jack");
mysql> select *from travelrecord;
+----+----------+-----------+
| id | org_code | test_name |
+----+----------+-----------+
| 1 | china86 | tom |
| 3 | china86 | jack |
+----+----------+-----------+
id为奇数,故写入的数据库是M1,符合预期
(2)M1宕机,设想情况:MyCAT切换至M2作为写入节点
此时,我们模拟故障,手动停止3306上的MySQL实例
[root@237_15 conf]# /etc/init.d/mysql3306 stop
Shutting down MySQL............ SUCCESS!
再往MyCAT内插入两组数据:
mysql> insert into travelrecord(org_code,test_name) values("china86","marry");
mysql> insert into travelrecord(org_code,test_name) values("china86","ellen");
mysql> select *from travelrecord;
+----+----------+-----------+
| id | org_code | test_name |
+----+----------+-----------+
| 1 | china86 | tom |
| 3 | china86 | jack |
| 4 | china86 | marry |
| 6 | china86 | ellen |
+----+----------+-----------+
id为偶数,故写入的是M2,符合预期
再截取一段debug日志,确认写入的实例端口为3307(MySQL_M2)
08/17 22:10:40.330 DEBUG [$_NIOREACTOR-0-RW] (MultiNodeQueryHandler.java:171) -received ok response ,executeResponse:true from MySQLConnection [id=7, lastTime=1502979040315, user=root, schema=testdb2, old shema=testdb2, borrowed=true, fromSlaveDB=false, threadId=5, charset=utf8, txIsolation=3, autocommit=true, attachment=dn2{insert into travelrecord(org_code,test_name) values("china86","ellen")}, respHandler=org.opencloudb.mysql.nio.handler.MultiNodeQueryHandler@7450171c, host=192.168.237.15, port=3307, statusSync=null, writeQueue=0, modifiedSQLExecuted=true]
(3)M1恢复正常,设想情况:MyCAT继续使用之前的M2作为写入节点
重启3306上的实例(MySQL_M1),再往MyCAT内插入两条数据:
mysql> insert into travelrecord(org_code,test_name) values("china86","tiger");
mysql> insert into travelrecord(org_code,test_name) values("china86","rabbit");
mysql> select *from travelrecord;
+----+----------+-----------+
| id | org_code | test_name |
+----+----------+-----------+
| 1 | china86 | tom |
| 3 | china86 | jack |
| 4 | china86 | marry |
| 6 | china86 | ellen |
| 8 | china86 | tiger |
| 10 | china86 | rabbit |
+----+----------+-----------+
id为偶数,故虽然M1已重启,但写入的数据库仍是M2,符合预期
再截取一段debug日志进行确认,写入的实例端口仍为3307(MySQL_M2)
08/17 22:17:20.549 DEBUG [$_NIOREACTOR-0-RW] (MultiNodeQueryHandler.java:171) -received ok response ,executeResponse:true from MySQLConnection [id=5, lastTime=1502979440533, user=root, schema=testdb1, old shema=testdb1, borrowed=true, fromSlaveDB=false, threadId=4, charset=utf8, txIsolation=3, autocommit=true, attachment=dn1{insert into travelrecord(org_code,test_name) values("china86","tiger")}, respHandler=org.opencloudb.mysql.nio.handler.MultiNodeQueryHandler@7cacca01, host=192.168.237.15, port=3307, statusSync=null, writeQueue=0, modifiedSQLExecuted=true]
...
08/17 22:17:31.698 DEBUG [$_NIOREACTOR-0-RW] (MultiNodeQueryHandler.java:171) -received ok response ,executeResponse:true from MySQLConnection [id=5, lastTime=1502979451682, user=root, schema=testdb1, old shema=testdb1, borrowed=true, fromSlaveDB=false, threadId=4, charset=utf8, txIsolation=3, autocommit=true, attachment=dn1{insert into travelrecord(org_code,test_name) values("china86","rabbit")}, respHandler=org.opencloudb.mysql.nio.handler.MultiNodeQueryHandler@73118f1d, host=192.168.237.15, port=3307, statusSync=null, writeQueue=0, modifiedSQLExecuted=true]
M1从故障中恢复,M2可用,重启MyCAT,设想情况:MyCAT还是会继续沿用M2作为写入节点
重启MyCAT,再插入两条数据,写入的数据库还是M2,符合预期
mysql> insert into travelrecord(org_code,test_name) values("china86","orange");
mysql> insert into travelrecord(org_code,test_name) values("china86","apple");
mysql> select *from travelrecord;
+----+----------+-----------+
| id | org_code | test_name |
+----+----------+-----------+
| 1 | china86 | tom |
| 3 | china86 | jack |
| 4 | china86 | marry |
| 6 | china86 | ellen |
| 8 | china86 | tiger |
| 10 | china86 | rabbit |
| 12 | china86 | orange |
| 14 | china86 | apple |
+----+----------+-----------+
(5)修改writeHost的index信息,指定写入节点为M1,设想情况:MyCAT将写入节点切换至M1
打开conf目录下的dnindex.properties文件
#update
#Thu Aug 17 22:08:16 CST 2017
237_15=1
可以看到当前所用的writeHost的index为1(0表示使用的是配置文件schema.xml中第一个writeHost,1代表了第二个,以此类推)
我们尝试修改index为0,并重启MyCAT
再插入两组数据,发现id已经变为奇数,说明写入的数据库重新划给MySQL_M1了,符合预期
mysql> insert into travelrecord(org_code,test_name) values("china86","black");
mysql> insert into travelrecord(org_code,test_name) values("china86","white");
mysql> select *from travelrecord;
+----+----------+-----------+
| id | org_code | test_name |
+----+----------+-----------+
| 1 | china86 | tom |
| 3 | china86 | jack |
| 4 | china86 | marry |
| 6 | china86 | ellen |
| 8 | china86 | tiger |
| 10 | china86 | rabbit |
| 12 | china86 | orange |
| 14 | china86 | apple |
| 15 | china86 | black |
| 17 | china86 | white |
+----+----------+-----------+
注:可以通过管理端口连接MyCAT,执行show @@heartbeat,若 RS_CODE=-1 表示该节点心跳检测出错。
———————————————————分割线—————————————————————-
mysql的问题其实是由于一系列的软肋决定的,所以不得不利用中间件或者其它方案去解决,包括:
在强制约束和事务与全文索引之间做出选择(InnoDb vs MyISAM)
在客户机代码中“模拟”事务是不容易的
如果不执行约束,就很容易得到不一致的db状态
如果没有全文搜索,会变得疯狂,比如% y %
必须在更新触发器之前创建检查约束的错误
当数据变得太大时,Mysql的承受能力就不妙了
Mysql创建的执行计划效率低下
Mysql有超过多个连接的问题(最好说多个连接)
…
但是! Oracle是所有这些问题的解决方案,它是一个完整的DBMS:事务、检查合同、视图的很多选项、全文搜索…
所以问题的本质是:成本!,mysql 很讨厌,却又不得不用!
[root@service1 bin]# cd /opt/mycat/conf/
[root@service1 conf]# vi schema.xml
[root@service1 conf]# cd /opt/mycat/bin
[root@service1 bin]# ./mycat start
INSERT INTO test2 VALUES (10);
[root@service1 logs]# tail -f mycat.log