Mycat最简单搭建1

简单理解为一个mysql中间件,它支持分流、基于心跳的自动故障切换,支持读写分离,支持MySQL主从,基于Nio管理线程的高并发… 
详见官网:http://www.mycat.io/ 

一、为什么需要mysql集群?

一个庞大的分布式系统的性能瓶颈中,最脆弱的就是连接,一个是客户端与后端的连接,另一个是后端与数据库的连接,说白了就是发送端请求太多,接收端能够的接收和处理的请求并不多,在客户端与后端中可以利用类似nginx的负载均衡解决,而在后端与数据库中可以利用类似mycat的负载均衡实现mysql集群,提高mysql的总体性能。

Mycat最简单搭建1_xml

二、准备

1. 两台服务器(最少),一主(master)一从(slave)。这里用vmware里面装的两个rhel 虚拟机演示,每台虚拟机的网络连接方式都设置为桥接模式,两台虚拟机的ip如下

   主:redhat6.5 server1: 192.168.1.117
   从:redhat6.5 server2: 192.168.1.228
 
  1. 虚拟机分别安装好mysql 5.7.22。
  2. jdk1.7以上,这里以jdk-7u67-linux-x64.tar.gz为例。
  3. 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端口)

Mycat最简单搭建1_数据库_02

往数据库里面添加一条数据8

Mycat最简单搭建1_数据库_03

现在切换到117/228连接,看数据过来没

Mycat最简单搭建1_sql_04

 

如果想看看读写分离的细节,可以到 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来演示各种读写分离的情况。

  1. balance=“0”,查看debug日志,走117

Mycat最简单搭建1_数据库_05

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]

 

  1. 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