MyCat高可用集群搭建

集群架构

MyCat实现读写分离架构

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

image-20210528142409915

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

MyCat集群架构

通过MyCat来实现后端MySQL的负载均衡 ,通过HAProxy再实现MyCat集群的负载均衡:

image-20210528142453972

HAProxy 负责将请求分发到 MyCat 上,起到负载均衡的作用,同时 HAProxy 也能检测到 MyCat 是否存活,HAProxy 只会将请求转发到存活的 MyCat 上。如果一台 MyCat 服务器宕机,HAPorxy 转发请求时不会转发到宕机的 MyCat 上,所以 MyCat 依然可用。

HAProxy介绍:

HAProxy 是一个开源的、高性能的基于TCP(第四层)和HTTP(第七层)应用的负载均衡软件。 使用HAProxy可以快速、可靠地实现基于TCP与HTTP应用的负载均衡解决方案。

具有以下优点:

  1. 可靠性和稳定性好, 可以与硬件级的F5负载均衡服务器媲美 ;

  2. 处理能力强, 最高可以通过维护4w-5w个并发连接, 单位时间处理的最大请求数达到2w个 ;

  3. 支持多种负载均衡算法 ;

  4. 有功能强大的监控界面, 通过此页面可以实时了解系统的运行情况 ;

但是,上述的架构也是存在问题的, 因为所有的客户端请求都是先到达HAProxy,由HAProxy再将请求再向下分发,如果HAProxy宕机的话,就会造成整个MyCat集群不能正常运行,依然存在单点故障。

MyCat的高可用集群

image-20210528142546548

图解说明:

  1. HAProxy 实现了 MyCat 多节点的集群高可用和负载均衡,而 HAProxy 自身的高可用则可以通过Keepalived 来实现。因此,HAProxy 主机上要同时安装 HAProxy 和 Keepalived,Keepalived 负责为该服务器抢占 vip(虚拟 ip),抢占到 vip 后,对该主机的访问可以通过原来的 ip访问,也可以直接通过 vip访问。

  2. Keepalived 抢占 vip 有优先级,由keepalived.conf 配置中的 priority 属性决定。但是一般哪台主机上的Keepalived服务先启动就会抢占到vip,即使是slave,只要先启动也能抢到(要注意避免Keepalived的资源抢占问题)。

  3. HAProxy 负责将 vip 上的请求分发到 MyCat 集群节点上,起到负载均衡的作用。同时 HAProxy 也能检测到 MyCat 是否存活,HAProxy 只会将请求转发到存活的 MyCat 上。

  4. 如果 Keepalived+HAProxy 高可用集群中的一台服务器宕机,集群中另外一台服务器上的 Keepalived会立刻抢占 vip 并接管服务,此时抢占了 vip 的 HAProxy 节点可以继续提供服务。

  5. 如果一台 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
image-20210528142700817

MySQL主从复制搭建

环境搭建之前需要每台服务器上都已经安装MySQL。

master
  1. 在master 的配置文件(/usr/my.cnf)中,配置如下内容:
1
2
3
4
5
6
7
8
9
10
11
12
#mysql 服务ID,保证整个集群环境中唯一
server-id=1
#mysql binlog 日志的存储路径和文件名
log-bin=/var/lib/mysql/mysqlbin
#设置logbin格式
binlog_format=STATEMENT
#是否只读,1 代表只读, 0 代表读写
read-only=0
#指定同步的数据库
binlog-do-db=db01
binlog-do-db=db02
binlog-do-db=db03
  1. 执行完毕之后,需要重启MySQL:
1
service mysql restart ;
  1. 创建同步数据的账户,并且进行授权操作:
1
2
3
grant replication slave on *.* to 'itcast'@'%' identified by '123456;	

flush privileges;
  1. 查看master状态:
1
show master status;

image-20210528142344820

字段含义:

1
2
3
File : 从哪个日志文件开始推送日志文件 
Position : 从哪个位置开始推送日志
Binlog_Do_DB : 指定需要同步的数据库
slave
  1. 在 slave 端配置文件中,配置如下内容:
1
2
3
4
5
6
#mysql服务端ID,唯一
server-id=2
#指定binlog日志
log-bin=/var/lib/mysql/mysqlbin
#启用中继日志
relay-log=mysql-relay
  1. 执行完毕之后,需要重启MySQL:
1
service mysql restart;
  1. 执行如下指令 :
1
2
#指定当前从库对应的主库的IP地址,用户名,密码,从哪个日志文件开始的那个位置开始同步推送日志。
change master to master_host= '192.168.192.157', master_user='itcast', master_password='123456', master_log_file='mysqlbin.000002', master_log_pos=120;

但是如果之前节点之间有主从复制关系,需要先停止两者的联系:

1
2
stop slave;
reset master;
  1. 开启同步操作:
1
2
start slave;
show slave status;

image-20210528143557533

测试验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
create database db01;

user db01;

create table user(
id int(11) not null auto_increment,
name varchar(50) not null,
sex varchar(1),
primary key (id)
)engine=innodb default charset=utf8;

insert into user(id,name,sex) values(null,'Tom','1');
insert into user(id,name,sex) values(null,'Trigger','0');
insert into user(id,name,sex) values(null,'Dawn','1');

在主节点以及从节点上都可以查到数据:

image-20210528143435791

MyCat安装配置

schema.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="ITCAST" checkSQLschema="true" sqlMaxLimit="100">
<table name="user" dataNode="dn1" primaryKey="id"/>
</schema>
<dataNode name="dn1" dataHost="localhost1" database="db01" />
<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="hostM1" url="192.168.192.157:3306" user="root" password="123456">
<readHost host="hostS1" url="192.168.192.158:3306" user="root" password="123456" />
</writeHost>
</dataHost>
</mycat:schema>
server.xml
1
2
3
4
5
6
7
8
9
<user name="root" defaultAccount="true">
<property name="password">123456</property>
<property name="schemas">ITCAST</property>
</user>

<user name="test">
<property name="password">123456</property>
<property name="schemas">ITCAST</property>
</user>

两台服务器均需要安装MyCat(依赖JDK)、JDK,并且MyCat服务做相同的配置 。MyCat、JDK安装见本博客的MyCat入门文档。

测试验证
  1. 两台服务器均启动MyCat:

    1
    2
    bin/mycat start;
    mysql -h 192.168.192.157/8 -P 8066 -u root -p;
  2. 通过查看日志验证环境是否搭建成功(记得修改日志级别为:DEBUG):

    1
    2
    select * from user;
    insert into user(id,name,sex) values(null,'Tom2','1');

    image-20210528145225031

    image-20210528145515739

    读操作走从节点,写操作走主节点。

HAProxy安装配置

安装
  1. 准备好HAProxy安装包,分别上传到两台服务器(192.168.192.159/160都安装)的/root目录下:
1
haproxy-1.5.16.tar.gz
  1. 解压到/usr/local/src目录下:
1
tar -zxvf haproxy-1.5.16.tar.gz -C /usr/local/src
  1. 进入解压后的目录,查看内核版本,进行编译:
1
2
3
4
5
6
7
cd /usr/local/src/haproxy-1.5.16
uname -r
make TARGET=linux2632 PREFIX=/usr/local/haproxy ARCH=x86_64

# TARGET=linux310,内核版本,使用uname -r查看内核,如:2.6.32-431.el6.x86_64,此时该参数就为linux2632;
# ARCH=x86_64:系统位数;
# PREFIX=/usr/local/haprpxy #/usr/local/haprpxy,为haprpxy安装路径。
  1. 编译完成后,进行安装:
1
make install PREFIX=/usr/local/haproxy
  1. 安装完成后,创建目录:
1
mkdir -p /usr/data/haproxy/
  1. 创建HAProxy配置文件:
1
vim /usr/local/haproxy/haproxy.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
global
log 127.0.0.1 local0
maxconn 4096
chroot /usr/local/haproxy
pidfile /usr/data/haproxy/haproxy.pid
uid 99
gid 99
daemon
node mysql-haproxy-01
description mysql-haproxy-01
defaults
log global
mode tcp
option abortonclose
option redispatch
retries 3
maxconn 2000
timeout connect 50000ms
timeout client 50000ms
timeout server 50000ms
listen proxy_status
bind 0.0.0.0:48066
mode tcp
balance roundrobin
server mycat_1 192.168.192.157:8066 check
server mycat_2 192.168.192.158:8066 check
frontend admin_stats
bind 0.0.0.0:8888
mode http
stats enable
option httplog
maxconn 10
stats refresh 30s
stats uri /admin
stats auth admin:123123
stats hide-version
stats admin if TRUE

内容解析如下 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#global 配置中的参数为进程级别的参数,通常与其运行的操作系统有关
global
#定义全局的syslog服务器, 最多可定义2个; local0 是日志设备, 对应于/etc/rsyslog.conf中的配置 , 默认收集info级别日志
log 127.0.0.1 local0
#log 127.0.0.1 local1 notice
#log loghost local0 info
#设定每个haproxy进程所接受的最大并发连接数 ;
maxconn 4096
#修改HAproxy工作目录至指定的目录并在放弃权限之前执行chroot操作, 可以提升haproxy的安全级别
chroot /usr/local/haproxy
#进程ID保存文件
pidfile /usr/data/haproxy/haproxy.pid
#指定用户ID
uid 99
#指定组ID
gid 99
#设置HAproxy以守护进程方式运行
daemon
#debug
#quiet
node mysql-haproxy-01 ## 定义当前节点的名称,用于 HA 场景中多 haproxy 进程共享同一个 IP 地址时
description mysql-haproxy-01 ## 当前实例的描述信息

#defaults:用于为所有其他配置段提供默认参数,这默认配置参数可由下一个"defaults"所重新设定
defaults
#继承global中的log定义
log global
#所使用的处理模式(tcp:四层 , http:七层, health:状态检查,只返回OK)
### tcp: 实例运行于纯 tcp 模式,在客户端和服务器端之间将建立一个全双工的连接,且不会对 7 层报文做任何类型的检查,此为默认模式
### http:实例运行于 http 模式,客户端请求在转发至后端服务器之前将被深度分析,所有不与 RFC 模式兼容的请求都会被拒绝
### health:实例运行于 health 模式,其对入站请求仅响应“OK”信息并关闭连接,且不会记录任何日志信息 ,此模式将用于相应外部组件的监控状态检测请求
mode tcp
#当服务器负载很高的时候,自动结束掉当前队列处理时间比较长的连接
option abortonclose

#当使用了cookie时,haproxy将会将请求的后端服务器的serverID插入到cookie中,以保证会话的session持久性,而此时,后端服务器宕机,但是客户端的cookie不会刷新,设置此参数,将会将客户请求强制定向到另外一个后端server上,以保证服务的正常。
option redispatch
retries 3
# 前端的最大并发连接数(默认为 2000)
maxconn 2000
# 连接超时(默认是毫秒,单位可以设置 us,ms,s,m,h,d)
timeout connect 5000
# 客户端超时时间
timeout client 50000
# 服务器超时时间
timeout server 50000

#listen: 用于定义通过关联“前端”和“后端”一个完整的代理,通常只对 TCP 流量有用
listen proxy_status
bind 0.0.0.0:48066 # 绑定端口
mode tcp
balance roundrobin # 定义负载均衡算法,可用于"defaults"、"listen"和"backend"中,默认为轮询
#格式: server <name> <address> [:[port]] [param*]
# weight : 权重,默认为 1,最大值为 256,0 表示不参与负载均衡
# backup : 设定为备用服务器,仅在负载均衡场景中的其他 server 均不可以启用此 server
# check : 启动对此 server 执行监控状态检查,其可以借助于额外的其他参数完成更精细的设定
# inter : 设定监控状态检查的时间间隔,单位为毫秒,默认为 2000,也可以使用 fastinter 和 downinter 来根据服务器端专题优化此事件延迟
# rise : 设置 server 从离线状态转换至正常状态需要检查的次数(不设置的情况下,默认值为 2)
# fall : 设置 server 从正常状态转换至离线状态需要检查的次数(不设置的情况下,默认值为 3)
# cookie : 为指定 server 设定 cookie 值,此处指定的值将会在请求入站时被检查,第一次为此值挑选的 server 将会被后续的请求所选中,其目的在于实现持久连接的功能
# maxconn: 指定此服务器接受的最大并发连接数,如果发往此服务器的连接数目高于此处指定的值,其将被放置于请求队列,以等待其他连接被释放
server mycat_1 192.168.192.157:8066 check inter 10s
server mycat_2 192.168.192.158:8066 check inter 10s

# 用来匹配接收客户所请求的域名,uri等,并针对不同的匹配,做不同的请求处理.
# HAProxy 的状态信息统计页面
frontend admin_stats
bind 0.0.0.0:8888
mode http
stats enable
option httplog
maxconn 10
stats refresh 30s
stats uri /admin
stats auth admin:123123
stats hide-version
stats admin if TRUE

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请求
启动访问
  1. 启动HAProxy:
1
/usr/local/haproxy/sbin/haproxy -f /usr/local/haproxy/haproxy.conf
  1. 查看HAProxy进程:
1
ps -ef|grep haproxy

image-20210528153132223

  1. 访问

http://192.168.192.159:8888/admin

界面:

image-20210528153356137

Keepalived安装配置

image-20210528153439544
  1. Keepalived之间维持一个心跳,如果与VIP绑定的Keepalived挂掉,则另外一台Keepalived立刻获得VIP绑定,执行请求分发功能。

  2. 当位于绑定VIP的Keepalived上的HAProxy挂掉后,Keepalived无法执行分发功能,但是另外一台的Keepalived仍然可以监控到该Keepalived存活,不能获得VIP绑定,这时这台Keepalived会试图重启HAProxy,如果启动不成功,Keepalived的脚本文件会将自己挂掉(自杀),之后另外一台Keepalived会立刻与VIP绑定,执行请求分发功能。

安装配置

在两台服务器上均安装HAProxy(159/160),并进行配置。

  1. 上传安装包到Linux:
1
alt + p --------> put D:/tmp/keepalived-1.4.5.tar.gz
  1. 解压安装包到目录 /usr/local/src:
1
tar -zxvf keepalived-1.4.5.tar.gz -C /usr/local/src
  1. 安装依赖插件:
1
yum install -y gcc openssl-devel popt-devel
  1. 进入解压后的目录,进行配置和编译:
1
2
3
cd /usr/local/src/keepalived-1.4.5

./configure --prefix=/usr/local/keepalived
  1. 进行编译,完成后进行安装:
1
make && make install
  1. 运行前配置:
1
2
3
4
5
cp /usr/local/src/keepalived-1.4.5/keepalived/etc/init.d/keepalived /etc/init.d/
mkdir /etc/keepalived
cp /usr/local/keepalived/etc/keepalived/keepalived.conf /etc/keepalived/
cp /usr/local/src/keepalived-1.4.5/keepalived/etc/sysconfig/keepalived /etc/sysconfig/
cp /usr/local/keepalived/sbin/keepalived /usr/sbin/
  1. 修改配置文件 /etc/keepalived/keepalived.conf:
  • Master: 192.168.192.159
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
global_defs {
notification_email {
javadct@163.com
}
notification_email_from keepalived@showjoy.com
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id haproxy01 #标识
vrrp_skip_check_adv_addr
vrrp_garp_interval 0
vrrp_gna_interval 0
}

vrrp_script chk_haproxy {
script "/etc/keepalived/haproxy_check.sh"
interval 2
weight 2
}


vrrp_instance VI_1 {
#主机配MASTER,备机配BACKUP
state MASTER
#所在机器网卡
interface eth1
virtual_router_id 51
#数值越大优先级越高,越容易抢占
priority 120
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
## 将 track_script 块加入 instance 配置块
track_script {
chk_haproxy ## 检查 HAProxy 服务是否存活
}
virtual_ipaddress {
#虚拟IP
192.168.192.200
}
}
  • BackUP: 1921.68.192.160
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
global_defs {
notification_email {
javadct@163.com
}
notification_email_from keepalived@showjoy.com
smtp_server 127.0.0.1
smtp_connect_timeout 30
#标识本节点
router_id haproxy02
vrrp_skip_check_adv_addr
vrrp_garp_interval 0
vrrp_gna_interval 0
}

# keepalived 会定时执行脚本并对脚本执行的结果进行分析,动态调整 vrrp_instance 的优先级
vrrp_script chk_haproxy {
# 检测 haproxy 状态的脚本路径
script "/etc/keepalived/haproxy_check.sh"
#检测时间间隔
interval 2
#如果条件成立,权重+2
weight 2
}

vrrp_instance VI_1 {
#主机配MASTER,备机配BACKUP
state BACKUP
#所在机器网卡
interface eth1
virtual_router_id 51
#数值越大优先级越高
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
## 将 track_script 块加入 instance 配置块
track_script {
chk_haproxy ## 检查 HAProxy 服务是否存活
}
virtual_ipaddress {
#虚拟IP
192.168.192.200
}
}
  1. 编写检测haproxy的shell脚本 在/etc/keepalived/haproxy_check.sh, haproxy_check.sh:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash

A=`ps -C haproxy --no-header | wc -l`

if [ $A -eq 0 ];then

/usr/local/haproxy/sbin/haproxy -f /usr/local/haproxy/haproxy.conf

echo "haproxy restart ..." &> /dev/null

sleep 1

if [ `ps -C haproxy --no-header | wc -l` -eq 0 ];then

/etc/init.d/keepalived stop

echo "stop keepalived" &> /dev/null

fi

fi
启动测试
  1. 启动Keepalived:
1
service keepalived start
  1. 登录验证:
1
mysql -uroot -p123456 -h 192.168.192.200 -P 48066

上面语句其实也是访问HAProxy,进而访问到MySQL中的数据信息。

当主节点挂掉之后,备用节点与VIP进行绑定,而当主节点重新启用后,主节点重新与VIP进行绑定,而备用节点仍然是备用节点。

要想知道VIP与哪台服务器绑定可以使用:

1
arp -a 地址  

MyCat架构剖析

MyCat总体架构介绍

源码下载及导入

image-20210529082401450

导入编辑器:

image-20210529160150015

总体架构

MyCat在逻辑上由几个模块组成:通信协议、路由解析、结果集处理、数据库连接、监控等模块。如图所示:

image-20210528190944761
  1. 通信协议模块: 通信协议模块承担底层的收发数据、线程回调处理工作, MyCat通信协议默认采用Reactor模式,在协议层采用MySQL协议;

  2. 路由解析模块:负责对传入的SQL语句进行语法解析,解析语句的条件、类型、关键字等,并进行优化;

  3. SQL执行模块:负责从连接池中获取连接,再根据路由解析的结果,把SQL语句分发到相应的节点执行;

  4. 数据库链接模块:负责创建、管理、维护后端的连接池。为减少每次建立数据库连接的开销,数据库使用连接池机制对连接声明周期进行管理;

  5. 结果集处理模块:负责对跨分片的查询结果进行汇聚、排序、截取等;

  6. 监控管理模块:负责MyCat的连接、内存等资源进行监控和管理。监控主要通过管理指令及监控服务展现一些监控数据; 管理则主要通过轮询事件来检测和释放不适用的资源。

总体执行流程

image-20210528191042826

MyCat网络I/O架构及实现

BIO、NIO与AIO

  1. BIO

BIO(同步阻塞I/O) 通常由一个单独的Acceptor线程负责监听客户端的连接,接收到客户端的连接请求后,会为每个客户端创建一个新的线程进行处理,处理完成之后,再给客户端返回结果,销毁线程 。

每个客户端请求接入时,都需要开启一个线程进行处理,一个线程只能处理一个客户端连接。 当客户端变多时,会创建大量的处理线程,每个线程都需要分配栈空间和CPU,并且频繁的线程上下文切换也会造成性能的浪费,所以该模式无法满足高性能、高并发接入的需求。

  1. NIO

NIO(同步非阻塞I/O)基于Reactor模式作为底层通信模型,Reactor模式可以将事件驱动的应用进行事件分派,将客户端发送过来的服务请求分派给合适的处理类(handler)。当Socket有流可读或可写入Socket时,操作系统会通知相应的应用程序进行处理,应用程序再将流读取到缓冲区或写入操作系统。这时已经不是一个连接对应一个处理线程了,而是一个有效的请求对应一个线程,当没有数据时,就没有工作线程来处理。

NIO 的最大优点体现在线程轮询访问Selector,当read或write到达时则处,未到达时则继续轮询。

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

image-20210529161255070
  1. MyCatStartUp是整个MyCat服务启动的入口;

  2. 在获取到MyCat的home目录后,把主要的任务交给MyCatServer,并调用其startup方法;

  3. 初始化系统配置,获取配置文件中的usingAIO的配置,如果配置为1,说明使用AIO模型,进入到AIO的分支,并创建两个连接,一个是管理后台连接(9066),一个server的连接(8066);

  4. 进入AIO分支,主要有AIOAcceptor接收客户端请求,绑定端口,创建服务端的异步Socket;在accept方法中完成两件事: ①FrontedConnection的创建,这是前段连接的关键; ② register注册事件,MySQL协议握手包就在此时发送。

AIOAcceptor的accept方法 :

image-20210529164017969

MyCat的NIO架构

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

image-20210529164113087
  1. 如果走NIO分支,将首先创建NIOAcceptor对象,并调用其start方法;

  2. NIOAcceptor 负责处理Accept事件,服务端接收客户端的连接事件,就是MyCat作为服务端去处理前端业务程序发过来的连接请求, 建立连接后,调用NIOAcceptor的NIOReactor.postRegister方法进行注册(并没有注解注册,而是放入缓冲队列,避免加锁的竞争)。

NIOAcceptor的accept方法 :

image-20210529164242242

Mycat实现MySQL协议

MySQL协议简介

概述

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

握手认证阶段
image-20210529162549815
  1. 握手认证阶段是客户端连接服务器的必经之路,客户端与服务端完成TCP的三次握手以后,服务端会向客户端发送一个初始化握手包,握手包中包含了协议版本、MySQLServer版本、线程ID、服务器的权能标识和字符集等信息。

  2. 客户端在接收到服务端的初始化握手包之后,会发送身份验证包给服务端(AuthPacket),该包中包含用户名、密码等信息。

  3. 服务端接收到客户端的登录验证包之后,需要进行逻辑校验,校验该登录信息是否正确。如果信息都符合,则返回一个OKPacket,表示登录成功,否则返回ERR_Packet,表示拒绝。

Wireshark抓包如下:

image-20210529162841533

报文分析如下:

  1. 初始化握手包:
image-20210529163131557

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

image-20210529164324303

说明:

  • Packet Length:包的长度

  • Packet Number:包的序号

  • Server Greeting:消息体,包含了协议版本、MySQLServer版本、线程ID和字符集等信息。

  1. 登录认证包

客户端在接收到服务端发来的初始握手包之后,向服务端发出认证请求,该请求包含以下信息(由Wireshark抓获):

image-20210529164431474
  1. OK包或ERROR包

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

OK报文如下:

image-20210529164511413

ERROR报文如下:

image-20210529164537597
命令执行阶段

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

Wireshark 捕获的数据包如下:

image-20210529164707467
  1. 命令包:
image-20210529164733540
  1. 结果集包:
image-20210529165457373

MySQL协议在MyCat中实现

握手认证实现

在MyCat中同时实现了NIO和AIO,通过配置可以选择NIO和AIO。MyCat Server在启动阶段已经选择好采用NIO还是AIO,因此建立I/O通道后,MyCat服务端一直等待客户端的连接,当有连接到来的时候,MyCat首先发送握手包。

  1. 握手包源码实现:MyCat源码中io.mycat.net.FrontendConnection类的实现如下:

image-20200127183259378

握手包信息组装完毕后,通过FrontedConnection写回客户端。

  1. 认证包源码实现

客户端接收到握手包后,紧接着向服务端发起一个认证包,MyCat封装为类 AuthPacket:

image-20210529165547943

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

image-20200127232022594

认证失败,调用failure方法,认证成功调用success方法。

failure方法源码:

image-20210529165726080

success方法源码:

image-20210529165801988
命令执行实现

命令执行阶段就是SQL命令和SQL语句执行阶段,在该阶段MyCat主要需要做的事情,就是对客户端发来的数据包进行拆包,并判断命令的类型,并解析SQL语句,执行响应的SQL语句,最后把执行结果封装在结果集包中,返回给客户端。

从客户端发来的命令交给 FrontendCommandHandler 中的handle方法处理:

image-20200127235140959

处理具体的请求,返回客户端结果集数据包:

image-20200128000050787

MyCat线程架构与实现

MyCat线程池实现

在MyCat中大量用到了线程池,通过线程池来避免频繁的创建和销毁线程而造成的系统性能的浪费。在MyCat中使用的线程池是JDK中提供的线程池 ThreadPoolExecutor 的子类 NameableExecutor , 构造方法如下:

image-20200108114506434

父类构造为:

image-20200108114611505

构造参数含义:

  • corePoolSize : 核心池大小

  • maximumPoolSize : 最大线程数

  • keepAliveTime: 线程没有任务执行时,最多能够存活多久

  • timeUnit: 时间单位

  • workQueue: 阻塞任务队列

  • threadFactory: 线程工厂,用来创建线程

MyCat线程架构

image-20210529170443397

在MyCat中主要有两大线程池:timerExecutor 和 businessExecutor。

  1. timerExecutor 线程池主要完成系统时间定时更新、处理器定时检查、数据节点定时连接、空闲超时检查、数据节点定时心跳检测等任务。

  2. businessExecutor是MyCat最重要的线程资源池,该资源池的线程使用的范围非常广,涵盖以下方面:

    • 后端用原生协议连接数据
    • JDBC执行SQL语句
    • SQL拦截
    • 数据合并服务
    • 批量SQL作业
    • 查询结果的异步分发
    • 基于guava实现异步回调

image-20200108141645417

MyCat内存管理及缓存框架与实现

这里所提到的内存管理指的是MyCat缓冲区管理,众所周知设置缓冲区的唯一目的是提高系统的性能,缓冲区通常是部分常用的数据存放在缓冲池中以便系统直接访问,避免使用磁盘IO访问磁盘数据,从而提高性能。

内存管理

  1. 缓冲池组成:缓冲池的最小单位为chunk,默认的chunk大小为4096字节(DEFAULT_BUFFER_CHUNK_SIZE),BufferPool的总大小为4096 x processors x 1000(其中processors为处理器数量)。对I/O进程而言,他们共享一个缓冲池。缓冲池有两种类型:本地缓存线程(以$_开头的线程)缓冲区和其他缓冲区,分配buffer时,优先获取ThreadLocalPool中的buffer,没有命中时会获取BufferPool中的buffer。

  2. 分配MyCat缓冲池:分配缓冲池时,可以指定大小,也可以用默认值。

    • allocate():先检测是否为本地线程,当执行线程为本地缓存线程时,localBufferPool取出一个可用的buffer。如果不是,则从ConcurrentLinkedQueue队列中取出一个buffer进行分配,如果队列没有可用的buffer,则创建一个直接缓冲区。
    • allocate(size):如果用户指定的size不大于chunkSize,则调用allocate()进行分配;反之则调用createTempBuffer(size)创建临时非直接缓冲区。
  1. MyCat缓冲池的回收:回收时先判断buffer是否有效,有如下情况时缓冲池不回收。
    • 不是直接缓冲区
    • buffer是空的
    • buffer的容量大于chunkSize

MyCat缓存架构

  1. 缓存框架选择:MyCat支持ehcache、mapdb、leveldb缓存,可通过配置文件cacheserver.properties来进行配置:

image-20200108154627518

  1. 缓存内容:MyCat有路由缓存、表主键到datanode缓存、ER关系缓存。
    • 路由缓存: 即SQLRouteCache, 根据SQL语句查找路由信息的缓存, 该缓存只是针对select语句, 如果执行了之前已经执行过的某个SQL语句(缓存命中), 那么路由信息就不需要重复计算了, 直接从缓存中获取。
    • 表主键到datanode缓存: 当分片字段与主键字段不一致时, 直接通过主键值查询时无法定位具体分片的(只能全分片下发), 所以设置该缓存之后, 就可以利用主键值查找到分片名, 缓存的key是ID值, value是节点名。
    • ER关系缓存: 在ER分片时使用, 而且在insert查询中才会使用缓存, 当子表插入数据时, 根据父子关联字段确定子表分片, 下次可以直接从缓存中获取所在的分片。

查看缓存指令: show @@cache;

image-20200108155642414

MyCat连接池架构与实现

这里所讨论的连接池是MyCat的后端连接池,也就是MyCat后端与各个数据库节点之间的连接架构。

  1. 连接池创建:MyCat按照每个dataHost创建一个连接池,根据schema.xml文件的配置取得最小的连接数minCon,并初始化minCon个连接。在初始化连接时,还需要判定用户选择的是JDBC还是原生的MySQL协议,以便于创建对应的连接。

  2. 连接池分配:分配连接就是从连接池队列中取出一个连接,在取出一个连接时,MyCat需要根据负载均衡(balance属性)的类型选择不同的数据源,因为连接和数据源绑在一起,所以需要知道MyCat读写的是那些数据源,才能分配响应的连接。

  3. 架构:

image-20210529171853415

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的值,如果有其中任何一个条件不满足,则切换。

主要流程如下:

image-20200128005840029

MyCat核心技术

MyCat分布式事务实现

MyCat在1.6版本以后已经支持XA分布式事务类型了。具体的使用流程如下:

  1. 在应用层需要设置事务不能自动提交:
1
set autocommit=0;
  1. 在SQL中设置XA为开启状态:
1
set xa = on;
  1. 执行SQL:
1
insert into user(id,name,sex) values(1,'Tom','1'),(2,'Rose','2'),(3,'Leo','1'),(4,'Lee','1');
  1. 对事务进行提交或回滚:
1
commit/rollback

完整流程如下:

image-20200129223657058

MyCat SQL路由实现

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

路由的作用
image-20200113225535847

如图所示,MyCat接收到应用系统发来的查询语句,要将其发送到后端连接的MySQL数据库去执行,但是后端有三个数据库服务器,具体要查询那一台数据库服务器呢,这就是路由需要实现的功能。SQL的路由既要保证数据的完整,也不能造成资源的浪费,还要保证路由的效率。

SQL解析器

Mycat1.3版本之前模式使用的是Fdbparser的foundationdb的开源SQL解析器,在2015年被apple收购后,从开源变为闭源了。

目前版本的MyCat采用的是Druid的SQL解析器,性能比采用Fdbparser整体性能提高20%以上。

MyCat跨库Join

全局表

每个企业级的系统中,都会存在一些系统的基础信息表,类似于字典表、省份、城市、区域、语言表等,这些表与业务表之间存在关系, 但不是业务主从关系,而是一种属性关系。当我们对业务表进行分片处理时,可以将这些基础信息表设置为全局表,也就是在每个节点中都存在该表。

全局表的特性如下:

  1. 全局表的insert、update、delete操作会实时地在所有节点同步执行,保持各个节点数据的一致性;

  2. 全局表的查询操作会从任意节点执行,因为所有节点的数据都一致;

  3. 全局表可以和任意表进行join操作。

image-20200128013501684
ER表

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

image-20200129101108379
catlet

catlet是MyCat为了解决跨分片Join提出的一种创新思路,也叫做人工智能(HBT)。MyCat参考了数据库中存储过程的实现方式,提出类似的跨库解决方案,用户可以根据系统提供的API接口实现跨分片Join。采用这种方案开发时,必须要实现Catlet接口的两个方法:

image-20200129104415975

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中数据排序采用堆排序法对多个分片返回有序数据,并在合并、排序后再返回给客户端。

image-20200129113055429