Mycat搭建负载均衡,读写分离的Mysql集群

Mycat搭建负载均衡,读写分离的Mysql集群

准备环境

1、mysql-5.7.24-linux-glibc2.12-x86_64.tar.gz

2、Mycat-server-1.6.7.4-test-20191113141017-linux.tar.gz

3、Centos7

运行环境

1个mycat,四个mysql实例(两个一主一从)。

下载安装配置运行

1、mysql安装配置运行

参考我的另一篇随笔:从零开始:mysql基于Amoeba的集群搭建

mysql需要创建3307,3308,3309,3310端口,其中,3307(主)和3308(从),3310(主)和3309(从)。

[root@iz2zeaf5jdjve80rjlsjgnz support-files]# ps -aux | grep mysql
root      5708  0.0  0.0 112712   964 pts/0    R+   23:25   0:00 grep --color=auto mysql
root      6218  0.0  0.0 113320  1608 pts/0    S    22:43   0:00 /bin/sh /software/mysql/mysql3310/bin/mysqld_safe --datadir=/software/mysql/mysql3310/data --pid-file=/software/mysql/mysql3310/mysqld.pid
mysql     7008  0.0 14.5 1796424 273876 pts/0  Sl   22:43   0:02 /software/mysql/mysql3310/bin/mysqld --basedir=/software/mysql/mysql3310 --datadir=/software/mysql/mysql3310/data --plugin-dir=/software/mysql/mysql3310/lib/plugin --user=mysql --log-error=iz2zeaf5jdjve80rjlsjgnz.err --open-files-limit=4161 --pid-file=/software/mysql/mysql3310/mysqld.pid --port=3310
root      7434  0.0  0.0 113320  1624 pts/0    S    23:15   0:00 /bin/sh /software/mysql/mysql3307/bin/mysqld_safe --datadir=/software/mysql/mysql3307/data --pid-file=/software/mysql/mysql3307/mysqld.pid
mysql     8214  0.1 15.3 1777900 288340 pts/0  Sl   23:15   0:00 /software/mysql/mysql3307/bin/mysqld --basedir=/software/mysql/mysql3307 --datadir=/software/mysql/mysql3307/data --plugin-dir=/software/mysql/mysql3307/lib/plugin --user=mysql --log-error=iz2zeaf5jdjve80rjlsjgnz.err --open-files-limit=4161 --pid-file=/software/mysql/mysql3307/mysqld.pid --port=3307
root     27817  0.0  0.0 113324  1300 pts/3    S    22:06   0:00 /bin/sh /software/mysql/mysql3308/bin/mysqld_safe --datadir=/software/mysql/mysql3308/data --pid-file=/software/mysql/mysql3308/mysqld.pid
mysql    28625  0.0  3.6 1795656 69588 pts/3   Sl   22:06   0:03 /software/mysql/mysql3308/bin/mysqld --basedir=/software/mysql/mysql3308 --datadir=/software/mysql/mysql3308/data --plugin-dir=/software/mysql/mysql3308/lib/plugin --user=mysql --log-error=iz2zeaf5jdjve80rjlsjgnz.err --open-files-limit=4161 --pid-file=/software/mysql/mysql3308/mysqld.pid --port=3308
root     29106  0.0  0.0 113324  1640 pts/3    S    22:07   0:00 /bin/sh /software/mysql/mysql3309/bin/mysqld_safe --datadir=/software/mysql/mysql3309/data --pid-file=/software/mysql/mysql3309/mysqld.pid
mysql    29916  0.0 15.1 1795660 284968 pts/3  Sl   22:07   0:03 /software/mysql/mysql3309/bin/mysqld --basedir=/software/mysql/mysql3309 --datadir=/software/mysql/mysql3309/data --plugin-dir=/software/mysql/mysql3309/lib/plugin --user=mysql --log-error=iz2zeaf5jdjve80rjlsjgnz.err --open-files-limit=4161 --pid-file=/software/mysql/mysql3309/mysqld.pid --port=3309

2、mycat安装

使用wget命令下载Mycat-server-1.6.7.4-test-20191113141017-linux.tar.gz

mkdir /software/mycat
cd /software/mycat
wget http://dl.mycat.io/1.6.7.4/Mycat-server-1.6.7.4-test-20191113141017-linux.tar.gz

使用tar解压

tar zxvf Mycat-server-1.6.7.4-test-20191113141017-linux.tar.gz

3、mycat配置

mycat配置文件主要有schema.xml和server.xml。

server.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- - - Licensed under the Apache License, Version 2.0 (the "License"); 
	- you may not use this file except in compliance with the License. - You 
	may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 
	- - Unless required by applicable law or agreed to in writing, software - 
	distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 
	WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the 
	License for the specific language governing permissions and - limitations 
	under the License. -->
<!DOCTYPE mycat:server SYSTEM "server.dtd">
<mycat:server xmlns:mycat="http://io.mycat/">
	<system>
	<property name="nonePasswordLogin">0</property> <!-- 0为需要密码登陆、1为不需要密码登陆 ,默认为0,设置为1则需要指定默认账户-->
	<property name="useHandshakeV10">1</property>
	<property name="useSqlStat">0</property>  <!-- 1为开启实时统计、0为关闭 -->
	<property name="useGlobleTableCheck">0</property>  <!-- 1为开启全加班一致性检测、0为关闭 -->
		<property name="sqlExecuteTimeout">300</property>  <!-- SQL 执行超时 单位:秒-->
		<property name="sequnceHandlerType">5</property>
		<!--<property name="sequnceHandlerPattern">(?:(s*nexts+values+fors*MYCATSEQ_(w+))(,|)|s)*)+</property>-->
		<!--必须带有MYCATSEQ_或者 mycatseq_进入序列匹配流程 注意MYCATSEQ_有空格的情况-->
		<property name="sequnceHandlerPattern">(?:(s*nexts+values+fors*MYCATSEQ_(w+))(,|)|s)*)+</property>
	<property name="subqueryRelationshipCheck">false</property> <!-- 子查询中存在关联查询的情况下,检查关联字段中是否有分片字段 .默认 false -->
	<property name="sequenceHanlderClass">io.mycat.route.sequence.handler.HttpIncrSequenceHandler</property>
      <!--  <property name="useCompression">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 | type 2 NettyBufferPool -->
		<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="dataNodeIdleCheckPeriod">300000</property> 5 * 60 * 1000L; //连接空闲检查
			<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">0</property>

		<!--
			单位为m
		-->
        <property name="memoryPageSize">64k</property>

		<!--
			单位为k
		-->
		<property name="spillsFileBufferSize">1k</property>

		<property name="useStreamOutput">0</property>

		<!--
			单位为m
		-->
		<property name="systemReserveMemorySize">384m</property>


		<!--是否采用zookeeper协调切换  -->
		<property name="useZKSwitch">false</property>

		<!-- XA Recovery Log日志路径 -->
		<!--<property name="XARecoveryLogBaseDir">./</property>-->

		<!-- XA Recovery Log日志名称 -->
		<!--<property name="XARecoveryLogBaseName">tmlog</property>-->
		<!--如果为 true的话 严格遵守隔离级别,不会在仅仅只有select语句的时候在事务中切换连接-->
		<property name="strictTxIsolation">false</property>
		
		<property name="useZKSwitch">true</property>
		
	</system>
	
	<!-- 全局SQL防火墙设置 -->
	<!--白名单可以使用通配符%或着*-->
	<!--例如<host host="127.0.0.*" user="root"/>-->
	<!--例如<host host="127.0.*" user="root"/>-->
	<!--例如<host host="127.*" user="root"/>-->
	<!--例如<host host="1*7.*" user="root"/>-->
	<!--这些配置情况下对于127.0.0.1都能以root账户登录-->
	<!--
	<firewall>
	   <whitehost>
	      <host host="1*7.0.0.*" user="root"/>
	   </whitehost>
       <blacklist check="false">
       </blacklist>
	</firewall>
	-->

    <!--使用navicat等软件登录mycat的账户和密码-->
	<user name="mycat" defaultAccount="true">
		<property name="password">mycat</property>
    <!--类似于mysql的数据库名-->
		<property name="schemas">DB1</property>
		<property name="defaultSchema">DB1</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">DB1</property>
		<property name="readOnly">true</property>
		<property name="defaultSchema">DB1</property>
	</user>

</mycat:server>

schema.xml

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">

	<schema name="DB1" checkSQLschema="false" sqlMaxLimit="100">
		<table name="ADMIN" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
	</schema>
	<!-- <dataNode name="dn1$0-743" dataHost="localhost1" database="db$0-743"
		/> -->
	<dataNode name="dn1" dataHost="localhost1" database="DB1" />
	<dataNode name="dn2" dataHost="localhost1" database="DB2" />
	<dataNode name="dn3" dataHost="localhost1" database="DB3" />

	<!--<dataNode name="dn4" dataHost="sequoiadb1" database="SAMPLE" />
	 <dataNode name="jdbc_dn1" dataHost="jdbchost" database="db1" />
	<dataNode	name="jdbc_dn2" dataHost="jdbchost" database="db2" />
	<dataNode name="jdbc_dn3" 	dataHost="jdbchost" database="db3" /> -->
	<dataHost name="localhost1" maxCon="1000" minCon="10" balance="3"
			  writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
		<heartbeat>show slave status</heartbeat>
		<!-- can have multi write hosts -->
		<writeHost host="hostM1" url="localhost:3307" user="root"
				   password="root">
			<readHost host="hosts1" url="localhost:3308" user="root"
					   password="root">
			</readHost>
		</writeHost>
		<writeHost host="hostM2" url="localhost:3310" user="root" 
					password="root">
			<readHost host="hosts2" url="localhost:3309" user="root"
					   password="root">
			</readHost>
		</writeHost>
	</dataHost>

	<!--
		<dataHost name="sequoiadb1" maxCon="1000" minCon="1" balance="0" dbType="sequoiadb" dbDriver="jdbc">
		<heartbeat> 		</heartbeat>
		 <writeHost host="hostM1" url="sequoiadb://1426587161.dbaas.sequoialab.net:11920/SAMPLE" user="jifeng" 	password="jifeng"></writeHost>
		 </dataHost>

	  <dataHost name="oracle1" maxCon="1000" minCon="1" balance="0" writeType="0" 	dbType="oracle" dbDriver="jdbc"> <heartbeat>select 1 from dual</heartbeat>
		<connectionInitSql>alter session set nls_date_format='yyyy-mm-dd hh24:mi:ss'</connectionInitSql>
		<writeHost host="hostM1" url="jdbc:oracle:thin:@127.0.0.1:1521:nange" user="base" 	password="123456" > </writeHost> </dataHost>

		<dataHost name="jdbchost" maxCon="1000" 	minCon="1" balance="0" writeType="0" dbType="mongodb" dbDriver="jdbc">
		<heartbeat>select 	user()</heartbeat>
		<writeHost host="hostM" url="mongodb://192.168.0.99/test" user="admin" password="123456" ></writeHost> </dataHost>

		<dataHost name="sparksql" maxCon="1000" minCon="1" balance="0" dbType="spark" dbDriver="jdbc">
		<heartbeat> </heartbeat>
		 <writeHost host="hostM1" url="jdbc:hive2://feng01:10000" user="jifeng" 	password="jifeng"></writeHost> </dataHost> -->

	<!-- <dataHost name="jdbchost" maxCon="1000" minCon="10" balance="0" dbType="mysql"
		dbDriver="jdbc"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1"
		url="jdbc:mysql://localhost:3306" user="root" password="123456"> </writeHost>
		</dataHost> -->
</mycat:schema>

首先schema标签中,设置的名字和server.xml设置的必须一致,它的子节点table表示设置哪个表,dataNode设置的是下面dataNode标签的name,表示将这个表分片到哪些database,dataHost属性设置的是下面dataHost的name。dataHost中writeHost设置的是3307(主服务器),readHost设置的是3308(从服务器)。第二个writeHost设置的和上面的相同,将writeHost设置的是3310(主服务器),readHost设置的是3308(从服务器)。

其中dataHost的balance、writeType、switchType属性。

balance, switchType, writeType
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",所有读请求随机的分发到writeHost下的readhost执行,writeHost不负担读压力
writeType表示写模式
writeType="0",所有的操作发送到配置的第一个writehost
writeType="1",随机发送到配置的所有writehost
writeType="2",不执行写操作
switchType指的是切换的模式,目前的取值也有4种:
switchType=‘-1‘ 表示不自动切换
switchType=‘1‘ 默认值,表示自动切换
switchType=‘2‘ 基于MySQL主从同步的状态决定是否切换,心跳语句为show slave status,dnindex.properties里面的localhost1的值会发生改变。
switchType=‘3‘基于MySQL galary cluster的切换机制(适合集群)(1.4.1),心跳语句为show status like ‘wsrep%‘。

由于switchType设置的2,所以heartbeat的值是show slave status。

4、mycat运行

cd /software/mycat/mycat/bin
./mycat start

使用navicat连接(默认是8066端口)

QQ截图20191124235323

5、测试心跳

[root@iz2zeaf5jdjve80rjlsjgnz bin]# ./mysql -h127.0.0.1 -umycat -P9066 -pmycat
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 98
Server version: 5.7.24-log MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

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> 

使用show命令

mysql> show @@heartbeat;
+--------+-------+-----------+------+---------+-------+--------+---------+--------------+---------------------+-------+
| NAME   | TYPE  | HOST      | PORT | RS_CODE | RETRY | STATUS | TIMEOUT | EXECUTE_TIME | LAST_ACTIVE_TIME    | STOP  |
+--------+-------+-----------+------+---------+-------+--------+---------+--------------+---------------------+-------+
| hostM1 | mysql | localhost | 3307 |       1 |     0 | idle   |   30000 | 65,22,22     | 2019-11-24 23:57:12 | false |
| hostM2 | mysql | localhost | 3310 |       1 |     0 | idle   |   30000 | 99,33,33     | 2019-11-24 23:57:12 | false |
| hosts1 | mysql | localhost | 3308 |       1 |     0 | idle   |   30000 | 81,33,33     | 2019-11-24 23:57:12 | false |
| hosts2 | mysql | localhost | 3309 |       1 |     0 | idle   |   30000 | 82,30,30     | 2019-11-24 23:57:12 | false |
+--------+-------+-----------+------+---------+-------+--------+---------+--------------+---------------------+-------+
4 rows in set (0.00 sec)

mysql> 

rs_code:表示心跳状态

  • 0:INIT_STATUS 表示初始化状态
  • 1:OK_STATUS 表示正常状态
  • -1:ERROR_STATUS 表示连接出错
  • -2:TIMEOUT_STATUS 表示连接超时

若节点发生故障,会连续进行默认5个周期的检测,心跳连续失败后会变成 -1 。


其中RS_CODE都是1,表示所有mysql实例状态正常。

6、测试分片

mysql> create table admin (id bigint not null primary key,user_id varchar(100),traveldate DATE, fee decimal,days int);
Query OK, 0 rows affected (0.22 sec)
mysql> insert into ADMIN(id,user_id,traveldate,fee,days)  values(1,@@hostname,20160101,100,10);
Query OK, 1 row affected (0.28 sec)
mysql> insert into ADMIN(id,user_id,traveldate,fee,days)  values(5000001,@@hostname,20160101,100,10);
Query OK, 1 row affected (0.18 sec)
mysql> insert into ADMIN(id,user_id,traveldate,fee,days)  values(10000001,@@hostname,20160101,100,10);
Query OK, 1 row affected (0.19 sec)

因为在schema.xml中配置了table的rule属性为auto-sharding-long。

这个属性值在rule.xml的内容为

<tableRule name="auto-sharding-long">
		<rule>
			<columns>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>
	</function>

对应的文件是autopartition-long.txt,打开这个文件

# range start-end ,data node index
# K=1000,M=10000.
0-500M=0
500M-1000M=1
1000M-1500M=2

表示id在0-500M范围内,500M-1000M范围内,1000M-1500M范围内,分别在不同的数据库。

QQ截图20191125001252

7、测试读写分离

查询ADMIN表。

mysql> select * from ADMIN;
+----------+-------------------------+------------+-----+------+
| ID       | USER_ID                 | TRAVELDATE | FEE | DAYS |
+----------+-------------------------+------------+-----+------+
|        1 | iz2zeaf5jdjve80rjlsjgnz | 2016-01-01 | 100 |   10 |
|  5000001 | iz2zeaf5jdjve80rjlsjgnz | 2016-01-01 | 100 |   10 |
| 10000001 | iz2zeaf5jdjve80rjlsjgnz | 2016-01-01 | 100 |   10 |
+----------+-------------------------+------------+-----+------+
3 rows in set (0.48 sec)

关闭3310主服务器。

kill -9 6218 7008

此时心跳测试出现-1。

dnindex.properties的localhost1发生改变,说明已经切换到另一个主服务器(关闭之前是0)。

#update
#Mon Nov 25 23:58:11 CST 2019
localhost1=1

mysql> show @@heartbeat;
+--------+-------+-----------+------+---------+-------+--------+---------+--------------+---------------------+-------+
| NAME   | TYPE  | HOST      | PORT | RS_CODE | RETRY | STATUS | TIMEOUT | EXECUTE_TIME | LAST_ACTIVE_TIME    | STOP  |
+--------+-------+-----------+------+---------+-------+--------+---------+--------------+---------------------+-------+
| hostM1 | mysql | localhost | 3307 |       1 |     0 | idle   |   30000 | 2,2,3        | 2019-11-25 00:23:16 | false |
| hostM2 | mysql | localhost | 3310 |      -1 |     2 | idle   |   30000 | 17,3,3       | 2019-11-25 00:23:16 | false |
| hosts1 | mysql | localhost | 3308 |       1 |     0 | idle   |   30000 | 2,2,2        | 2019-11-25 00:23:16 | false |
| hosts2 | mysql | localhost | 3309 |       1 |     0 | idle   |   30000 | 2,3,3        | 2019-11-25 00:23:16 | false |
+--------+-------+-----------+------+---------+-------+--------+---------+--------------+---------------------+-------+
4 rows in set (0.16 sec)

再次查询ADMIN表。

mysql> select * from ADMIN;
1105 - Table 'DB1.ADMIN' doesn't exist

结论:

1、设置了两个及以上的writeHost后,如果默认的writeHost出现宕机,那么会切换到第二个writeHost,相应的,dnindex.properties的localhost1值会改变,show @@heartbeat;命令查询到的HOST对应的RS_CODE会变成-1。

2、自动切换writeHost之后,第一个writeHost的值不会同步过去,所以再次查询ADMIN表会出现Table 'DB1.ADMIN' doesn't exist。

解决办法:

有四个mysql实例

mysql3307
mysql3308
mysql3309
mysql3310

在schema.xml中配置:

<writeHost Host="mysql3307">
    <readHost Host="mysql3308"></readHost>
</writeHost>
<writeHost Host="mysql3308">
    <readHost Host="mysql3309"></readHost>
</writeHost>
<writeHost Host="mysql3309">
    <readHost Host="mysql3310"></readHost>
</writeHost>

在3307宕机后,3308复制的3307,所以3308的数据和3307是一样的,切换到3308后,3309和3308的数据是同步的,以此类推,可以解决主宕机后数据不同步的问题。

原文地址:https://www.cnblogs.com/lxxxxxxy/p/11932868.html