就是指通过某种特定的条件,将我们存放在同一个数据库中的数据分散存放到多个数据库(主机)上面,以达到分散单台设备负载的效果
◆ 数据库的拆分简单明了,拆分规则明确;
◆ 应用程序模块清晰明确,整合容易;
◆ 数据维护方便易行,容易定位;
◆ 部分表关联无法在数据库级别完成,需要在程序中完成,存在跨库join的问题,对于这类的表,就需要去做平衡,是数据库让步业务,共用一个数据源,还是分成多个库,业务之间通过接口来做调用;在系统初期,数据量比较少,或者资源有限的情况下,会选择共用数据源,但是当数据发展到了一定的规模,负载很大的情况,就需要必须去做分割。
◆ 对于访问极其频繁且数据量超大的表仍然存在性能瓶颈,不一定能满足要求;
◆ 事务处理相对更为复杂;
◆ 切分达到一定程度之后,扩展性会遇到限制;
◆ 过多切分可能会带来系统过渡复杂而难以维护。
相对于垂直拆分,水平拆分不是将表做分类,而是按照某个字段的某种规则来分散到多个库之中,每个表中包含一部分数据。
可以将数据的水平切分理解为是按照数据行的切分,就是将表中的某些行切分到一个数据库,而另外的某些行又切分到其他的数据库中
表关联基本能够在数据库端全部完成;
不会存在某些超大型数据量和高负载的表遇到瓶颈的问题;
应用程序端整体架构改动相对较少;
事务处理相对简单;
只要切分规则能够定义好,基本上较难遇到扩展性限制;
切分规则相对更为复杂,很难抽象出一个能够满足整个数据库的切分规则;
后期数据的维护难度有所增加,人为手工定位数据更困难;
应用系统各模块耦合度较高,可能会对后面数据的迁移拆分造成一定的困难。
节点合并排序分页问题;
多数据源管理问题。
按照用户ID求模,将数据分散到不同的数据库,具有相同数据用户的数据都被分散到一个库中。
按照日期,将不同月甚至日的数据分散到不同的库中。
按照某个特定的字段求摸,或者根据特定范围段分散到不同的库中。
几个原则
数据切分带来的问题
引入分布式事务的问题;
跨节点 Join 的问题;
跨节点合并排序分页问题;
是一个数据库代理
MySQL、SQL Server、Oracle、DB2、PostgreSQL等主流数据库,也支持MongoDB这种新型NoSQL方式的存储
Mycat并不存储数据,只做数据路由。其会拦截用户发送过来的SQL语句,对SQL语句做一些特定的分析:如分片分析、路由分析、读写分离分析、缓存分析等,然后将此SQL发往后端的真实数据库,并将返回的结果做适当的处理,最终再返回给用户。
因此,mycat中存在一些逻辑XX性的概念,如逻辑库,逻辑表。
存在在mycat里面的虚拟库,我们可以通过直接操作逻辑库,而具体的分片、分表还是分库,由我们配置后,交给mycat自动处理。
存在在mycat里面的虚拟表【简单了解下,后面讲用的时候再细讲】
分片表,是指那些原有的很大数据的表,需要切分到多个数据库的表,这样,每个分片都有一部分数据,所有分片构成了完整的数据
子表的记录与所关联的父表记录存放在同一个数据分片上,即子表依赖于父表,通过表分组(Table Group)保证数据Join不会跨库操作。
表分组(Table Group)是解决跨分片数据join的一种很好的思路,也是数据切分规划的重要一条规则
例如字典表,每一个数据分片节点上有保存了一份字典表数据
数据冗余是解决跨分片数据join的一种很好的思路,也是数据切分规划的另外一条重要规则
数据切分后,一个大表被分到不同的分片数据库上面,每个表分片所在的数据库就是分片节点。
在dataNode中可以指定对应的dataHost
dataHost,就可以指定我们真实的物理主机的信息。当然,做mycat高可用时,也可以指定某个mycat节点作为“真实主机”。
数据切分后,每个分片节点(dataNode)不一定都会独占一台机器,同一机器上面可以有多个分片数据库,这样一个或多个分片节点(dataNode)所在的机器就是节点主机(dataHost),为了规避单节点主机并发数限制,尽量将读写压力高的分片节点(dataNode)均衡的放在不同的节点主机(dataHost)
前面讲了数据切分,一个大表被分成若干个分片表,就需要一定的规则,这样按照某种业务规则把数据分到某个分片的规则就是分片规则。
如:取模、按天/月分区、固定hash、一致性hash等。
数据切分后,原有的关系数据库中的主键约束在分布式条件下将无法使用,因此需要引入外部机制保证数据唯一性标识,这种保证全局性的数据唯一标识的机制就是全局序列号(sequence)。
mycat中有几种实现全局序列号的方式,后面会具体讲。
接下来看看mycat的基础使用:
源码地址:
mycat源码地址
VM参数:
-DMYCAT_HOME=${换成源码路径}\src\main
-XX:MaxDirectMemorySize=512M
首先先简单演示下,不做任何分表分库的操作:
在server.xml
中添加一个访问mycat逻辑库schemas的用户:
<user name="cat">
<property name="password">123456</property>
<property name="schemas">mycatDB</property>
</user>
该配置信息为:添加一个name为cat的用户,密码为123456,对应的逻辑库为mycatDB
接着在schema.xml
配置:
scheme标签中就是一个逻辑库的配置。
scheme:
name:逻辑库的名字
sqlMaxLimit=“100” :查询时不会进行分表,会添加limit分页
dataNode:指定数据节点,与下面的dataNode标签的name对应。
dataNode
一个数据节点。
name:数据节点的名字(唯一)
dataHost:与下面的dataHost标签的name对应
database:物理库的名字
dataHost
一个数据host
在里面的writeHost,url和user、password为我们实际物理库的连接信息。
table:
table标签共有九个属性:
<schema name="mycatDB" checkSQLschema="true" sqlMaxLimit="100" dataNode="localdn">
<table name="t_order" dataNode="DN1" primaryKey="orderId" >
table>
schema>
<dataNode name="DN1" dataHost="H1" database="consult" />
<dataHost name="H1" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<heartbeat>select user()heartbeat>
<writeHost host="hostM1" url="jdbc:mysql://localhost:3306?useSSL=false" user="root"
password="123456">
writeHost>
dataHost>
配置完成后我们启动mycat工程。
接着搭建一个Springboot工程,我们可以直接连接mycat的逻辑库:
spring.druid.jdbcUrl=jdbc:mysql://localhost:8066/mycatDB?useCompression=true
spring.druid.username=cat
spring.druid.password=123456
spring.druid.driver-class-name=com.mysql.jdbc.Driver
mycat端口号默认为8066,mycatDB就是我们配置的逻辑库的名字,下面的用户名和密码就是逻辑库配置的用户名和密码。
接着写一个单元测试:
@Test
public void test1() {
Area area = new Area ();
area.setAreaCode("wml");
area.setAreaName("wml");
area.setState(1);
commonMapper.addArea(area);
}
向consult库中的一个表中插入一条数据。
插入成功。
scheme.xml
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="mycatDB" checkSQLschema="true" dataNode="localdn">
<table name="t_order" dataNode="localdn" autoIncrement="true" subTables="t_order$1-3" primaryKey="order_id" rule="mod-long">
table>
schema>
<dataNode name="localdn" dataHost="localhost1" database="consult" />
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<heartbeat>select user()heartbeat>
<connectionInitSql>connectionInitSql>
<writeHost host="hostM1" url="jdbc:mysql://localhost:3306" user="root"
password="123456">
writeHost>
dataHost>
mycat:schema>
table标签中有两个属性说明下:
<tableRule name="mod-long">
<rule>
<columns>order_idcolumns>
<algorithm>mod-longalgorithm>
rule>
tableRule>
columns指定分片操作的列。
algorithm指定分片规则,与rule.xml
中的function
标签的name
属性一致。
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<property name="count">3property>
function>
count中指定dataNode的个数
接着测试一下:
首先准备三个分表:
CREATE TABLE `t_order1` (
`order_id` int(11) NOT NULL,
`content` varchar(255) DEFAULT NULL,
PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--另外两个t_order2和t_order3一样的,换一个表名即可。
测试插入:
public void test() {
for (int i = 0; i < 1000; i++) {
Order order = new Order();
order.setOrderId(i);
order.setContent("wml" + i);
orderMapper.addOrder(order);
}
}
@Insert("insert into t_order(order_id,content) values(#{order_id},#{order_content})")
int addOrder(Order order);
可以看到成功插入到三张表中。
再测试查询:
@Test
public void test() {
Order order= orderMapper.querOrderById(877);
System.out.println(order);
}
我们根据id查询,id为877,按照规则,877%3=1,mycat应该从t_order2进行查询。
启动后查看mycat日志:
2020-08-06 22:00:40,661 [DEBUG][$_NIOREACTOR-0-RW] SQLRouteCache add cache ,key:mycatDBselect * from t_order where order_id=877 value:select * from t_order where order_id=877, route={
1 -> localdn{SELECT *
FROM t_order2
WHERE order_id = 877}
} (io.mycat.cache.impl.EnchachePool:EnchachePool.java:60)
2020-08-06 22:00:40,661 [DEBUG][$_NIOREACTOR-0-RW] ServerConnection [id=36, schema=mycatDB, host=127.0.0.1, user=cat,txIsolation=3, autocommit=true, schema=mycatDB, executeSql=select * from t_order where order_id=877]select * from t_order where order_id=877, route={
1 -> localdn{SELECT *
FROM t_order2
WHERE order_id = 877}
} rrs (io.mycat.server.NonBlockingSession:NonBlockingSession.java:126)
可以看到,会根据id进行hash,直接从第二张表查询。在这之前,会先从缓存中进行查询,缓存没有再从数据库查询。
而如果直接select * from ,就会进行全表查询,发出三个查询语句分别查询三个库进行汇总。