Bootstrap

分布式专题-数据库分库分表之Mycat03- MyCat高可用

前言

MyCat共计分为三节,分别是:

依据前面两节的实验,本节我们将实现MyCat的高可用。

本节重点:

➢ MyCat高可用
➢ 理解Mycat 注解的作用与应用场景
➢ Mycat 扩缩容与数据导入导出
➢ Mycat 核心原理分析总结

MyCat高可用

目前Mycat 没有实现对多Mycat 集群的支持,可以暂时使用HAProxy 来做负载思路:HAProxy 对Mycat 进行负载。Keepalived 实现VIP

环境准备:

  • 192.168.200.114:8066 -> MASTER(MyCat )
  • 192.168.200.113:8066 -> BACKUP(MyCat )
  • Haproxy:1.8.12(tar包安装)
  • Xinetd(yum 安装)
  • keepalive(yum 安装)

Haproxy

  1. 下载tar.gz包
    http://www.haproxy.org/download/1.8/src/haproxy-1.8.12.tar.gz (需要科学上网…咳咳咳。)
  2. 解压并安装haproxy
    解压
tar -zxvf haproxy-1.8.12.tar.gz

安装

cd haproxy-1.8.12  

将haproxy应用程序安装在/usr/local/haproxy 下

make TARGET=linux26 PREFIX=/usr/local/haproxy ARCH=x86_64  
make install PREFIX=/usr/local/haproxy
  1. 配置haproxy
    创建配置文件
vi /usr/local/haproxy/haproxy.cfg

并编辑文件、内容如下:

	global
			log         127.0.0.1 local2
			pidfile     /var/run/haproxy.pid
			maxconn     4000
			daemon
		defaults
			log global
			option dontlognull
			retries 3
			option redispatch
			maxconn 2000
			timeout connect 5000
			timeout client 50000
			timeout server 50000

		listen admin_status
			bind 0.0.0.0:1080 
			stats uri /admin ##haproxy自带的管理页面通过http://ip:port/admin访问
			stats auth admin:admin  ##管理页面的用户名和密码
			mode http
			option httplog

		listen allmycat_service
			bind 0.0.0.0:8096 ##转发到 mycat 的 8066 端口,即 mycat 的服务端口
			mode tcp
			option tcplog
			option httpchk OPTIONS * HTTP/1.1\r\nHost:\ www
			balance roundrobin
			server mycat_113 192.168.200.113:8066 check port 48700 inter 5s rise 2 fall 3
			server mycat_114 192.168.200.114:8066 check port 48700 inter 5s rise 2 fall 3
			timeout server 20000

		listen allmycat_admin
			bind 0.0.0.0:8097 ##转发到 mycat 的 9066 端口,即 mycat 的管理控制台端口
			mode tcp
			option tcplog
			option httpchk OPTIONS * HTTP/1.1\r\nHost:\ www
			balance roundrobin
			server mycat_113 192.168.200.113:9066 check port 48700 inter 5s rise 2 fall 3
			server mycat_114 192.168.200.114:9066 check port 48700 inter 5s rise 2 fall 3
			timeout server 20000
  1. 配置haproxy的日志输出
    haproxy采用rsyslog的方式进行日志配置,首先安装rsyslog,可通过
rpm -qa|grep rsyslog

命令判断有没有安装,没有安装自行安装(yum 方式简单明了)

find / -name 'rsyslog.conf

找到rsyslog的配置文件

vi rsyslog.conf

#$ModLoad imudp   
#$UDPServerRun 514

注释解开,找到

 Save boot messages also to boot.log 

在这一行下面加入

local2.*  /var/log/haproxy.log

保存退出
重启rsyslog:

service rsyslog restart
  1. 启动haproxy
/usr/local/haproxy/sbin/haproxy -f /usr/local/haproxy/haproxy.cfg

此时出现:

proxy allmycat_service has no server available!
proxy allmycat_admin has no server available!

因为他的check方案没有通过

option httpchk OPTIONS * HTTP/1.1\r\nHost:\ www check port 48700 inter 5s rise 2 fall 3

上面的配置的意思是,通过http检测的方式进行服务的检测,5s检测一次。

Xinetd

通过Xinetd 提供48700端口的http服务来让haproxy进行服务的检测
在mycat的服务器上安装xinetd,安装命令:

yum install xinetd

找到xinetd的配置文件

 cat /etc/xinetd.conf

找到 includedir /etc/xinetd.d ,进入该目录

cd /etc/xinetd.d  

并新建 mycat_status shell脚本,

vim mycat_status

内容如下:

service mycat_status   #代表被托管服务的名称
		{
			flags = REUSE
			socket_type = stream				 # socket连接方式
			port = 48700						 # 服务监听的端口
			wait = no							 # 是否并发
			user = root							 # 以什么用户进行启动
			server =/usr/local/bin/mycat_status  # 被托管服务的启动脚本
			log_on_failure += USERID 			 # 设置失败时,UID添加到系统登记表
			disable = no       					 #是否禁用托管服务,no表示开启托管服务
		}

保存退出
创建托管服务启动脚本/usr/local/bin/mycat_status

vim /usr/local/bin/mycat_status

内容如下:

mycat=`/root/mycat/bin/mycat status |grep 'not running'| wc -l`
		if [ "$mycat" = "0" ];
		then
			/bin/echo -e "HTTP/1.1 200 OK\r\n"
		else
			/bin/echo -e "HTTP/1.1 503 Service Unavailable\r\n"
		fi

保存退出 并赋予执行权限

chmod +x /usr/local/bin/mycat_status

验证脚本的正确性

sh /usr/local/bin/mycat_status  

如果返回 200OK字样说明成功

加入mycat_status服务

vi /etc/services

在末尾加入以下内容:

mycat_status 48700/tcp # mycat_status

保存退出,重启xinetd服务,

service xinetd restart

验证服务是否启动成功

netstat -antup|grep 48700

此时重启haproxy服务即正常

以上为-haproxy 4层代理的负载均衡配置,接下来通过keepalive+haproxy 实现高可用方案

keepalive

keepalive安装 (192.168.200.114 -> MASTER 192.168.200.113 -> BACKUP )

yum install  keepalived

找到配置文件:

find / -name 'keepalived.conf'

修改配置文件:

vi /etc/keepalived/keepalived.conf

内容如下:

! Configuration File for keepalived
		vrrp_instance VI_1 {
				state MASTER            #192.168.200.113 上改为 BACKUP
				interface ens33         #对外提供服务的网络接口
				virtual_router_id 100   #VRRP 组名,两个节点的设置必须一样,以指明各个节点属于同一 VRRP 组
				priority 150            #数值愈大,优先级越高,192.168.200.113 上改为比150小的正整数
				advert_int 1            #同步通知间隔
				authentication {        #包含验证类型和验证密码。类型主要有 PASS、AH 两种,通常使用的类型为 PASS,据说AH 使用时有问题
						auth_type PASS
						auth_pass 1111
				}
				virtual_ipaddress { #vip 地址    ens33  通过ifconfig获取
						192.168.200.233 dev ens33 scope global
				}
		} 

编辑保存
启动keepalived,执行命令

 service keepalived start

应用通过访问192.168.200.233 即访问抢占了vip(192.168.200.233)的物理机(192.168.200.113)。

通过上面的配置,我们可以将前端的请求转到抢占了vip(192.168.200.233)的物理机(192.168.200.113)。但是没有通过监听haproxy的服务,或者说我们没有根据haproxy服务来进行vip的降级。keepalived提供了很多的配置来做服务的检测和降级,但是我们今天不学keepalived的方式我们采用一种定时任务(linux自带的crontab)的方式来做。

  1. 创建check_haproxy.sh
    并编辑内容
vi /root/script/check_haproxy.sh

内容如下:

		#!/bin/bash
		LOGFILE='/root/log/checkHaproxy.log'
		date >> $LOGFILE
		count=`ps aux | grep -v grep | grep /usr/local/haproxy/sbin/haproxy | wc -l`
		if [ $count = 0 ];
		then
				echo 'first check fail , restart haproxy !' >> $LOGFILE
				/usr/local/haproxy/sbin/haproxy -f /usr/local/haproxy/haproxy.cfg
		else
				exit 0
		fi

		sleep 3

		count=`ps aux | grep -v grep | grep /usr/local/haproxy/sbin/haproxy | wc -l`
		if [ $count = 0 ];
		then
				echo 'second check fail , stop keepalive service !' >> $LOGFILE
				service keepalived stop
		else
				echo 'second check success , start keepalive service !' >> $LOGFILE
				keepalived=` ps aux | grep -v grep | grep /usr/sbin/keepalived | wc -l`
				if [ $count = 0 ];
				then
					service keepalived start
				fi
				exit 0
		fi
  1. 执行 crontab -e 编辑定时任务
    每一分钟检测haproxy服务存活,如果服务启动不了,停掉keepalived服务, vip即转发至backup的192.168.200.113的机器
* * * * * sh /root/script/check_haproxy.sh	

MyCat注解

注解的作用

当关联的数据不在同一个节点的时候,Mycat 是无法实现跨库join 的。举例:
如果直接在 150 插入主表数据,151 插入明细表数据,此时关联查询无法查询出来。

-- 111节点插入
INSERT INTO `order_info` (`order_id`, `uid`, `nums`, `state`, `create_time`, `update_time`) VALUES (9, 1000003, 2673, 1, '2021-9-25 11:35:49', '2021-9-25 11:35:49');

-- 112节点插入
INSERT INTO `order_detail` (`order_id`, `id`, `goods_id`, `price`, `is_pay`, `is_ship`, `status`) VALUES (9, 20210001, 2673, 19.99, 1, 1, 1);

在mycat 数据库查询,直接查询没有结果。

select a.order_id,b.price from order_info a, order_detail b where a.nums = b.goods_id;

Mycat 作为一个中间件,有很多自身不支持的SQL 语句,比如存储过程,但是这些语句在实际的数据库节点上是可以执行的。有没有办法让 Mycat 做一层透明的代理转发, 直接找到目标数据节点去执行这些SQL 语句呢?

那我们必须要有一种方式告诉Mycat 应该在哪个节点上执行。这个就是 Mycat 的注解。我们在需要执行的SQL 语句前面加上一段代码,帮助Mycat 找到我们的目标节点。

注解的用法

注解的形式是 :

/*!mycat: sql=注解 SQL 语句*/

注解的使用方式是 :

/*!mycat: sql=注解SQL 语句*/ + {真正执行的SQL}

使用时将 = 号后的 “注解SQL 语句” 替换为需要的 SQL 语句即可。使用注解有一些限制,或者注意的地方:

原始 SQL注解 SQL
select如果需要确定分片,则使用能确定分片的注解,比如/*!mycat: sql=select * from users where user_id=1*/ ,如果要在所有分片上执行则可以不加能确定分片的条件
insert使用 insert 的表作为注解 SQL,必须能确定到某个分片原始 SQL 插入的字段必须包括分片字段。非分片表(只在某个节点上):必须能确定到某个分片
delete使用 delete 的表作为注解 SQL
update使用 update 的表作为注解 SQL

使用注解并不额外增加 MyCat 的执行时间;从解析复杂度以及性能考虑,注解
SQL 应尽量简单,因为它只是用来做路由的。

注解可以帮我们解决什么问题呢?

注解使用示例

创建表或存储过程
-- 存储过程
/*!mycat: sql=select * from customer where id =1 */ CREATE PROCEDURE test_proc() BEGIN END ;

-- 表
/*!mycat: sql=select * from customer where id =1 */ CREATE TABLE test2(id INT);
特殊语句自定义分片

Mycat 本身不支持insert select,通过注解支持

/*!mycat: sql=select * from customer where id =1 */ INSERT INTO test2(id) SELECT id FROM order_detail;
多表 ShareJoin
/*!mycat:catlet=io.mycat.catlets.ShareJoin */
select a.order_id,b.price from order_info a, order_detail b where a.nums = b.goods_id;

读写分离

读写分离 : 配置 Mycat 读写离后,默认查询都会从读节点获取数据,但是有些场景需要获取实时数据,如果从读节点获取数据可能因延时而无法实现实时,Mycat 支持通过注解 /*balance*/ 来强制从写节点(write host)查询数据。

/*balance*/ select a.* from customer a where a.id=6666;
读写分离数据库选择(1.6 版本之后)
/*!mycat: db_type=master */ select * from customer;
/*!mycat: db_type=slave */ select * from customer;
/*#mycat: db_type=master */ select * from customer;
/*#mycat: db_type=slave */ select * from customer;

注解支持的’! ‘不被mysql 单库兼容
注解支持的’#'不被MyBatis 兼容

注解原理

Mycat 在执行 SQL 之前会先解析 SQL 语句,在获得分片信息后再到对应的物理节点上执行。如果SQL 语句无法解析,则不能被执行。如果语句中有注解,则会先解析注解的内容获得分片信息,再把真正需要执行的SQL 语句发送对对应的物理节点上。

所以我们在使用主机的时候,应该清楚地知道目标SQL 应该在哪个节点上执行,注解的SQL 也指向这个分片,这样才能使用。如果注解没有使用正确的条件,会导致原始
SQL 被发送到所有的节点上执行,造成数据错误。

分片策略详解

分片的目标是将大量数据和访问请求均匀分布在多个节点上,通过这种方式提升数据服务的存储和负载能力。

总体上分为连续分片和离散分片,还有一种是连续分片和离散分片的结合,例如先范围后取模。

在这里插入图片描述
比如范围分片(id 或者时间)就是典型的连续分片,单个分区的数量和边界是确定的。离散分片的分区总数量和边界是确定的,例如对key 进行哈希运算,或者再取模。

关键词:范围查询、热点数据、扩容

连续分片优点:

  1. 范围条件查询消耗资源少(不需要汇总数据)
  2. 扩容无需迁移数据(分片固定)

连续分片缺点:

  1. 存在数据热点的可能性
  2. 并发访问能力受限于单一或少量DataNode(访问集中)

离散分片优点:

  1. 并发访问能力增强(负载到不同的节点)
  2. 范围条件查询性能提升(并行计算)

离散分片缺点:

  1. 数据扩容比较困难,涉及到数据迁移问题
  2. 数据库连接消耗比较多

连续分片

规则声明:

<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

特点:容易出现冷热数据

按自然月分片
建表语句:

CREATE TABLE `sharding_by_month` (
`create_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`db_nm` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

逻辑表:

<schema name="catmall" checkSQLschema="false" sqlMaxLimit="100">
<table name="sharding_by_month" dataNode="dn1,dn2,dn3" rule="qs-sharding-by-month" />
</schema>

分片规则:

<tableRule name="sharding-by-month">
<rule>
<columns>create_time</columns>
<algorithm>qs-partbymonth</algorithm>
</rule>
</tableRule>

分片算法:

<function name="qs-partbymonth" class="io.mycat.route.function.PartitionByMonth">
<property name="dateFormat">yyyy-MM-dd</property>
<property name="sBeginDate">2021-10-01</property>
<property name="sEndDate">2021-12-31</property>
</function>
  • columns 标识将要分片的表字段,字符串类型,与 dateFormat 格式一致。
  • algorithm 为分片函数。
  • dateFormat 为日期字符串格式。
  • sBeginDate 为开始日期。
  • sEndDate 为结束日期

注意:节点个数要大于月份的个数

测试语句:

INSERT INTO sharding_by_month (create_time,db_nm) VALUES ('2021-10-16', database());
INSERT INTO sharding_by_month (create_time,db_nm) VALUES ('2021-10-27', database()); 
INSERT INTO sharding_by_month (create_time,db_nm) VALUES ('2021-11-04', database()); 
INSERT INTO sharding_by_month (create_time,db_nm) VALUES ('2021-11-11', database());
INSERT INTO sharding_by_month (create_time,db_nm) VALUES ('2021-12-25', database()); 
INSERT INTO sharding_by_month (create_time,db_nm) VALUES ('2021-12-31', database());

另外还有按天分片(可以指定多少天一个分片)、按小时分片

离散分片

枚举分片

将所有可能出现的值列举出来,指定分片。例如:全国 34 个省,要将不同的省的数据存放在不同的节点,可用枚举的方式。

建表语句:

CREATE TABLE `sharding_by_intfile` (
`age` int(11) NOT NULL,
`db_nm` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

逻辑表:

<table name="sharding_by_intfile" dataNode="dn$1-3" rule="qs-sharding-by-intfile" />

分片规则:

<tableRule name="sharding-by-intfile">
<rule>
<columns>sharding_id</columns>
<algorithm>hash-int</algorithm>
</rule>
</tableRule>

分片算法:

<function name="hash-int" class="org.opencloudb.route.function.PartitionByFileMap">
<property name="mapFile">partition-hash-int.txt</property>
<property name="type">0</property>
<property name="defaultNode">0</property>
</function>

type:默认值为 0,0 表示 Integer,非零表示 String。
PartitionByFileMap.java,通过map 来实现。

策略文件:partition-hash-int.txt

16=0
17=1
18=2

插入数据测试:

INSERT INTO `sharding_by_intfile` (age,db_nm) VALUES (16, database()); INSERT INTO `sharding_by_intfile` (age,db_nm) VALUES (17, database()); INSERT INTO `sharding_by_intfile` (age,db_nm) VALUES (18, database());

特点:适用于枚举值固定的场景。

一致性哈希

一致性 hash 有效解决了分布式数据的扩容问题。

建表语句:

CREATE TABLE `sharding_by_murmur` (
`id` int(10) DEFAULT NULL,
`db_nm` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

逻辑表

<schema name="test" checkSQLschema="false" sqlMaxLimit="100">
<table name="sharding_by_murmurhash" primaryKey="id" dataNode="dn$1-3" rule="sharding-by-murmur" />
</schema>

分片规则

<tableRule name="sharding-by-murmur">
<rule>
<columns>id</columns>
<algorithm>qs-murmur</algorithm>
</rule>
</tableRule>

分片算法

<function name="qs-murmur" class="io.mycat.route.function.PartitionByMurmurHash">
<property name="seed">0</property>
<property name="count">3</property>
<property name="virtualBucketTimes">160</property>
</function>

测试语句:

INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (1, database()); INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (2, database()); INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (3, database()); INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (4, database()); INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (5, database()); INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (6, database()); INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (7, database()); INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (8, database()); INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (9, database()); INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (10, database()); INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (11, database()); INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (12, database()); INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (13, database()); INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (14, database()); INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (15, database()); INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (16, database()); INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (17, database());INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (18, database()); INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (19, database()); INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (20, database());

特点:可以一定程度减少数据的迁移。

十进制取模分片

根据分片键进行十进制求模运算。

<tableRule name="mod-long">
<rule>
<columns>sid</columns>
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<!-- how many data nodes -->
<property name="count">3</property>
</function>

特点:分布均匀,但是迁移工作量比较大

固定分片哈希

这是先求模得到逻辑分片号,再根据逻辑分片号直接映射到物理分片的一种散列算法。
建表语句:

CREATE TABLE `sharding_by_long` (
`id` int(10) DEFAULT NULL,
`db_nm` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

逻辑表

<schema name="test" checkSQLschema="false" sqlMaxLimit="100">
<table name="sharding_by_long" dataNode="dn$1-3" rule="qs-sharding-by-long" />
</schema>

分片规则

<tableRule name="qs-sharding-by-long">
<rule>
<columns>id</columns>
<algorithm>qs-sharding-by-long</algorithm>
</rule>
</tableRule>

平均分成 8 片(%1024 的余数,1024=128*8):

<function name="qs-sharding-by-long" class="io.mycat.route.function.PartitionByLong">
<property name="partitionCount">8</property>
<property name="partitionLength">128</property>
</function>
  • partitionCount 为指定分片个数列表。
  • partitionLength 为分片范围列表。

在这里插入图片描述

第二个例子:
两个数组,分成不均匀的 3 个节点(%1024 的余数,1024=2256+1512):

<function name="qs-sharding-by-long" class="io.mycat.route.function.PartitionByLong">
<property name="partitionCount">2,1</property>
<property name="partitionLength">256,512</property>
</function>

3 个节点,对 1024 取模余数的分布
在这里插入图片描述
测试语句

INSERT INTO `sharding_by_long` (id,db_nm) VALUES (222, database()); INSERT INTO `sharding_by_long` (id,db_nm) VALUES (333, database()); INSERT INTO `sharding_by_long` (id,db_nm) VALUES (666, database());

特点:在一定范围内id 是连续分布的。

取模范围分片

逻辑表

<schema name="test" checkSQLschema="false" sqlMaxLimit="100">
<table name="sharding_by_pattern" primaryKey="id" dataNode="dn$0-10" rule="qs-sharding-by-pattern" />
</schema>

建表语句

CREATE TABLE `sharding_by_pattern` (
`id` varchar(20) DEFAULT NULL,
`db_nm` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

分片规则

<tableRule name="sharding-by-pattern">
<rule>
<columns>user_id</columns>
<algorithm>sharding-by-pattern</algorithm>
</rule>
</tableRule>

分片算法

<function name="sharding-by-pattern" class=" io.mycat.route.function.PartitionByPattern">
<property name="patternValue">100</property>
<property name="defaultNode">0</property>
<property name="mapFile">partition-pattern.txt</property>
</function>

patternValue 取模基数,这里设置成 100
partition-pattern.txt,一共 3 个节点

  • id=19%100=19,在dn1
  • id=222%100=22,dn2
  • id=371%100=71,dn3

partition-pattern.txt

# id partition range start-end ,data node index ###### first host configuration
1-20=0
21-70=1
71-100=2
0-0=0

测试语句

INSERT INTO `sharding_by_pattern` (id,db_nm) VALUES (19, database()); INSERT INTO `sharding_by_pattern` (id,db_nm) VALUES (222, database()); INSERT INTO `sharding_by_pattern` (id,db_nm) VALUES (371, database());

特点:可以调整节点的数据分布。

范围取模分片

建表语句

CREATE TABLE `sharding_by_rang_mod` (
`id` bigint(20) DEFAULT NULL,
`db_nm` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

逻辑表

<schema name="test" checkSQLschema="false" sqlMaxLimit="100">
<table name="sharding_by_rang_mod" dataNode="dn$1-3" rule="qs-sharding-by-rang-mod" />
</schema>

分片规则

<tableRule name="qs-sharding-by-rang-mod">
<rule>
<columns>id</columns>
<algorithm>qs-rang-mod</algorithm>
</rule>
</tableRule>

分片算法

<function name="qs-rang-mod" class="io.mycat.route.function.PartitionByRangeMod">
<property name="mapFile">partition-range-mod.txt</property>
</function>

partition-range-mod.txt

# range start-end ,data node group size 0-20000=1
20001-40000=2

解读:先范围后取模。Id 在 20000 以内的,全部分布到dn1。Id 在 20001-40000 的,%2 分布到dn2,dn3。

插入数据:

INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (666, database()); INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (6667, database()); INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (16666, database()); INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (21111, database()); INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (22222, database()); INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (23333, database()); INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (24444, database());

特点:扩容的时候旧数据无需迁移

其他分片规则

应用指定分片PartitionDirectBySubString
日期范围哈希PartitionByRangeDateHash
冷热数据分片PartitionByHotDate
也可以自定义分片规则:

extends AbstractPartitionAlgorithm

implements RuleAlgorithm

切分规则的选择

步骤:

  1. 找到需要切分的大表,和关联的表
  2. 确定分片字段(尽量使用主键),一般用最频繁使用的查询条件
  3. 考虑单个分片的存储容量和请求、数据增长(业务特性)、扩容和数据迁移问题。

例如:按照什么递增?序号还是日期?主键是否有业务意义?

一般来说,分片数要比当前规划的节点数要大。总结:根据业务场景,合理地选择分片规则。

举例:

3.7 亿的数据怎么分表?我是不是分成 3 台服务器?

  1. 一年内到达多少?两年内到达多少?(数据的增长速度)?
    答:一台设备每秒钟往 3 张表各写入一条数据,一共 4 台设备。每张表一天
    86400*4=345600 条。每张表一个月 10368000 条。
    分析:增长速度均匀,可以用日期切分,每个月分一张表。
  2. 什么业务?所有的数据都会访问,还是访问新数据为主?
    答:访问新数据为主,但是所有的数据都可能会访问到。
  3. 表结构和表数据是什么样的?一个月消耗多少空间?
    答:字段不多,算过了,三年数据量有 3.7 亿,30G。
    分析:30G 没必要分库,浪费机器。
  4. 访问量怎么样?并发压力大么?
    答:并发有一点吧
    分析:如果并发量不大,不用分库,只需要在单库分表。不用引入 Mycat 中间件了。如果要自动路由的话可以用Sharding-JDBC,否则就是自己拼装表名。
  5. 3 张表有没有关联查询之类的操作? 答:没有。
    分析:还是拼装表名简单一点。

Mycat 离线扩缩容

当我们规划了数据分片,而数据已经超过了单个节点的存储上线,或者需要下线节点的时候,就需要对数据重新分片。

Mycat 自带的工具

准备工作

  1. mycat 所在环境安装 mysql 客户端程序。
  2. mycat 的 lib 目录下添加 mysql 的 jdbc 驱动包。
  3. 对扩容缩容的表所有节点数据进行备份,以便迁移失败后的数据恢复。

步骤

以取模分片表sharding-by-mod 缩容为例。

时间数据
迁移前数据dn0 3,6 / dn1 1,4/ dn3 2,5
迁移后数据dn0 2,4,6 / dn1 1,3,5
  1. 复制 schema.xml、rule.xml 并重命名为 newSchema.xml、newRule.xml 放于conf 目录下
  2. 修改 newSchema.xml 和 newRule.xml 配置文件为扩容缩容后的 mycat 配置参数(表的节点数、数据源、路由规则)。

注意:
只有节点变化的表才会进行迁移。仅分片配置变化不会迁移。

newSchema.xml

<table name="sharding_by_mod" dataNode="dn1,dn2,dn3" rule="sharding-by-mod" />

改成(减少了一个节点):

<table name="sharding_by_mod" dataNode="dn1,dn2" rule="sharding-by-mod" />

newRule.xml 修改count 个数

<function name="qs-sharding-by-mod-long" class="io.mycat.route.function.PartitionByMod">
<property name="count">2</property>
</function>
  1. 修改 conf 目录下的 migrateTables.properties 配置文件,告诉工具哪些表需要进行扩容或缩容,没有出现在此配置文件的 schema 表不会进行数据迁移,格式:
    注意,
  • 不迁移的表,不要修改dn 个数,否则会报错。
  • ER 表,因为只有主表有分片规则,子表不会迁移。
catmall=sharding-by-mod
  1. dataMigrate.sh 中这个必须要配置
    通 过 命 令
find	/	-name	mysqldump

查找mysqldump 路 径 为"/usr/bin/mysqldump",指定#mysql bin 路径为"/usr/bin/"

#mysql bin 路径
RUN_CMD="$RUN_CMD -mysqlBin= /usr/bin/"
  1. 停止mycat 服务
  2. 执行执行 bin/ dataMigrate.sh 脚本
    注意:必须要配置Java 环境变量,不能用openjdk
  3. 脚本执行完成, 如果最后的数据迁移验证通过, 就可以将之前的
    newSchema.xml 和 newRule.xml 替 换 之 前 的 schema.xml 和 rule.xml 文件,并重启 mycat 即可。

注意事项:

  • 保证分片表迁移数据前后路由规则一致(取模——取模)。
  • 保证分片表迁移数据前后分片字段一致。
  • 全局表将被忽略。
  • 不要将非分片表配置到migrateTables.properties 文件中。
  • 暂时只支持分片表使用MySQL 作为数据源的扩容缩容。

migrate 限制比较多,还可以使用mysqldump。

mysqldump 方式

系统第一次上线,把单张表迁移到Mycat,也可以用mysqldump。
MySQL 导出

mysqldump -uroot -p123456 -h127.0.0.1 -P3306 -c -t --skip-extended-insert gpcat > mysql-1017.sql

-c 代表带列名
-t 代表只要数据,不要建表语句
–skip-extended-insert 代表生成多行 insert(mycat childtable 不支持多行插入 /ChildTable multi insert not provided)
Mycat 导入

mysql -uroot -p123456 -h127.0.0.1 -P8066 catmall < mysql-1017.sql

Mycat 导出

mysqldump -h192.168.200.113 -uroot -p123456 -P8066 -c -t --skip-extended-insert catmall customer > mycat-cust.sql

其他导入方式:

load data local infile ‘/mycat/customer.txt’ into table customer;
source sql ‘/mycat/customer.sql’;

Mycat 核心流程总结

官网的架构图:

在这里插入图片描述

启动

  1. MycatServer 启动,解析配置文件,包括服务器、分片规则等
  2. 创建工作线程,建立前端连接和后端连接

MycatServer 包请参考文末链接

启动SQL

  1. 前 端 连 接 接 收 MySQL命 令
    2 . 解析MySQL,Mycat 用的是 Druid 的DruidParser
  2. 获取路由
  3. 改写MySQL,例如两个条件在两个节点上,则变成两条单独的SQL 例如
select * from customer where id in(5000001, 10000001);

改写成:

select * from customer where id = 5000001-- (dn2 执 行 )
select * from customer where id = 10000001-- (dn3 执行)

又比如多表关联查询,先到各个分片上去获取结果,然后在内存中计算

  1. 与后端数据库建立连接
  2. 发送SQL 语句到MySQL 执行
  3. 获取返回结果
  4. 处理返回结果,例如排序、计算等等
  5. 返回给客户端

源码下载与调试环境搭建

下载源代码,导入工程
git clone https://github.com/MyCATApache/Mycat-Server
配置

schema.xml

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100">
<table name="travelrecord" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
<table name="company" primaryKey="ID" type="global" dataNode="dn1,dn2,dn3" />
<table name="hotnews" primaryKey="ID" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="mod-long" />
</schema>

<dataNode name="dn1" dataHost="localhost1" database="db1" />
<dataNode name="dn2" dataHost="localhost1" database="db2" />
<dataNode name="dn3" dataHost="localhost1" database="db3" />
<dataHost name="localhost1" maxCon="20" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="127.0.0.1:3306" user="root" password="123456">
</writeHost>
</dataHost>
</mycat:schema>
表结构

本地数据库创建db1、db2、db3 数据库,全部执行建表脚本

CREATE TABLE `company` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(64) DEFAULT '',
`market_value` bigint(20) DEFAULT '0', PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;

CREATE TABLE `hotnews` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`title` varchar(64) DEFAULT '',
`content` varchar(512) DEFAULT '0',
`time` varchar(8) DEFAULT '',
`cat_name` varchar(10) DEFAULT '', PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `travelrecord` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`city` varchar(32) DEFAULT '',
`time` varchar(8) DEFAULT '', PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
逻辑表配置

travelrecord 表配置

<table name="travelrecord" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
<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>

hotnews 表配置

<table name="hotnews" primaryKey="ID" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="mod-long" />
<tableRule name="mod-long">
<rule>
<columns>id</columns>
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<!-- how many data nodes -->
<property name="count">3</property>
</function>

company 表配置

<table name="company" primaryKey="ID" type="global" dataNode="dn1,dn2,dn3" />
debug 方式启动

debug 方式启动main 方法

Mycat-Server-1.6.5-RELEASE\src\main\java\io\mycat\MycatStartup.java
连接本机 Mycat 服务

测试语句

insert into travelrecord(`id`, `city`, `time`) values(1, 'beijing', '20210217'); insert into hotnews(`title`, `content`) values('test', 'zhangsan');
insert into company(`name`, `market_value`) values('spring', 100);
调试入口

连 接 入 口 :

io.mycat.net.NIOAcceptor#accept

SQL 入 口 :

io.mycat.server.ServerQueryHandler#query

Step Over 可以看到上一层的调用

后记

更多架构知识,欢迎关注本系列文章Java架构师成长之路

;