概述
Mycat 是什么?从定义和分类来看,它是一个开源的分布式数据库系统,是一个实现了 MySQL 协议的 Server,前端用户可以把它看作是一个数据库代理,用 MySQL 客户端工具和命令行访问,而其后端可以用 MySQL 原生(Native) 协议与多个 MySQL 服务器通信,也可以用 JDBC 协议与大多数主流数据库服务器通信,其核心功能是分表分库,即将一个大表水平分割为 N 个小表,存储在后端 MySQL 服务器里或者其他数据库里
什么是分库分表(数据切分)
简单来说,就是指通过某种特定的条件,将我们存放在同一个数据库中的数据分散存放到多个数据库(主机)上面,以达到分散单台设备负载的效果
数据的切分(Sharding)根据其切分规则的类型,可以分为两种切分模式。一种是按照不同的表(或者 Schema)来切分到不同的数据库(主机)之上,这种切可以称之为数据的垂直(纵向)切分;另外一种则是根据表中的数据的逻辑关系,将同一个表中的数据按照某种条件拆分到多台数据库(主机)上面,这种切分称之为数据的水平(横向)切分
垂直切分的最大特点就是规则简单,实施也更为方便,尤其适合各业务之间的耦合度非常低,相互影响很小,业务逻辑非常清晰的系统。在这种系统中,可以很容易做到将不同业务模块所使用的表分拆到不同的数据库中。根据不同的表来进行拆分,对应用程序的影响也更小,拆分规则也会比较简单清晰
水平切分于垂直切分相比,相对来说稍微复杂一些。因为要将同一个表中的不同数据拆分到不同的数据库中,对于应用程序来说,拆分规则本身就较根据表名来拆分更为复杂,后期的数据维护也会更为复杂一些
垂直切分
一个数据库由很多表的构成,每个表对应着不同的业务,垂直切分是指按照业务将表进行分类,分布到不同的数据库上面,这样也就将数据或者说压力分担到不同的库上面,如下图:
系统被切分成了,用户,订单交易,支付几个模块。一个架构设计较好的应用系统,其总体功能肯定是由很多个功能模块所组成的,而每一个功能模块所需要的数据对应到数据库中就是一个或者多个表。而在架构设计中,各个功能模块相互之间的交互点越统一越少,系统的耦合度就越低,系统各个模块的维护性以及扩展性也就越好。这样的系统,实现数据的垂直切分也就越容易
但是往往系统之有些表难以做到完全的独立,存在这扩库 join 的情况,对于这类的表,就需要去做平衡,是数据库让步业务,共用一个数据源,还是分成多个库,业务之间通过接口来做调用。在系统初期,数据量比较少,或者资源有限的情况下,会选择共用数据源,但是当数据发展到了一定的规模,负载很大的情况,就需要必须去做分割
一般来讲业务存在着复杂 join 的场景是难以切分的,往往业务独立的易于切分。 如何切分,切分到何种程度是考验技术架构的一个难题
-
优点
- 拆分后业务清晰,拆分规则明确
- 系统之间整合或扩展容易
- 数据维护简单
-
缺点
- 部分业务表无法
join
,只能通过接口方式解决,提高了系统复杂度 - 受每种业务不同的限制存在单库性能瓶颈,不易数据扩展跟性能提高
- 事务处理复杂
- 部分业务表无法
由于垂直切分是按照业务的分类将表分散到不同的库,所以有些业务表会过于庞大,存在单库读写与存储瓶颈,所以就需要水平拆分来做解决
水平切分
相对于垂直拆分,水平拆分不是将表做分类,而是按照某个字段的某种规则来分散到多个库之中,每个表中包含一部分数据。简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中的某些行切分到一个数据库,而另外的某些行又切分到其他的数据库中,如图:
拆分数据就需要定义分片规则。关系型数据库是行列的二维模型,拆分的第一原则是找到拆分维度。 比如:从会员的角度来分析,商户订单交易类系统中查询会员某天某月某个订单,那么就需要按照会员结合日期来拆分,不同的数据按照会员 ID
做分组,这样所有的数据查询 join
都会在单库内解决;如果从商户的角度来讲,要查询某个商家某天所有的订单数,就需要按照商户 ID
做拆分;但是如果系统既想按会员拆分,又想按商家数据,则会有一定的困难。如何找到合适的分片规则需要综合考虑衡量。几种典型的分片规则包括:
- 按照用户
ID
求模,将数据分散到不同的数据库,具有相同数据用户的数据都被分散到一个库中 - 按照日期,将不同月甚至日的数据分散到不同的库中
- 按照某个特定的字段求摸,或者根据特定范围段分散到不同的库中
如图,切分原则都是根据业务找到适合的切分规则分散到不同的库,下面用用户 ID
求模举例
-
优点
- 拆分规则抽象好,
join
操作基本可以数据库做 - 不存在单库大数据,高并发的性能瓶颈
- 应用端改造较少
- 提高了系统的稳定性跟负载能力
- 拆分规则抽象好,
-
缺点
- 拆分规则难以抽象
- 分片事务一致性难以解决
- 数据多次扩展难度跟维护量极大
- 跨库
join
性能较差
-
垂直与水平拆分的共同缺点
- 引入分布式事务的问题
- 跨节点 join 的问题
- 跨节点合并排序分页问题
- 多数据源管理问题
数据源管理方案
-
客户端模式,在每个应用程序模块中配置管理自己需要的一个(或者多个)数据源,直接访问各个数据库,在模块内完成数据的整合
-
通过中间代理层来统一管理所有的数据源,后端数据库集群对前端应用程序透明
绝大部分人在面对上面这两种解决思路的时候都会倾向于选择第二种,尤其是系统不断变得庞大复杂的时候。确实,这是一个非常正确的选择,虽然短期内需要付出的成本可能会相对更大一些,但是对整个系统的扩展性来说,是非常有帮助的
数据库中间件 MyCat
MyCat 是一个强大的数据库中间件,不仅仅可以用作读写分离、以及分表分库、容灾备份,而且可以用于多租户应用开发、云平台基础设施、让你的架构具备很强的适应性和灵活性,借助于即将发布的 MyCat 智能优化模块,系统的数据访问瓶颈和热点一目了然,根据这些统计分析数据,你可以自动或手工调整后端存储,将不同的表映射到不同存储引擎上,而整个应用的代码一行也不用改变
应用场景
- 单纯的读写分离,此时配置最为简单,支持读写分离,主从切换
- 分表分库,对于超过 1000 万的表进行分片,最大支持 1000 亿的单表分片
- 多租户应用,每个应用一个库,但应用程序只连接 MyCat,从而不改造程序本身,实现多租户化
- 报表系统,借助于 MyCat 的分表能力,处理大规模报表的统计
- 替代 Hbase,分析大数据
- 作为海量数据实时查询的一种简单有效方案,比如 100 亿条频繁查询的记录需要在 3 秒内查询出来结果,除了基于主键的查询,还可能存在范围查询或其他属性查询,此时 MyCat 可能是最简单有效的选择
环境准备
部署方式全部基于 Docker
部署 3 台 MySQL 容器
案例分片方案按照自定义数字范围分片方式(auto-sharding-long
)进行数据库分片,其规则要求需要 3 台 MySQL,docker-compose.yml
配置如下
- mysql-1
version: '3.1'
services:
mysql-1:
image: mysql
container_name: mysql-1
environment:
MYSQL_ROOT_PASSWORD: 123456
command:
--default-authentication-plugin=mysql_native_password
--character-set-server=utf8mb4
--collation-server=utf8mb4_general_ci
--explicit_defaults_for_timestamp=true
--lower_case_table_names=1
ports:
- 3306:3306
volumes:
- ./data:/var/lib/mysql
- mysql-2
version: '3.1'
services:
mysql-2:
image: mysql
container_name: mysql-2
environment:
MYSQL_ROOT_PASSWORD: 123456
command:
--default-authentication-plugin=mysql_native_password
--character-set-server=utf8mb4
--collation-server=utf8mb4_general_ci
--explicit_defaults_for_timestamp=true
--lower_case_table_names=1
ports:
- 3307:3306
volumes:
- ./data:/var/lib/mysql
- mysql-3
version: '3.1'
services:
mysql-3:
image: mysql
container_name: mysql-3
environment:
MYSQL_ROOT_PASSWORD: 123456
command:
--default-authentication-plugin=mysql_native_password
--character-set-server=utf8mb4
--collation-server=utf8mb4_general_ci
--explicit_defaults_for_timestamp=true
--lower_case_table_names=1
ports:
- 3308:3306
volumes:
- ./data:/var/lib/mysql
部署 MyCat 数据库中间件
- 克隆
git clone https://github.com/dekuan/docker.mycat.git
- 构建
cd docker.mycat
docker-compose build
- 启动
# 注意:配置完成后再启动
docker-compose up -d
配置 MyCat 数据库分片
- 服务端用户名密码配置:
vi config/mycat/server.xml
,找到第 90 行,参考如下内容配置
<mycat:server xmlns:mycat="http://io.mycat/">
<!-- Mycat 数据库用户名 -->
<user name="root">
<!-- Mycat 数据库密码 -->
<property name="password">123456</property>
<!-- Mycat 数据库名 -->
<property name="schemas">myshop</property>
<!-- 是否使用加密的密码,0 表示不使用加密的密码 -->
<property name="usingDecrypt">0</property>
</user>
</mycat:server>
- 数据节点、数据库、分库分表配置:
vi config/mycat/schema.xml
,参考如下内容配置
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="myshop" checkSQLschema="true" sqlMaxLimit="100">
<!-- 需要分片的表
在节点 dataNode1, dataNode2, dataNode3 上分片
分片规则是 auto-sharding-long 即连续分片规则之自定义数字范围分片
对应的分片规则配置文件在 config/mycat/rule.xml
-->
<table
name="tb_admin"
primaryKey="id"
dataNode="dataNode1,dataNode2,dataNode3"
rule="auto-sharding-long"/>
</schema>
<!-- 数据节点 dataNode1,对应的主机 dataHost1, 对应是数据库 myshop_1 -->
<dataNode name="dataNode1" dataHost="dataHost1" database="myshop_1" />
<dataNode name="dataNode2" dataHost="dataHost2" database="myshop_2" />
<dataNode name="dataNode3" dataHost="dataHost3" database="myshop_3" />
<!-- 主机 dataHost1 -->
<dataHost name="dataHost1" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="-1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<!-- MySQL 数据库的连接串 -->
<writeHost
host="192.168.141.206"
url="jdbc:mysql://192.168.141.206:3306?useSSL=false&serverTimezone=UTC&characterEncoding=utf8"
user="root" password="123456">
<!--<readHost host="192.168.1.104" url="192.168.1.104:3306" user="druid" password="druid" />-->
</writeHost>
</dataHost>
<dataHost name="dataHost2" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="-1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost
host="192.168.141.206"
url="jdbc:mysql://192.168.141.206:3307?useSSL=false&serverTimezone=UTC&characterEncoding=utf8"
user="root" password="123456">
<!--<readHost host="192.168.1.104" url="192.168.1.104:3306" user="druid" password="druid" />-->
</writeHost>
</dataHost>
<dataHost name="dataHost3" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="-1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost
host="192.168.141.206"
url="jdbc:mysql://192.168.141.206:3308?useSSL=false&serverTimezone=UTC&characterEncoding=utf8"
user="root" password="123456">
<!--<readHost host="192.168.1.104" url="192.168.1.104:3306" user="druid" password="druid" />-->
</writeHost>
</dataHost>
</mycat:schema>
- 分片规则配置:
vi config/mycat/rule.xml
,分别查看第 32 行和第 105 行
<!-- 第 32 行 -->
<tableRule name="auto-sharding-long">
<rule>
<!-- 指定分片表列名 -->
<columns>id</columns>
<!-- 指定分片函数与 function 对应 -->
<algorithm>rang-long</algorithm>
</rule>
</tableRule>
<!-- 第 105 行 -->
<function name="rang-long" class="io.mycat.route.function.AutoPartitionByLong">
<!-- 函数中的 mapFile 代表规则配置文件的路径,config/mycat/autopartition-long.txt -->
<property name="mapFile">autopartition-long.txt</property>
</function>
- 自定义数字范围分片规则:
vi config/mycat/autopartition-long.txt
# range start-end ,data node index
# K=1000,M=10000.
# ID 0-5000000 保存在 dataNode1
0-500M=0
# ID 5000000-10000000 保存在 dataNode2
500M-1000M=1
# ID 10000000-15000000 保存在 dataNode3
1000M-1500M=2
测试 MyCat 数据分片
分别创建数据库
分别在 3 台 MySQL 数据库上创建 myshop_1
、myshop_2
、myshop_3
,按照刚才创建 MySQL 容器的序号创建
通过 MyCat 操作数据库
使用 SQLyog 之类的客户端工具连接 MyCat 数据库,默认端口号为 8066
创建测试数据
- 建表语句
create table tb_admin (id int not null primary key,name varchar(100),sharding_id int not null);
- 测试数据
insert into tb_admin(id, name,sharding_id) values(1000000, 'lixiaohong', 0);
insert into tb_admin(id, name,sharding_id) values(6000000, 'lixiaolu', 1);
insert into tb_admin(id, name,sharding_id) values(7000000, 'pgone', 1);
insert into tb_admin(id, name,sharding_id) values(11000000, 'jianailiang', 2);
检验测试结果
按照上面的配置规则
id
为 1000000
的数据应该写入 dataNode1
即 myshop_1.tb_admin
表中
id
为 6000000
的数据应该写入 dataNode2
即 myshop_2.tb_admin
表中
id
为 7000000
的数据应该写入 dataNode2
即 myshop_2.tb_admin
表中
id
为 11000000
的数据应该写入 dataNode3
即 myshop_3.tb_admin
表中
至此这说明分片成功了
网友评论