1 MySQL 集群
1.5 MySQL 中间件代理服务器
1.5.2 数据库切片
在整个 IT 系统架构中,数据库是非常重要,通常又是访问压力较大的一个服务,除了在程序开发的本身做优化,如:SQL语句优化、代码优化,数据库的处理本身优化也是非常重要的。主从、热备、分表分库等都是系统发展迟早会遇到的技术问题问题。Mycat 是一个广受好评的数据库中间件,已经在很多产品上进行使用了。
Mycat 是一个开源的分布式数据库系统,是一个实现了 MySQL 协议的服务器,前端用户可以把它看作是一个数据库代理(类似于Mysql Proxy),用 MySQL 客户端工具和命令行访问,而其后端可以用 MySQL 原生协议与多个 MySQL 服务器通信,也可以用 JDBC 协议与大多数主流数据库服务器通信,其核心功能是分表分库,即将一个大表水平分割为 N 个小表,存储在后端 MySQL 服务器里或者其他数据库里。
Mycat 发展到目前的版本,已经不是一个单纯的 MySQL 代理了,它的后端可以支持 MySQL、SQL Server、Oracle、DB2、PostgreSQL等主流数据库,也支持 MongoDB 这种新型 NoSQL 方式的存储,未来还会支持更多类型的存储。而在最终用户看来,无论是哪种存储方式,在 MyCat 里,都是一个传统的数据库表,支持标准的 SQL 语句进行数据的操作,这样一来,对前端业务系统来说,可以大幅降低开发难度,提升开发速度。
Mycat 可以简单概括为:
一个彻底开源的,面向企业应用开发的大数据库集群
支持事务,ACID,可以替代 MySQL 的加强版数据库
一个可以视为 MySQL 集群的企业级数据库,用来替代昂贵的 Oracle 集群
一个融合内存缓存技术,NoSQL技术,HDFS 大数据的新型 SQL Server
结合传统数据库和新型分布式数据仓库的新一代企业级数据库产品
一个新颖的数据库中间件产品
官方网站
http://www.mycat.org.cn/
Mycat 关键特性
支持SQL92标准
遵守MySQL 原生协议,跨语言,跨平台,跨数据库的通用中间件代理
基于心跳的自动故障切换,支持读写分离,支持MySQL主从,以及galera cluster集群
支持Galera for MySQL集群,Percona Cluster或者MariaDB cluster
基于Nio实现,有效管理线程,高并发问题
支持数据的多片自动路由与聚合,支持sum,count,max等常用的聚合函数,支持跨库分页
支持单库内部任意join,支持跨库2表join,甚至基于caltlet的多表join
支持通过全局表,ER关系的分片策略,实现了高效的多表join查询
支持多租户方案
支持分布式事务(弱xa)
支持全局序列号,解决分布式下的主键生成问题
分片规则丰富,插件化开发,易于扩展
强大的web,命令行监控
支持前端作为mysq通用代理,后端JDBC方式支持Oracle、DB2、SQL Server 、 mongodb 、巨杉
支持密码加密
支持服务降级
支持IP白名单
支持SQL黑名单、sql注入攻击拦截
支持分表(1.6)
集群基于ZooKeeper管理,在线升级,扩容,智能优化,大数据处理(2.0开发版)
为什么要使用 MyCat
这里要先搞清楚 Mycat 和 MySQL 的区别(Mycat的核心作用)。我们可以把上层看作是对下层的抽象,例如操作系统是对各类计算机硬件的抽象。那么我们什么时候需要抽象?假如只有一种硬件的时候,我们需要开发一个操作系统吗?再比如一个项目只需要一个人完成的时候不需要 leader,但是当需要几十人完成时,就应该有一个管理者,发挥沟通协调等作用,而这个管理者对于他的上层来说就是对项目组的抽象。
同样的,当我们的应用只需要一台数据库服务器的时候我们并不需要 Mycat,而如果你需要分库甚至分表,这时候应用要面对很多个数据库的时候,这个时候就需要对数据库层做一个抽象,来管理这些数据库,而最上面的应用只需要面对一个数据库层的抽象或者说数据库中间件就好了,这就是 Mycat 的核心作用。所以可以这样理解:数据库是对底层存储文件的抽象,而 Mycat 是对数据库的抽象。
Mycat 工作原理
Mycat 的原理中最重要的一个动词是"拦截",它拦截了用户发送过来的 SQL 语句,首先对 SQL 语句做了一些特定的分析:如分片分析,路由分析,读写分离分析,缓存分析等,然后将此 SQL 发往后端的真实数据库,并将返回的结果做适当的处理,最终再返回给用户。
Mycat 应用场景
Mycat 适用的场景很丰富,以下是几个典型的应用场景。
单纯的读写分离,此时配置最为简单,支持读写分离,主从切换
分表分库,对于超过1000 万的表进行分片,最大支持 1000 亿的单表分片
多租户应用,每个应用一个库,但应用程序只连接 Mycat,从而不改造程序本身,实现多租户化
报表系统,借助于 Mycat 的分表能力,处理大规模报表的统计
替代 Hbase,分析大数据
作为海量数据实时查询的一种简单有效方案,比如 100 亿条频繁查询的记录需要在3秒内查询出来结果,除了基于主键的查询,还可能存在范围查询或其他属性查询,此时 Mycat 可能是最简单有效的选择
Mycat 不适合的应用场景
设计使用 Mycat 时有非分片字段查询,请慎重使用 Mycat,可以考虑放弃!
设计使用 Mycat 时有分页排序,请慎重使用 Mycat,可以考虑放弃!
设计使用 Mycat 时如果要进行表 JOIN 操作,要确保表的关联字段具有相同的数据分布,否则请慎重使用 Mycat,可以考虑放弃!
设计使用 Mycat 时如果有分布式事务,得先看是否得保证事务的强一致性,否则请慎重使用 Mycat,可以考虑放弃!
MyCat 的高可用
需要注意: 在生产环境中, Mycat 节点最好使用双节点, 即双机热备环境, 防止Mycat这一层出现单点故障。
可以使用的高可用集群方式有:
Keepalived+Mycat+Mysql
Keepalived+LVS+Mycat+Mysql
Keepalived+Haproxy+Mycat+Mysql
Mycat 安装
#mycat 是基于 java 语言开发,先要安装 java 环境
[root@rocky8 ~]# yum install -y java
[root@rocky8 ~]# java -version
openjdk version "1.8.0_362"
OpenJDK Runtime Environment (build 1.8.0_362-b09)
OpenJDK 64-Bit Server VM (build 25.362-b09, mixed mode)
#下载
[root@rocky8 ~]# wget http://dl.mycat.org.cn/1.6.7.6/20220524101549/Mycat-server-1.6.7.6-release-20220524173810-linux.tar.gz
#解压
[root@rocky8 ~]# mkdir /apps
[root@rocky8 ~]# tar xf Mycat-server-1.6.7.6-release-20220524173810-linux.tar.gz -C /apps
#查看
[root@rocky8 ~]# ls /apps/mycat/
bin catlet conf lib logs version.txt
bin #mycat命令,启动、重启、停止等
catlet #扩展功能目录,默认为空
conf #配置文件目录
lib #引用的jar包
logs #日志目录,默认为空
version.txt #版本说明文件
#日志
logs/wrapper.log #mycat启动日志
logs/mycat.log #mycat详细工作日志
#常用配置文件
conf/server.xml #Mycat 软件本身相关的配置文件,设置账号、参数等
conf/schema.xml #对应的物理数据库和数据库表的配置,读写分离、高可用、分布式策略定制、节点控制
conf/rule.xml #Mycat分片(分库分表)规则配置文件,记录分片规则列表、使用方法等
#写path
[root@rocky8 ~]# vim /etc/profile.d/mycat.sh
PATH=/apps/mycat/bin:$PATH
[root@rocky8 ~]# source /etc/profile.d/mycat.sh
#启动
[root@rocky8 ~]# mycat start
Starting Mycat-server...
#连接mycat
[root@rocky8 ~]# mysql -uroot -p123456 -h 127.0.0.1 -P8066
......
配置文件说明
server.xml #存放Mycat软件本身相关的配置文件,比如:连接Mycat的用户,密码,数据库名称等
#相关配置项
user #用户配置节点
name #客户端登录MyCAT的用户名
password #客户端登录MyCAT的密码
schemas #数据库名,这里会和schema.xml中的配置关联,多个用逗号分开
privileges #配置用户针对表的增删改查的权限
readOnly #mycat逻辑库所具有的权限。true为只读,false为读写都有,默认为false
server.xml文件里登录mycat的用户名和密码可以任意定义,这个账号和密码是为客户机登录 mycat 时使用的账号信息
逻辑库名(如上面的TESTDB,也就是登录mycat后显示的库名,切换这个库之后,显示的就是代理的真实mysql数据库的表)要在schema.xml里面也定义,否则会导致mycat服务启动失败!
这里只定义了一个标签,所以把多余的都注释了。如果定义多个标签,即设置多个连接mycat的用户名和密码,那么就需要在schema.xml文件中定义多个对应的库!
schema.xml #是最主要的配置项,此文件关联mysql读写分离策略,读写分离、分库分表策略、分片节点都是在此文件中配置的
#MyCat作为中间件,它只是一个代理,本身并不进行数据存储,需要连接后端的MySQL物理服务器,此文件就是用来连接 MySQL 服务器的
schema #数据库设置,此数据库为逻辑数据库,name与server.xml中schema对应
dataNode #分片信息,也就是分库相关配置
dataHost #物理数据库,真正存储数据的数据库
#name属性唯一标识dataHost标签,供上层的标签使用。
#maxCon属性指定每个读写实例连接池的最大连接。也就是说,标签内嵌套的writeHost、readHost标签都会使用这个属性的值来实例化出连接池的最大连接数
#minCon属性指定每个读写实例连接池的最小连接,初始化连接池的大小
#每个节点的属性逐一说明
schema
name #逻辑数据库名,与server.xml中的schema对应
checkSQLschema #数据库前缀相关设置,这里为false
sqlMaxLimit #select 时默认的limit,避免查询全表
table
name #表名,物理数据库中表名
dataNode #表存储到哪些节点,多个节点用逗号分隔。节点为下文dataNode设置的name
primaryKey #主键字段名,自动生成主键时需要设置
autoIncrement #是否自增
rule #分片规则名,具体规则下文rule详细介绍
dataNode
name #节点名,与table中dataNode对应
datahost #物理数据库名,与datahost中name对应
database #物理数据库中数据库名
dataHost
name #物理数据库名,与dataNode中dataHost对应
balance #均衡负载的方式
writeType #写入方式
dbType #数据库类型
heartbeat #心跳检测语句,注意语句结尾的分号要加
schema.xml 文件中有三点需要注意:balance="1",writeType="0",switchType="1"
schema.xml 中的 balance 的取值决定了负载均衡对非事务内的读操作的处理。balance 属性负载均衡类型,目前的取值有 4 种:
balance="0":不开启读写分离机制,所有读操作都发送到当前可用的 writeHost 上,即读请求仅发送到 writeHost 上。
balance="1":一般用此模式,读请求随机分发到当前 writeHost 对应的 readHost 和 standby 的 writeHost 上。即全部的 readHost 与stand by writeHost 参与 select 语句的负载均衡,简单的说,当双主双从模式(M1 ->S1 , M2->S2,并且 M1 与 M2 互为主备),正常情况下, M2,S1, S2都参与 select语句的负载均衡。
balance="2":读请求随机分发到当前 dataHost 内所有的 writeHost 和 readHost 上。即所有读操作都随机的在 writeHost, readhost 上分发。
balance="3":读请求随机分发到当前 writeHost 对应的 readHost上。即所有读请求随机的分发到 wiriterHost 对应的 readhost 执行, writerHost 不负担读压力,注意 balance=3 只在 1.4 及其以后版本有,1.3 没有。
writeHost 和 readHost 标签
这两个标签都指定后端数据库的相关配置给 mycat,用于实例化后端连接池。
唯一不同的是:writeHost 指定写实例、readHost 指定读实例,组合这些读写实例来满足系统的要求。
在一个 dataHost 内可以定义多个 writeHost 和 readHost。但是,如果 writeHost 指定的后端数据库宕机,那么这个 writeHost 绑定的所有 readHost 都将不可用。另一方面,由于这个 writeHost 宕机系统会自动的检测到,并切换到备用的 writeHost 上去。
注意:
Mycat 主从分离只是在读的时候做了处理,写入数据的时候,只会写入到 writehost,需要通过 mycat 的主从复制将数据复制到 readhost。
读写分离实现
主机清单
前置工作:关闭 selinux,关闭 firewalld 防火墙
配置 MySQL 主从
#master节点配置
[root@rocky8 ~]# cat /etc/my.cnf
[client-server]
......
!includedir /etc/my.cnf.d
[mysqld]
server-id=177
log-bin=/data/mysql/logbin/mysql-bin
#启动服务
[root@rocky8 ~]# systemctl start mysqld
#查看
mysql> show master logs;
+------------------+-----------+-----------+
| Log_name | File_size | Encrypted |
+------------------+-----------+-----------+
| mysql-bin.000001 | 157 | No |
+------------------+-----------+-----------+
1 row in set (0.00 sec)
#slave节点配置
[root@rocky8 ~]# cat /etc/my.cnf
[client-server]
......
!includedir /etc/my.cnf.d
[mysqld]
server-id=183
read-only
log-bin=/data/mysql/logbin/mysql-bin
#启动服务
[root@rocky8 ~]# systemctl restart mysqld
#配置主从
mysql> CHANGE MASTER TO
-> MASTER_HOST='10.0.0.177',
-> MASTER_USER='repluser',
-> MASTER_PASSWORD='123456',
-> MASTER_PORT=3306,
-> MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=157;
Query OK, 0 rows affected, 9 warnings (0.01 sec)
mysql> start slave;
Query OK, 0 rows affected, 1 warning (0.01 sec)
mysql> show slave status\G
......
#主从测试-master节点写入数据
mysql> create database db1;
Query OK, 1 row affected (0.01 sec)
mysql> use db1;
Database changed
#创建表
mysql> CREATE TABLE stu (
-> id int UNSIGNED AUTO_INCREMENT PRIMARY KEY,
-> name VARCHAR(20) NOT NULL,
-> age tinyint UNSIGNED,
-> gender ENUM('M','F') default 'M'
-> )ENGINE=InnoDB;
Query OK, 0 rows affected (0.01 sec)
#写数据
mysql> insert into stu(name,age)values('tom',10);
Query OK, 1 row affected (0.03 sec)
#slave节点上查看
mysql> show databases like '%db1%';
+------------------+
| Database (%db1%) |
+------------------+
| db1 |
+------------------+
1 row in set (0.00 sec)
mysql> select * from db1.stu;
+----+------+------+--------+
| id | name | age | gender |
+----+------+------+--------+
| 1 | tom | 10 | M |
+----+------+------+--------+
1 row in set (0.00 sec)
配置 Mycat
#mycat 是基于 java 语言开发,先要安装 java 环境
[root@rocky8 ~]# yum install -y java
[root@rocky8 ~]# java -version
openjdk version "1.8.0_362"
OpenJDK Runtime Environment (build 1.8.0_362-b09)
OpenJDK 64-Bit Server VM (build 25.362-b09, mixed mode)
#下载
[root@rocky8 ~]# wget http://dl.mycat.org.cn/1.6.7.6/20220524101549/Mycat-server-1.6.7.6-release-20220524173810-linux.tar.gz
#解压
[root@rocky8 ~]# mkdir /apps
[root@rocky8 ~]# tar xf Mycat-server-1.6.7.6-release-20220524173810-linux.tar.gz -C /apps
#写path
[root@rocky8 ~]# vim /etc/profile.d/mycat.sh
PATH=/apps/mycat/bin:$PATH
[root@rocky8 ~]# source /etc/profile.d/mycat.sh
#启动
[root@rocky8 ~]# mycat start
Starting Mycat-server...
#查看运行状态
[root@rocky86 ~]# mycat status
Mycat-server is running (33531).
#查看默认用户名和密码
[root@rocky86 ~]# cat /apps/mycat/conf/server.xml
<user name="root" defaultAccount="true">
<property name="password">123456</property>
<property name="schemas">TESTDB</property>
<property name="defaultSchema">TESTDB</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>
#安装mysql客户端
[root@rocky86 ~]# yum install mysql -y
#连接mycat
[root@rocky8 ~]# mysql -uroot -p123456 -h 127.0.0.1 -P8066
......
#查看
mysql> show databases;
+----------+
| DATABASE |
+----------+
| TESTDB |
+----------+
1 row in set (0.00 sec)
在后端数据库中创建供 Mycat 连接的账号
#在master节点上创建账号并授权,该账号会被同步到 slave 节点
mysql> create user 'mycater'@'10.0.0.%' IDENTIFIED BY '123456';
Query OK, 0 rows affected (0.01 sec)
mysql> GRANT ALL ON db1.* TO 'mycater'@'10.0.0.%';
Query OK, 0 rows affected (0.01 sec)
mysql> flush privileges;
Query OK, 0 rows affected (0.01 sec)
修改 server.xml 配置 mycat 连接后端数据库的账号密码
[root@rocky86 ~]# vim /apps/mycat/conf/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="useHandshakeV10">1</property><property name="serverPort">3306</property> #mycat 监听的端口从8066改成3306</system><user name="root" defaultAccount="true"> #客户端连接mycat的配置<property name="password">123456</property><property name="schemas">db1</property><property name="defaultSchema">db1</property></user></mycat:server>
修改 schema.xml 实现读写分离策略
[root@rocky86 ~]# vim /apps/mycat/conf/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" dataNode="dn1"></schema><dataNode name="dn1" dataHost="localhost1" database="db1" /><dataHost name="localhost1" maxCon="1000" minCon="10" balance="1"writeType="0" dbType="mysql" dbDriver="native" switchType="1"slaveThreshold="100"><heartbeat>select user();</heartbeat><writeHost host="host1" url="10.0.0.177:3306" user="mycater"password="123456"><readHost host="host2" url="10.0.0.183:3306" user="mycater"password="123456" /></writeHost></dataHost></mycat:schema>
#重启mycat 服务
[root@rocky86 ~]# mycat restart
Stopping Mycat-server...
Stopped Mycat-server.
Starting Mycat-server...
#连接
[root@rocky86 ~]# mysql -uroot -p123456 -h127.1
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 1
Server version: 5.6.29-mycat-1.6.7.6-release-20220524173810 MyCat Server
(OpenCloudDB)
Copyright (c) 2000, 2022, 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> show databases;
+----------+
| DATABASE |
+----------+
| db1 |
+----------+
1 row in set (0.01 sec)
mysql> show tables from db1;
+---------------+
| Tables_in_db1 |
+---------------+
| stu |
+---------------+
1 row in set (0.14 sec)
#验证读写分离,别在master,slave节点上开启通用日志
[root@rocky8 ~]# vim /etc/my.cnf
......
general_log
#重启
[root@rocky8 ~]# systemctl restart mysqld.service
#查看通用日志,master,slave节点都有,文件名与机名同名
[root@rocky8 ~]# tail -n 3 /var/lib/mysql/rocky8.log
2024-10-18T15:36:47.569864Z 8 Query select user() #每10S一次,这是配置在 schema.xml 中的 heartbeat
2024-10-18T15:36:57.535337Z 8 Query select user() #用于心跳检测,判断后端 mysql 服务是否在线
2024-10-18T15:37:07.542357Z 8 Query select user()
测试
#mycat 客户端执行查询
mysql> show tables from db1;
+---------------+
| Tables_in_db1 |
+---------------+
| stu |
+---------------+
1 row in set (0.00 sec)
mysql> select * from db1.stu;
+----+------+------+--------+
| id | name | age | gender |
+----+------+------+--------+
| 1 | tom | 10 | M |
+----+------+------+--------+
1 row in set (0.00 sec)
#查看slave 节点上的通用日志
[root@rocky8 ~]# cat /var/lib/mysql/rocky8.log | grep db1
2024-10-18T15:50:38.078865Z 19 Query show tables from db1
2024-10-18T15:50:39.735313Z 18 Query select * from db1.stu
#mycat 客户端执行写操作
mysql> insert into db1.stu(name,age,gender)values('jerry',20,'F');
Query OK, 1 row affected (0.08 sec)
#查看 master 节点上的通用日志
[root@rocky8 ~]# cat /var/lib/mysql/rocky8.log | grep db1
2024-10-18T15:53:02.092641Z 11 Query insert into db1.stu(name,age,gender)values('jerry',20,'F')
#继续测试读与分离,mycat客户端查询 server_id,显示结果是 slave 节点
mysql> select @@server_id;
+-------------+
| @@server_id |
+-------------+
| 183 |
+-------------+
1 row in set (0.00 sec)
当 slave 节点离线,mycat 会将查操作移换到 master 节点
#slave停止mysql服务
[root@rocky8 ~]# systemctl stop mysqld
#查询失败,需要等待下一次心跳检测发现 slave 节点离线
mysql> select * from db1.stu;
ERROR 1184 (HY000): java.net.ConnectException: Connection refused
#这个查询发生在 master 节点上
mysql> select * from db1.stu;
+----+-------+------+--------+
| id | name | age | gender |
+----+-------+------+--------+
| 1 | tom | 10 | M |
| 2 | jerry | 20 | F |
+----+-------+------+--------+
2 rows in set (0.01 sec)
#master
mysql> select @@server_id;
+-------------+
| @@server_id |
+-------------+
| 177 |
+-------------+
1 row in set (0.01 sec)
#恢复 slave 节点后,下一次心跳检测之后,查询将再次回到 slave 节点上
[root@rocky8 ~]# systemctl start mysqld
#mycat查询
mysql> select @@server_id;
+-------------+
| @@server_id |
+-------------+
| 177 |
+-------------+
1 row in set (0.01 sec)
mysql> select @@server_id;
+-------------+
| @@server_id |
+-------------+
| 183 |
+-------------+
1 row in set (0.00 sec)
当 master 节点离线,读写操作将无法执行
[root@rocky8 ~]
mysql> select * from db1.stu;
ERROR 1184 (HY000): java.net.ConnectException: Connection refused
— END —

优网科技秉承"专业团队、品质服务" 的经营理念,诚信务实的服务了近万家客户,成为众多世界500强、集团和上市公司的长期合作伙伴!
优网科技成立于2001年,擅长网站建设、网站与各类业务系统深度整合,致力于提供完善的企业互联网解决方案。优网科技提供PC端网站建设(品牌展示型、官方门户型、营销商务型、电子商务型、信息门户型、DIY体验、720全景展厅及3D虚拟仿真)、移动端应用(手机站、APP开发)、微信定制开发(微信官网、微信商城、企业微信)、微信小程序定制开发等一系列互联网应用服务。