一、分库分表(延续上节博客的内容)
1. 垂直拆分
(1) 场景

(2) 配置
XML
<schema name="SHOPPING" checkSQLschema="true" sqlMaxLimit="100" >
<table name="tb_goods_base" dataNode="dn1" primaryKey="id"/>
<table name="tb_goods_cat" dataNode="dn1" primaryKey="id"/>
<table name="tb_goods_desc" dataNode="dn1" primaryKey="goods_id"/>
<table name="tb_goods_item" dataNode="dn1" primaryKey="id"/>
<table name="tb_order_item" dataNode="dn2" primaryKey="id"/>
<table name="tb_order_master" dataNode="dn2" primaryKey="order_id" rule="order_rule" />
<table name="tb_order_pay_log" dataNode="dn2" primaryKey="out_trade_no"/>
<table name="tb_user" dataNode="dn3" primaryKey="id"/>
<table name="tb_areas_provinces" dataNode="dn3" primaryKey="id"/>
<table name="tb_areas_city" dataNode="dn3" primaryKey="id"/>
<table name="tb_areas_region" dataNode="dn3" primaryKey="id"/>
<table name="tb_user_address" dataNode="dn3" primaryKey="id"/>
</schema>
<dataNode name="dn1" dataHost="dhost1" database="shopping" />
<dataNode name="dn2" dataHost="dhost2" database="shopping" />
<dataNode name="dn3" dataHost="dhost3" database="shopping" />
说明:
schema 节点下关联多个 table 标签,不同 table 对应不同 dataNode:
① dn1 对应商品类表(如 tb_goods_base、tb_goods_cat 等)
② dn2 对应订单类表(如 tb_order_item、tb_order_master 等)
③ dn3 对应用户及地址类表(如 tb_user、tb_areas_provinces 等)
(3) 测试
① 在 mycat 的命令行中,通过 source 指令导入表结构及对应数据,查看数据分布情况:
bash
source /root/shopping_table.sql
source /root/shopping_insert.sql
② 查询用户的收件人及收件人地址信息 (包含省、市、区):
sql
select ua.user_id,ua.contact,p.province,c.city,r.area,ua.address from tb_user_address ua,tb_areas_city c,tb_areas_provinces p,tb_areas_region r where ua.province_id = p.provinceid and ua.city_id = c.cityid and ua.town_id = r.areaid ;
③ 查询每一笔订单及订单的收件地址信息 (包含省、市、区):
sql
SELECT o.order_id, p.payment,o.receiver,p.province,c.city,r.area FROM tb_order_master o,tb_areas_provinces p,tb_areas_city c,tb_areas_region r WHERE o.receiver_province = p.provinceid AND o.receiver_city = c.cityid AND o.receiver_region = r.areaid ;
(4) 全局表配置
对于省、市、区 / 县表tb_areas_provinces, tb_areas_city, tb_areas_region,是属于数据字典表,在多个业务模块中都可能会遇到,可以将其设置为全局表,利于业务操作。
XML
<table name="tb_areas_provinces" dataNode="dn1,dn2,dn3" primaryKey="id" type="global"/>
<table name="tb_areas_city" dataNode="dn1,dn2,dn3" primaryKey="id" type="global"/>
<table name="tb_areas_region" dataNode="dn1,dn2,dn3" primaryKey="id" type="global"/>
2. 水平拆分
(1) 场景
在业务系统中,有一张表 (日志表),业务系统每天都会产生大量的日志数据,单台服务器的数据存储及处理能力是有限的,可以对数据库表进行拆分。
(2) 配置
① shema.xml
XML
<schema name="ITCAST" checkSQLschema="true" sqlMaxLimit="100" >
<table name="tb_log" dataNode="dn4,dn5,dn6" rule="mod-long" />
</schema>
<dataNode name="dn4" dataHost="dhost1" database="itcast" />
<dataNode name="dn5" dataHost="dhost2" database="itcast" />
<dataNode name="dn6" dataHost="dhost3" database="itcast" />
② server.xml
XML
<user name="root" defaultAccount="true">
<property name="password">123456</property>
<property name="schemas">SHOPPING,ITCAST</property>
<!-- 表级 DML 权限设置 -->
<!--
<privileges check="false">
<schema name="TESTDB" dml="0110">
<table name="tb01" dml="0000"></table>
<table name="tb02" dml="1111"></table>
</schema>
</privileges>
-->
</user>
(3) 测试
在 mycat 的命令行中,执行如下 SQL 建表、并插入数据,查看数据分布情况。
sql
CREATE TABLE tb_log (
id bigint(20) NOT NULL COMMENT 'ID',
model_name varchar(200) DEFAULT NULL COMMENT '模块名',
model_value varchar(200) DEFAULT NULL COMMENT '模块值',
type varchar(200) DEFAULT NULL COMMENT '类型',
return_value varchar(200) DEFAULT NULL COMMENT '返回值',
return_class varchar(200) DEFAULT NULL COMMENT '返回类型',
operate_user varchar(200) DEFAULT NULL COMMENT '操作用户',
operate_time timestamp NULL DEFAULT NULL COMMENT '操作时间',
param_and_value varchar(500) DEFAULT NULL COMMENT '请求参数名及参数值',
operate_class varchar(200) DEFAULT NULL COMMENT '操作类',
operate_method varchar(200) DEFAULT NULL COMMENT '操作方法',
cost_time bigint(20) DEFAULT NULL COMMENT '执行方法耗时,单位ms',
summary text DEFAULT NULL COMMENT '摘要 1.2,Arond.102',
PRIMARY KEY (id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. 分片规则
(1) 范围分片
根据指定的字段及其配置的范围与数据节点的对应情况,来决定该数据属于哪一个分片。

说明:
- 0~5000000 → dataNode1
- 5000001~10000000 → dataNode2
- 10000001 及以上 → dataNode3

(2) 取模分片
根据指定的字段值与节点数量进行求模运算,根据运算结果,来决定该数据属于哪一个分片。

说明: 通过id % 3的结果分配数据:
id%3=0→ dataNode1id%3=1→ dataNode2id%3=2→ dataNode3
(3) 一致性hash 分片
所谓一致性哈希,相同的哈希因子计算总是被划分到相同的分区表中,不会因为分区节点的增加而改变原来数据的分区位置。

① schema.xml
XML
<table name="tb_order" dataNode="dn4,dn5,dn6" rule="sharding-by-murmur" />
<dataNode name="dn4" dataHost="dhost1" database="itcast" />
<dataNode name="dn5" dataHost="dhost2" database="itcast" />
<dataNode name="dn6" dataHost="dhost3" database="itcast" />
② rule.xml
XML
<tableRule name="sharding-by-murmur">
<rule>
<columns>id</columns>
<algorithm>murmur</algorithm>
</rule>
</tableRule>
<function name="murmur" class="io.mycat.route.function.PartitionByMurmurHash">
<property name="seed">0</property> <!-- 默认是0 -->
<property name="count">3</property> <!-- 要分片的数据库节点数,必须指定,否则没法分片 -->
<property name="virtualBucketTimes">160</property> <!-- 一个实际的数据库节点被映射为这么多虚拟节点,默认是160倍,也就是虚拟节点数是物理节点数的160倍 -->
<!-- <property name="weightMapFile">weightMapFile</property> 节点的权重,没有指定权重的节点默认是1。 -->
</function>
(4) 枚举分片
通过在配置文件中配置可能的枚举值,指定数据分布到不同数据库节点上。本规则适用于按照省份、性别、状态拆分数据等业务。

(5) 应用指定算法
运行阶段由应用自主决定路由到哪个分片,直接根据字符串(必须是数字)计算分片号。

说明:
- 00xxxx → 对应 dataNode1 的数据库
- 01xxxx → 对应 dataNode2 的数据库
- 02xxxx → 对应 dataNode3 的数据库

(6) 固定hash算法
该算法类似于十进制的求模运算,但是为二进制的操作,例如,取 id 的二进制低 10 位与1111111111进行位 & 运算。

说明:
① 任何一个十位的二进制与 1111111111(十位全1)进行与运算后的结果,结果就是这个十位二进制数本身。
② 原因是这个二进制数的每一位都是 1,因此在与运算中:
- 如果原数某一位是 1 → 1 & 1 = 1
- 如果原数某一位是 0 → 0 & 1 = 0
也就是说,它不会改变原数的任何一位,只是保留原数的所有位。
③ 举例验证:假设原数是 1010101010(十位):
bash
1010101010
& 1111111111
-----------
1010101010 ← 结果和原数完全相同

(7) 字符串hash解析
截取字符串中的指定位置的子字符串,进行hash算法,算出分片


运算示例:
以 "world" 为例:
- 截取子串:
world→ 取0:2对应 "wor" - 执行 hash 运算:得到结果 26629
- 计算分片:
26629 & (1024-1) = 5
(8) 按天分片

说明:
配置参数: begin:2022-01-01、end:2022-01-30、partitionDay:10
分片时间区间与 dataNode 对应:
2022-01-01 ~ 2022-01-10→ dataNode12022-01-11 ~ 2022-01-20→ dataNode22022-01-21 ~ 2022-01-30→ dataNode3

(9) 按月分片
使用场景为按月份来分片,每个自然月为一个分片

说明:
分片时间范围为 begin:2022-01-01 至 end:2022-03-31,数据按月份对应到不同 dataNode:
- dataNode1 对应数据:2022-01-10、2022-04-12(注:结束时间后会循环分片)
- dataNode2 对应数据:2022-02-20
- dataNode3 对应数据:2022-03-31
4. Mycat 管理及监控
(1) Mycat 原理

① select * from tb_user;
- 客户端把 SQL 发给 Mycat Server;
- Mycat 先解析 SQL ,再做分片分析(按规则,这个 SQL 要查所有分片);
- 经路由分析,确定要访问右边 3 个数据库节点;
- 从 3 个节点取数据后,做分页、排序、聚合、结果合并;
- 把合并后的结果返回给客户端。
② select * from tb_user where status in(1,3) order by id;
- 客户端发 SQL 后,Mycat 解析出
status条件; - 分片分析 + 路由分析:只需要访问
status=1(dn1)和status=3(dn3)的数据库; - 从这 2 个节点取数据后,做排序处理 + 结果合并;
- 返回最终结果给客户端。
(2) Mycat 管理
① Mycat 默认开通 2 个端口,可在server.xml中修改:
- 8066 数据访问端口,用于执行 DML 和 DDL 操作。
- 9066 数据库管理端口,用于管理 Mycat 集群状态。
② 连接管理端口的命令:
bash
mysql -h 192.168.200.210 -P 9066 -uroot -p123456
③ Mycat 管理命令及含义:
| 命令 | 含义 |
|---|---|
| show @@help | 查看 Mycat 管理工具文档 |
| show @@version | 查看 Mycat 的版本 |
| reload @@config | 重新加载 Mycat 的配置文件 |
| show @@datasource | 查看 Mycat 的数据源信息 |
| show @@datanode | 查看 Mycat 现有的分片节点信息 |
| show @@threadpool | 查看 Mycat 的线程池信息 |
| show @@sql | 查看执行的 SQL |
| show @@sql.sum | 查看执行的 SQL 统计 |
二、读写分离
1. 介绍
(1) 读写分离,简单地说是把对数据库的读和写操作分开,对应不同的数据库服务器。主数据库提供写操作,从数据库提供读操作,这样能有效地减轻单台数据库的压力。
(2) 通过 MyCat 即可轻易实现上述功能,不仅可以支持 MySQL,也可以支持 Oracle 和 SQL Server。

说明:
① 应用程序连接 MyCat,MyCat 将insert/update/delete操作路由到writeHost对应的主库(mysql (m))
② 将select操作路由到readHost对应的从库(mysql (s))
③ 主库与从库之间通过主从复制(blog)同步数据
2. 一主一从读写分离
(1) 配置


Mycat 中balance参数的取值及含义说明表:
| 参数值 | 含义 |
|---|---|
| 0 | 不开启读写分离机制,所有读操作都发送到当前可用的 writeHost 上 |
| 1 | 全部的 readHost 与备用的 writeHost 都参与 select 语句的负载均衡(主要针对于双主双从模式) |
| 2 | 所有的读写操作都随机在 writeHost、readHost 上分发 |
| 3 | 所有的读请求随机分发到 writeHost 对应的 readHost 上执行,writeHost 不负担读压力 |
3. 双主双从
(1) 介绍
一个主机 Master1 用于处理所有写请求,它的从机 Slave1 和另一台主机 Master2 还有它的从机 Slave2 负责所有读请求。当 Master1 主机宕机后,Master2 主机负责写请求,Master1、Master2 互为备机。架构图如下:

说明:
① 应用程序连接 MyCat,MyCat 分发请求;
② mysql (m1)(Master1)复制数据到 mysql (s1)(Slave1),mysql (m2)(Master2)复制数据到 mysql (s2)(Slave2),同时 mysql (m1) 与 mysql (m2) 之间也存在复制关系,mysql (m1) 为主、mysql (m2) 为备
**Q:**主库不是负责写的吗?为什么 Master2 负责读了?
A: 双主双从中,Master1 是 "主用主库":默认承担所有写请求; 而 Master2 是"备用主库"
- 平时(Master1 正常运行时),它不处理写请求,而是作为读节点来分担读压力(和 Slave1、Slave2 一起负责读请求);
- 只有当 Master1 宕机后,它才会切换为 "主库",接手写请求。
(2) 准备工作
我们需要准备 5 台服务器,具体的服务器及软件安装情况如下:
| 编号 | IP | 预装软件 | 角色 |
|---|---|---|---|
| 1 | 192.168.200.210 | MyCat、MySQL | MyCat 中间件服务器 |
| 2 | 192.168.200.211 | MySQL | M1(Master1) |
| 3 | 192.168.200.212 | MySQL | S1(Slave1) |
| 4 | 192.168.200.213 | MySQL | M2(Master2) |
| 5 | 192.168.200.214 | MySQL | S2(Slave2) |
关闭以上所有服务器的防火墙:
systemctl stop firewalldsystemctl disable firewalld
(3) 搭建
① 主库配置(M1)
修改配置文件 /etc/my.cnf
bash
#mysql 服务ID,保证整个集群环境中唯一,取值范围:1 - 2^32-1,默认为1
server-id=1
#指定同步的数据库
binlog-do-db=db01
binlog-do-db=db02
binlog-do-db=db03
#在作为从数据库的时候,有写入操作也要更新二进制日志文件
log-slave-updates
重启 MySQL 服务器
bash
systemctl restart mysqld
② 主库配置(M2)
修改配置文件 /etc/my.cnf
bash
#mysql 服务ID,保证整个集群环境中唯一,取值范围:1 - 2^32-1,默认为1
server-id=3
#指定同步的数据库
binlog-do-db=db01
binlog-do-db=db02
binlog-do-db=db03
#在作为从数据库的时候,有写入操作也要更新二进制日志文件
log-slave-updates
重启 MySQL 服务器
bash
systemctl restart mysqld
③ 两台主库创建账户并授权(注:该操作需在 Master1、Master2 两台主库分别执行)
bash
# 创建itcast用户,并设置密码,该用户可在任意主机连接该MySQL服务
CREATE USER 'itcast'@'%' IDENTIFIED WITH mysql_native_password BY 'Root@123456';
# 为'itcast'@'%'用户分配主从复制权限
GRANT REPLICATION SLAVE ON *.* TO 'itcast'@'%';
查看两台主库的二进制日志坐标
bash
show master status;
④ 从库配置(S1)
修改配置文件 /etc/my.cnf
bash
#mysql 服务ID,保证整个集群环境中唯一,取值范围:1 - 2^32-1,默认为1
server-id=2
重启 MySQL 服务器
bash
systemctl restart mysqld
⑤ 从库配置(S2)
修改配置文件 /etc/my.cnf
bash
#mysql 服务ID,保证整个集群环境中唯一,取值范围:1 - 2^32-1,默认为1
server-id=4
重启 MySQL 服务器
bash
systemctl restart mysqld
⑥ 两台从库关联的从库
执行CHANGE MASTER TO命令,关联对应的主库(注意:slave1 对应 master1,slave2 对应 master2):
sql
CHANGE MASTER TO
MASTER_HOST='xxx.xxx.xxx.xxx', # 对应主库的IP
MASTER_USER='xxx', # 主库授权的复制用户(如itcast)
MASTER_PASSWORD='xxx', # 复制用户的密码(如Root@123456)
MASTER_LOG_FILE='xxx', # 主库`show master status`查到的日志文件名
MASTER_LOG_POS=xxx; # 主库`show master status`查到的日志位置
启动两台从库主从复制,查看从库状态
sql
# 启动主从复制
start slave;
# 查看从库状态(需确保Slave_IO_Running、Slave_SQL_Running均为Yes)
show slave status\G;
(4) 测试
分别在两台主库Master1、Master2上执行DDL、DML语句,查看涉及到的数据库服务器的数据同步情况。
sql
create database db01;
use db01;
create table tb_user(
id in(11)not null primary key,
name varchar(50) not null,
sex varcahr(1)
)engine=innodb default charset=utf8mb4
insert into tb user(id,name,sex) values(l,'Tom','1');
insert into tb user(id,name,sex) values(2,'Trigger','0');
insert into tb user(id,name,sex) values(3,'Dawn','1');
insert into tb user(id,name,sex) values(4,"ack Ma','1');
insertinto tb user(id,name,sex) values(5,'Coco','0');
insert into tb user(id,name,sex) values(6,'erry','1');
4. 双主双从读写分离
(1) 配置


(2) 测试
① 登录MyCat,测试查询及更新操作,判定是否能够进行读写分离,以及读写分离的策略是否正确。
② 当主库挂掉一个之后,是否能够自动切换。