MyCat - 高可用集群搭建、架构剖析
MyCat 高可用集群搭建
集群架构
MyCat 实现读写分离架构
在之前的笔记中, 已经讲解过了通过 MyCat 来实现 MySQL 的读写分离,从而完成 MySQL 集群的负载均衡, 如下面的结构图:

但是以上架构存在问题 ,由于 MyCat 中间件是单节点的服务,前端客户端所有的压力过来都直接请求这一台 MyCat,存在单点故障。所以这个时候, 我们就需要考虑 MyCat 的集群 。
MyCat 集群架构
通过 MyCat 来实现后端 MySQL 的负载均衡 ,通过 HAProxy 再实现 MyCat 集群的负载均衡:

HAProxy 负责将请求分发到 MyCat 上,起到负载均衡的作用,同时 HAProxy 也能检测到 MyCat 是否存活,HAProxy 只会将请求转发到存活的 MyCat 上。如果一台 MyCat 服务器宕机,HAPorxy 转发请求时不会转发到宕机的 MyCat 上,所以 MyCat 依然可用。
HAProxy 介绍:
HAProxy 是一个开源的、高性能的基于 TCP (第四层) 和 HTTP (第七层) 应用的负载均衡软件。 使用 HAProxy 可以快速、可靠地实现基于 TCP 与 HTTP 应用的负载均衡解决方案。
具有以下优点:
可靠性和稳定性好,可以与硬件级的 F5 负载均衡服务器媲美;
处理能力强,最高可以通过维护 4w-5w 个并发连接,单位时间处理的最大请求数达到 2w 个;
支持多种负载均衡算法;
有功能强大的监控界面,通过此页面可以实时了解系统的运行情况;
但是,上述的架构也是存在问题的, 因为所有的客户端请求都是先到达 HAProxy,由 HAProxy 再将请求再向下分发,如果 HAProxy 宕机的话,就会造成整个 MyCat 集群不能正常运行,依然存在单点故障。
MyCat 的高可用集群

图解说明:
HAProxy 实现了 MyCat 多节点的集群高可用和负载均衡,而 HAProxy 自身的高可用则可以通过 Keepalived 来实现。因此,HAProxy 主机上要同时安装 HAProxy 和 Keepalived,Keepalived 负责为该服务器抢占 vip(虚拟 ip),抢占到 vip 后,对该主机的访问可以通过原来的 ip 访问,也可以直接通过 vip 访问。
Keepalived 抢占 vip 有优先级,由 keepalived.conf 配置中的 priority 属性决定。但是一般哪台主机上的 Keepalived 服务先启动就会抢占到 vip,即使是 slave,只要先启动也能抢到(要注意避免 Keepalived 的资源抢占问题)。
HAProxy 负责将 vip 上的请求分发到 MyCat 集群节点上,起到负载均衡的作用。同时 HAProxy 也能检测到 MyCat 是否存活,HAProxy 只会将请求转发到存活的 MyCat 上。
如果 Keepalived+HAProxy 高可用集群中的一台服务器宕机,集群中另外一台服务器上的 Keepalived 会立刻抢占 vip 并接管服务,此时抢占了 vip 的 HAProxy 节点可以继续提供服务。
如果一台 MyCat 服务器宕机,HAPorxy 转发请求时不会转发到宕机的 MyCat 上,所以 MyCat 依然可用。
综上:MyCat 的高可用及负载均衡由 HAProxy 来实现,而 HAProxy 的高可用,由 Keepalived 来实现。
keepalived 介绍:
- Keepalived 是一种基于 VRRP 协议来实现的高可用方案,可以利用其来避免单点故障。 通常有两台甚至多台服务器运行 Keepalived,一台为主服务器 (Master), 其他为备份服务器,但是对外表现为一个虚拟 IP (VIP), 主服务器会发送特定的消息给备份服务器,当备份服务器接收不到这个消息时,即认为主服务器宕机,备份服务器就会接管虚拟 IP, 继续提供服务,从而保证了整个集群的高可用。
- VRRP (虚拟路由冗余协议 - Virtual Router Redundancy Protocol) 协议是用于实现路由器冗余的协议,VRRP 协议将两台或多台路由器设备虚拟成一个设备,对外提供虚拟路由器 IP (一个或多个),而在路由器组内部,如果实际拥有这个对外 IP 的路由器如果工作正常的话就是 MASTER,或者是通过算法选举产生。MASTER 实现针对虚拟路由器 IP 的各种网络功能,如 ARP 请求,ICMP,以及数据的转发等;其他设备不拥有该虚拟 IP,状态是 BACKUP,除了接收 MASTER 的 VRRP 状态通告信息外,不执行对外的网络功能。当主机失效时,BACKUP 将接管原先 MASTER 的网络功能。
- VRRP 协议使用多播数据来传输 VRRP 数据,VRRP 数据使用特殊的虚拟源 MAC 地址发送数据而不是自身网卡的 MAC 地址,VRRP 运行时只有 MASTER 路由器定时发送 VRRP 通告信息,表示 MASTER 工作正常以及虚拟路由器 IP (组),BACKUP 只接收 VRRP 数据,不发送数据,如果一定时间内没有接收到 MASTER 的通告信息,各 BACKUP 将宣告自己成为 MASTER,发送通告信息,重新进行 MASTER 选举状态。
高可用集群搭建
部署环境规划
名称 | IP | 端口 | 用户名 / 密码 |
---|---|---|---|
MySQL Master | 192.168.192.157 | 3306 | root/123456 |
MySQL Slave | 192.168.192.158 | 3306 | root/123456 |
MyCat 节点 1 | 192.168.192.157 | 8066 | root/123456 |
MyCat 节点 2 | 192.168.192.158 | 8066 | root/123456 |
HAProxy 节点 1/keepalived 主 | 192.168.192.159 | ||
HAProxy 节点 2/keepalived 备 | 192.168.192.160 |

MySQL 主从复制搭建
环境搭建之前需要每台服务器上都已经安装 MySQL。
master
- 在 master 的配置文件(/usr/my.cnf)中,配置如下内容:
1 | #mysql 服务ID,保证整个集群环境中唯一 |
- 执行完毕之后,需要重启 MySQL:
1 | service mysql restart ; |
- 创建同步数据的账户,并且进行授权操作:
1 | grant replication slave on *.* to 'itcast'@'%' identified by '123456; |
- 查看 master 状态:
1 | show master status; |
字段含义:
1 | File : 从哪个日志文件开始推送日志文件 |
slave
- 在 slave 端配置文件中,配置如下内容:
1 | #mysql服务端ID,唯一 |
- 执行完毕之后,需要重启 MySQL:
1 | service mysql restart; |
- 执行如下指令 :
1 | #指定当前从库对应的主库的IP地址,用户名,密码,从哪个日志文件开始的那个位置开始同步推送日志。 |
但是如果之前节点之间有主从复制关系,需要先停止两者的联系:
1 | stop slave; |
- 开启同步操作:
1 | start slave; |
测试验证
1 | create database db01; |
在主节点以及从节点上都可以查到数据:
MyCat 安装配置
schema.xml
1 |
|
server.xml
1 | <user name="root" defaultAccount="true"> |
两台服务器均需要安装 MyCat(依赖 JDK)、JDK,并且 MyCat 服务做相同的配置 。MyCat、JDK 安装见本博客的 MyCat 入门文档。
测试验证
两台服务器均启动 MyCat:
shell1
2bin/mycat start;
mysql -h 192.168.192.157/8 -P 8066 -u root -p;通过查看日志验证环境是否搭建成功(记得修改日志级别为:DEBUG):
sql1
2select * from user;
insert into user(id,name,sex) values(null,'Tom2','1');image-20210528145225031image-20210528145515739读操作走从节点,写操作走主节点。
HAProxy 安装配置
安装
- 准备好 HAProxy 安装包,分别上传到两台服务器(192.168.192.159/160 都安装)的 /root 目录下:
1 | haproxy-1.5.16.tar.gz |
- 解压到 /usr/local/src 目录下:
1 | tar -zxvf haproxy-1.5.16.tar.gz -C /usr/local/src |
- 进入解压后的目录,查看内核版本,进行编译:
1 | cd /usr/local/src/haproxy-1.5.16 |
- 编译完成后,进行安装:
1 | make install PREFIX=/usr/local/haproxy |
- 安装完成后,创建目录:
1 | mkdir -p /usr/data/haproxy/ |
- 创建 HAProxy 配置文件:
1 | vim /usr/local/haproxy/haproxy.conf |
1 | global |
内容解析如下 :
1 | #global 配置中的参数为进程级别的参数,通常与其运行的操作系统有关 |
HAProxy 的负载均衡策略:
策略 | 含义 |
---|---|
roundrobin | 表示简单的轮循,即客户端每访问一次,请求轮循跳转到后端不同的节点机器上 |
static-rr | 基于权重轮循,根据权重轮循调度到后端不同节点 |
leastconn | 加权最少连接,表示最少连接者优先处理 |
source | 表示根据请求源 IP,这个跟 Nginx 的 IP_hash 机制类似,使用其作为解决 session 问题的一种方法 |
uri | 表示根据请求的 URL,调度到后端不同的服务器 |
url_param | 表示根据请求的 URL 参数来进行调度 |
hdr(name) | 表示根据 HTTP 请求头来锁定每一次 HTTP 请求 |
rdp-cookie(name) | 表示根据 cookie(name)来锁定并哈希每一次 TCP 请求 |
启动访问
- 启动 HAProxy:
1 | /usr/local/haproxy/sbin/haproxy -f /usr/local/haproxy/haproxy.conf |
- 查看 HAProxy 进程:
1 | ps -ef|grep haproxy |
- 访问
http://192.168.192.159:8888/admin
界面:
Keepalived 安装配置

Keepalived 之间维持一个心跳,如果与 VIP 绑定的 Keepalived 挂掉,则另外一台 Keepalived 立刻获得 VIP 绑定,执行请求分发功能。
当位于绑定 VIP 的 Keepalived 上的 HAProxy 挂掉后,Keepalived 无法执行分发功能,但是另外一台的 Keepalived 仍然可以监控到该 Keepalived 存活,不能获得 VIP 绑定,这时这台 Keepalived 会试图重启 HAProxy,如果启动不成功,Keepalived 的脚本文件会将自己挂掉(自杀),之后另外一台 Keepalived 会立刻与 VIP 绑定,执行请求分发功能。
安装配置
在两台服务器上均安装 HAProxy(159/160),并进行配置。
- 上传安装包到 Linux:
1 | alt + p --------> put D:/tmp/keepalived-1.4.5.tar.gz |
- 解压安装包到目录 /usr/local/src:
1 | tar -zxvf keepalived-1.4.5.tar.gz -C /usr/local/src |
- 安装依赖插件:
1 | yum install -y gcc openssl-devel popt-devel |
- 进入解压后的目录,进行配置和编译:
1 | cd /usr/local/src/keepalived-1.4.5 |
- 进行编译,完成后进行安装:
1 | make && make install |
- 运行前配置:
1 | cp /usr/local/src/keepalived-1.4.5/keepalived/etc/init.d/keepalived /etc/init.d/ |
- 修改配置文件 /etc/keepalived/keepalived.conf:
- Master: 192.168.192.159
1 | global_defs { |
- BackUP: 1921.68.192.160
1 | global_defs { |
- 编写检测 haproxy 的 shell 脚本 在 /etc/keepalived/haproxy_check.sh, haproxy_check.sh:
1 | !/bin/bash |
启动测试
- 启动 Keepalived:
1 | service keepalived start |
- 登录验证:
1 | mysql -uroot -p123456 -h 192.168.192.200 -P 48066 |
上面语句其实也是访问 HAProxy,进而访问到 MySQL 中的数据信息。
当主节点挂掉之后,备用节点与 VIP 进行绑定,而当主节点重新启用后,主节点重新与 VIP 进行绑定,而备用节点仍然是备用节点。
要想知道 VIP 与哪台服务器绑定可以使用:
1 | arp -a 地址 |
MyCat 架构剖析
MyCat 总体架构介绍
源码下载及导入
导入编辑器:
总体架构
MyCat 在逻辑上由几个模块组成:通信协议、路由解析、结果集处理、数据库连接、监控等模块。如图所示:

通信协议模块: 通信协议模块承担底层的收发数据、线程回调处理工作, MyCat 通信协议默认采用 Reactor 模式,在协议层采用 MySQL 协议;
路由解析模块:负责对传入的 SQL 语句进行语法解析,解析语句的条件、类型、关键字等,并进行优化;
SQL 执行模块:负责从连接池中获取连接,再根据路由解析的结果,把 SQL 语句分发到相应的节点执行;
数据库链接模块:负责创建、管理、维护后端的连接池。为减少每次建立数据库连接的开销,数据库使用连接池机制对连接声明周期进行管理;
结果集处理模块:负责对跨分片的查询结果进行汇聚、排序、截取等;
监控管理模块:负责 MyCat 的连接、内存等资源进行监控和管理。监控主要通过管理指令及监控服务展现一些监控数据; 管理则主要通过轮询事件来检测和释放不适用的资源。
总体执行流程

MyCat 网络 I/O 架构及实现
BIO、NIO 与 AIO
- BIO
BIO (同步阻塞 I/O) 通常由一个单独的 Acceptor 线程负责监听客户端的连接,接收到客户端的连接请求后,会为每个客户端创建一个新的线程进行处理,处理完成之后,再给客户端返回结果,销毁线程 。
每个客户端请求接入时,都需要开启一个线程进行处理,一个线程只能处理一个客户端连接。 当客户端变多时,会创建大量的处理线程,每个线程都需要分配栈空间和 CPU,并且频繁的线程上下文切换也会造成性能的浪费,所以该模式无法满足高性能、高并发接入的需求。
- NIO
NIO (同步非阻塞 I/O) 基于 Reactor 模式作为底层通信模型,Reactor 模式可以将事件驱动的应用进行事件分派,将客户端发送过来的服务请求分派给合适的处理类 (handler)。当 Socket 有流可读或可写入 Socket 时,操作系统会通知相应的应用程序进行处理,应用程序再将流读取到缓冲区或写入操作系统。这时已经不是一个连接对应一个处理线程了,而是一个有效的请求对应一个线程,当没有数据时,就没有工作线程来处理。
NIO 的最大优点体现在线程轮询访问 Selector,当 read 或 write 到达时则处,未到达时则继续轮询。
- AIO
AIO 是全程 Asynchronous IO (异步非阻塞的 IO),是一种非阻塞异步的通信模式。在 NIO 的基础上引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。AIO 中客户端的 I/O 请求都是由 OS 先完成了再通知服务器应用去启动线程进行处理。
AIO 与 NIO 的主要区别在于回调与轮询,客户端不需要关注服务处理事件是否完成,也不需要轮询,只需要关注自己的回调函数。
通信架构
在 MyCat 中实现了 NIO 与 AIO 两种 I/O 模式,可以通过配置文件 server.xml 进行指定 :
1 | <property name="usingAIO">1</property> |
usingAIO 为 1 代表使用 AIO 模型,为 0 表示使用 NIO 模型。
MyCat 的 AIO 架构

MyCatStartUp 是整个 MyCat 服务启动的入口;
在获取到 MyCat 的 home 目录后,把主要的任务交给 MyCatServer,并调用其 startup 方法;
初始化系统配置,获取配置文件中的 usingAIO 的配置,如果配置为 1,说明使用 AIO 模型,进入到 AIO 的分支,并创建两个连接,一个是管理后台连接 (9066),一个 server 的连接 (8066);
进入 AIO 分支,主要有 AIOAcceptor 接收客户端请求,绑定端口,创建服务端的异步 Socket;在 accept 方法中完成两件事: ①FrontedConnection 的创建,这是前段连接的关键;② register 注册事件,MySQL 协议握手包就在此时发送。
AIOAcceptor 的 accept 方法 :

MyCat 的 NIO 架构
如果设置的 usingAIO 为 0,那么将走 NIOAcceptor 通道,流程如下:

如果走 NIO 分支,将首先创建 NIOAcceptor 对象,并调用其 start 方法;
NIOAcceptor 负责处理 Accept 事件,服务端接收客户端的连接事件,就是 MyCat 作为服务端去处理前端业务程序发过来的连接请求, 建立连接后,调用 NIOAcceptor 的 NIOReactor.postRegister 方法进行注册(并没有注解注册,而是放入缓冲队列,避免加锁的竞争)。
NIOAcceptor 的 accept 方法 :

Mycat 实现 MySQL 协议
MySQL 协议简介
概述
MySQL 协议处于应用层之下、TCP/IP 之上,在 MySQL 客户端和服务端之间使用。包含了链接器、MySQL 代理、主从复制服务器之间通信,并支持 SSL 加密、传输数据的压缩、连接和身份验证及数据交互等。其中,握手认证阶段和命令执行阶段是 MySQL 协议中的两个重要阶段。
握手认证阶段

握手认证阶段是客户端连接服务器的必经之路,客户端与服务端完成 TCP 的三次握手以后,服务端会向客户端发送一个初始化握手包,握手包中包含了协议版本、MySQLServer 版本、线程 ID、服务器的权能标识和字符集等信息。
客户端在接收到服务端的初始化握手包之后,会发送身份验证包给服务端(AuthPacket),该包中包含用户名、密码等信息。
服务端接收到客户端的登录验证包之后,需要进行逻辑校验,校验该登录信息是否正确。如果信息都符合,则返回一个 OKPacket,表示登录成功,否则返回 ERR_Packet,表示拒绝。
Wireshark 抓包如下:
报文分析如下:
- 初始化握手包:

通过抓包工具 Wireshark 抓取到的握手包信息如下:

说明:
Packet Length:包的长度
Packet Number:包的序号
Server Greeting:消息体,包含了协议版本、MySQLServer 版本、线程 ID 和字符集等信息。
- 登录认证包
客户端在接收到服务端发来的初始握手包之后,向服务端发出认证请求,该请求包含以下信息(由 Wireshark 抓获):

- OK 包或 ERROR 包
服务端接收到客户端的登录认证包之后,如果通过认证,则返回一个 OKPacket,如果未通过认证,则返回一个 ERROR 包。
OK 报文如下:

ERROR 报文如下:

命令执行阶段
在握手认证阶段通过并完成以后,客户端可以向服务端发送各种命令来请求数据,此阶段的流程是:命令请求 -> 返回结果集。
Wireshark 捕获的数据包如下:

- 命令包:

- 结果集包:

MySQL 协议在 MyCat 中实现
握手认证实现
在 MyCat 中同时实现了 NIO 和 AIO,通过配置可以选择 NIO 和 AIO。MyCat Server 在启动阶段已经选择好采用 NIO 还是 AIO,因此建立 I/O 通道后,MyCat 服务端一直等待客户端的连接,当有连接到来的时候,MyCat 首先发送握手包。
- 握手包源码实现:MyCat 源码中 io.mycat.net.FrontendConnection 类的实现如下:
握手包信息组装完毕后,通过 FrontedConnection 写回客户端。
- 认证包源码实现
客户端接收到握手包后,紧接着向服务端发起一个认证包,MyCat 封装为类 AuthPacket:

客户端发送的认证包转由 FrontendAuthenticator 的 Handler 来处理,主要操作就是 拆包,检查用户名、密码合法性,检查连接数是够超出限制。源码实现如下:
认证失败,调用 failure 方法,认证成功调用 success 方法。
failure 方法源码:

success 方法源码:

命令执行实现
命令执行阶段就是 SQL 命令和 SQL 语句执行阶段,在该阶段 MyCat 主要需要做的事情,就是对客户端发来的数据包进行拆包,并判断命令的类型,并解析 SQL 语句,执行响应的 SQL 语句,最后把执行结果封装在结果集包中,返回给客户端。
从客户端发来的命令交给 FrontendCommandHandler 中的 handle 方法处理:
处理具体的请求,返回客户端结果集数据包:
MyCat 线程架构与实现
MyCat 线程池实现
在 MyCat 中大量用到了线程池,通过线程池来避免频繁的创建和销毁线程而造成的系统性能的浪费。在 MyCat 中使用的线程池是 JDK 中提供的线程池 ThreadPoolExecutor 的子类 NameableExecutor , 构造方法如下:
父类构造为:
构造参数含义:
corePoolSize : 核心池大小
maximumPoolSize : 最大线程数
keepAliveTime: 线程没有任务执行时,最多能够存活多久
timeUnit: 时间单位
workQueue: 阻塞任务队列
threadFactory: 线程工厂,用来创建线程
MyCat 线程架构

在 MyCat 中主要有两大线程池:timerExecutor 和 businessExecutor。
timerExecutor 线程池主要完成系统时间定时更新、处理器定时检查、数据节点定时连接、空闲超时检查、数据节点定时心跳检测等任务。
businessExecutor 是 MyCat 最重要的线程资源池,该资源池的线程使用的范围非常广,涵盖以下方面:
- 后端用原生协议连接数据
- JDBC 执行 SQL 语句
- SQL 拦截
- 数据合并服务
- 批量 SQL 作业
- 查询结果的异步分发
- 基于 guava 实现异步回调
MyCat 内存管理及缓存框架与实现
这里所提到的内存管理指的是 MyCat 缓冲区管理,众所周知设置缓冲区的唯一目的是提高系统的性能,缓冲区通常是部分常用的数据存放在缓冲池中以便系统直接访问,避免使用磁盘 IO 访问磁盘数据,从而提高性能。
内存管理
缓冲池组成:缓冲池的最小单位为 chunk,默认的 chunk 大小为 4096 字节 (DEFAULT_BUFFER_CHUNK_SIZE),BufferPool 的总大小为 4096 x processors x 1000 (其中 processors 为处理器数量)。对 I/O 进程而言,他们共享一个缓冲池。缓冲池有两种类型:本地缓存线程(以 $_开头的线程)缓冲区和其他缓冲区,分配 buffer 时,优先获取 ThreadLocalPool 中的 buffer,没有命中时会获取 BufferPool 中的 buffer。
分配 MyCat 缓冲池:分配缓冲池时,可以指定大小,也可以用默认值。
- allocate ():先检测是否为本地线程,当执行线程为本地缓存线程时,localBufferPool 取出一个可用的 buffer。如果不是,则从 ConcurrentLinkedQueue 队列中取出一个 buffer 进行分配,如果队列没有可用的 buffer,则创建一个直接缓冲区。
- allocate (size):如果用户指定的 size 不大于 chunkSize,则调用 allocate () 进行分配;反之则调用 createTempBuffer (size) 创建临时非直接缓冲区。
- MyCat 缓冲池的回收:回收时先判断 buffer 是否有效,有如下情况时缓冲池不回收。
- 不是直接缓冲区
- buffer 是空的
- buffer 的容量大于 chunkSize
MyCat 缓存架构
- 缓存框架选择:MyCat 支持 ehcache、mapdb、leveldb 缓存,可通过配置文件 cacheserver.properties 来进行配置:
- 缓存内容:MyCat 有路由缓存、表主键到 datanode 缓存、ER 关系缓存。
- 路由缓存:即 SQLRouteCache, 根据 SQL 语句查找路由信息的缓存,该缓存只是针对 select 语句,如果执行了之前已经执行过的某个 SQL 语句 (缓存命中), 那么路由信息就不需要重复计算了,直接从缓存中获取。
- 表主键到 datanode 缓存:当分片字段与主键字段不一致时,直接通过主键值查询时无法定位具体分片的 (只能全分片下发), 所以设置该缓存之后,就可以利用主键值查找到分片名,缓存的 key 是 ID 值,value 是节点名。
- ER 关系缓存:在 ER 分片时使用,而且在 insert 查询中才会使用缓存,当子表插入数据时,根据父子关联字段确定子表分片,下次可以直接从缓存中获取所在的分片。
查看缓存指令: show @@cache;
MyCat 连接池架构与实现
这里所讨论的连接池是 MyCat 的后端连接池,也就是 MyCat 后端与各个数据库节点之间的连接架构。
连接池创建:MyCat 按照每个 dataHost 创建一个连接池,根据 schema.xml 文件的配置取得最小的连接数 minCon,并初始化 minCon 个连接。在初始化连接时,还需要判定用户选择的是 JDBC 还是原生的 MySQL 协议,以便于创建对应的连接。
连接池分配:分配连接就是从连接池队列中取出一个连接,在取出一个连接时,MyCat 需要根据负载均衡(balance 属性)的类型选择不同的数据源,因为连接和数据源绑在一起,所以需要知道 MyCat 读写的是那些数据源,才能分配响应的连接。
架构:

MyCat 主从切换架构与实现
MyCat 主从切换概述
MyCat 实现 MySQL 读写分离的目的在于降低单节点数据库的访问压力,原理就是让主数据库执行增删改操作,从数据库执行查询操作,利用 MySQL 数据库的复制机制将 Master 的数据同步到 slave 上。
当 master 宕机后,slave 承载的业务如何切换到 master 继续提供服务,以及 slave 宕机后如何将 master 切换到 slave 上。手动切换数据源很简单, 但不是运维工作的首选,本节重点就是讲解如何实现自动切换。
MyCat 的读写分离依赖于 MySQL 的主从同步,也就是说 MyCat 没有实现数据的主从同步功能,但是实现了自动切换功能。
1). 自动切换
自动切换是 MyCat 主从复制的默认配置,当主机或从机宕机后,MyCat 自动切换到可用的服务器上。假设写服务器为 M,读服务器为 S, 则:
正常时,写 M 读 S;
当 M 宕机后,读写 S;恢复 M 后,写 S,读 M;
当 S 宕机后,读写 M;恢复 S 后,写 M,读 S 。
2). 基于 MySQL 主从同步状态的切换
这种切换方式与自动切换不同,MyCat 检测到主从数据同步延迟时,会自动切换到拥有最新数据的 MySQL 服务器上,防止读到很久以前的数据。
原理就是通过检查 MySQL 的主从同步状态(show slave status)中的 Seconds_Behind_Master、Slave_IO_Running、Slave_SQL_Running 三个字段,来确定当前主从同步的状态以及主从之间的数据延迟。 Seconds_Behind_Master 为 0 表示没有延迟,数值越大,则说明延迟越高。
MyCat 主从切换实现
基于延迟的切换,则判断结果集中的 Slave_IO_Running、Slave_SQL_Running 两个个字段是否都为 yes,以及 Seconds_Behind_Master 是否小于配置文件中配置的 slaveThreshold 的值,如果有其中任何一个条件不满足,则切换。
主要流程如下:

MyCat 核心技术
MyCat 分布式事务实现
MyCat 在 1.6 版本以后已经支持 XA 分布式事务类型了。具体的使用流程如下:
- 在应用层需要设置事务不能自动提交:
1 | set autocommit=0; |
- 在 SQL 中设置 XA 为开启状态:
1 | set xa = on; |
- 执行 SQL:
1 | insert into user(id,name,sex) values(1,'Tom','1'),(2,'Rose','2'),(3,'Leo','1'),(4,'Lee','1'); |
- 对事务进行提交或回滚:
1 | commit/rollback |
完整流程如下:

MyCat SQL 路由实现
MyCat 的路由是和 SQL 解析组件息息相关的,SQL 路由模块是 MyCat 数据库中间件最重要的模块之一,使用 MyCat 主要是为了分库分表,而分库分表的核心就是路由。
路由的作用

如图所示,MyCat 接收到应用系统发来的查询语句,要将其发送到后端连接的 MySQL 数据库去执行,但是后端有三个数据库服务器,具体要查询那一台数据库服务器呢,这就是路由需要实现的功能。SQL 的路由既要保证数据的完整,也不能造成资源的浪费,还要保证路由的效率。
SQL 解析器
Mycat1.3 版本之前模式使用的是 Fdbparser 的 foundationdb 的开源 SQL 解析器,在 2015 年被 apple 收购后,从开源变为闭源了。
目前版本的 MyCat 采用的是 Druid 的 SQL 解析器,性能比采用 Fdbparser 整体性能提高 20% 以上。
MyCat 跨库 Join
全局表
每个企业级的系统中,都会存在一些系统的基础信息表,类似于字典表、省份、城市、区域、语言表等,这些表与业务表之间存在关系, 但不是业务主从关系,而是一种属性关系。当我们对业务表进行分片处理时,可以将这些基础信息表设置为全局表,也就是在每个节点中都存在该表。
全局表的特性如下:
全局表的 insert、update、delete 操作会实时地在所有节点同步执行,保持各个节点数据的一致性;
全局表的查询操作会从任意节点执行,因为所有节点的数据都一致;
全局表可以和任意表进行 join 操作。

ER 表
关系型数据库是基于实体关系模型 (Entity Relationship Model) 的,MyCat 中的 ER 表便来源于此。MyCat 提出了基于 ER 关系的数据分片策略,子表的记录与其所关联的父表的记录存放在同一个数据分片中,通过表分组 (Table Group) 保证数据关联查询不会跨库操作。

catlet
catlet 是 MyCat 为了解决跨分片 Join 提出的一种创新思路,也叫做人工智能 (HBT)。MyCat 参考了数据库中存储过程的实现方式,提出类似的跨库解决方案,用户可以根据系统提供的 API 接口实现跨分片 Join。采用这种方案开发时,必须要实现 Catlet 接口的两个方法:
route 方法:路由的方法,传递系统配置和 schema 配置等;
processSQL 方法:EngineCtx 执行 SQL 并给客户端返回结果集 。
当自定义 Catlet 完成之后,需要将 Catlet 的实现类进行编译,并将其字节码文件 XXXCatlet.class 存放在 mycat_home/catlet 目录下,系统会加载相关 Class,而且每隔 1 分钟扫描一次文件是否更新,若更新则自动重新加载,因此无需重启服务。
ShareJoin
ShareJoin 是 Catlet 的实现,是一个简单的跨分片 Join,目前支持两个表的 Join,原理就是解析 SQL 语句,拆分成单表的语句执行,单后把各个节点的数据进行汇集。要想使用 Catlet 完成 join,还需要借助于 MyCat 中的注解,在执行 SQL 语句时,使用 catlet 注解:
1 | /*!mycat:catlet=demo.catlets.ShareJoin */ select a.id as aid , a.id , b.id as bid , b.name as name from customer a, company b where a.company_id=b.id and a.id = 1; |
MyCat 数据汇聚与排序
通过 MyCat 实现数据汇聚和排序,不仅可以减少各分片与客户端之间的数据传输 IO,也可以帮助开发者总复杂的数据处理中解放出来,从而专注于开发业务代码。
在 MySQL 中存在两种排序方式:一种利用有序索引获取有序数据,另一种通过相应的排序算法将获取到的数据在内存中进行排序。而 MyCat 中数据排序采用堆排序法对多个分片返回有序数据,并在合并、排序后再返回给客户端。
