Skip to content

MyCat

数据切分

简单来说,就是指通过某种特定的条件,将我们存放在同一个数据库中的数据分散存放到多个数据库(主机)上面,以达到分散单台设备负载的效果。

数据切分分为两种:垂直切分 和 水平切分

数据库垂直切分

image

垂直切分的优点

  • 数据库的拆分简单明了,拆分规则明确;
  • 应用程序模块清晰明确,整合容易;
  • 数据维护方便易行,容易定位;

垂直切分的缺点

  • 部分表关联无法在数据库级别完成,需要在程序中完成,存在跨库 join 的问题,对于这类的表,就需要去做平衡,是数据库让步业务,共用一个数据源,还是分成多个库,业务之间通过接口来做调用;在系统初期,数据量比较少,或者资源有限的情况下,会选择共用数据源,但是当数据发展到了一定的规模,负载很大的情况,就需要必须去做分割。
  • 对于访问极其频繁且数据量超大的表仍然存在性能瓶颈,不一定能满足要求;
  • 事务处理相对更为复杂;
  • 切分达到一定程度之后,扩展性会遇到限制;
  • 过多切分可能会带来系统过渡复杂而难以维护。

数据库水平切分

相对于垂直拆分,水平拆分不是将表做分类,而是按照某个字段的某种规则来分散到多个库之中,每个表中包含一部分数据。简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中的某些行切分到一个数据库,而另外的某些行又切分到其他的数据库中. image

水平切分的优点

  • 表关联基本能够在数据库端全部完成;
  • 不会存在某些超大型数据量和高负载的表遇到瓶颈的问题;
  • 应用程序端整体架构改动相对较少;
  • 事务处理相对简单;
  • 只要切分规则能够定义好,基本上较难遇到扩展性限制;

水平切分的缺点

  • 切分规则相对更为复杂,很难抽象出一个能够满足整个数据库的切分规则;
  • 后期数据的维护难度有所增加,人为手工定位数据更困难;
  • 应用系统各模块耦合度较高,可能会对后面数据的迁移拆分造成一定的困难。
  • 跨节点合并排序分页问题
  • 多数据源管理问题

几种典型的分片规则

  • 按照用户 ID 求模,将数据分散到不同的数据库,具有相同数据用户的数据都被分散到一个库中。
  • 按照日期,将不同月甚至日的数据分散到不同的库中。
  • 按照某个特定的字段求摸,或者根据特定范围段分散到不同的库中。

由于数据切分后数据 Join 的难度在此也分享一下数据切分的经验

  • 第一原则:能不切分尽量不要切分。
  • 第二原则:如果要切分一定要选择合适的切分规则,提前规划好。
  • 第三原则:数据切分尽量通过数据冗余或表分组(Table Group)来降低跨库 Join 的可能。
  • 第四原则:由于数据库中间件对数据 Join 实现的优劣难以把握,而且实现高性能难度极大,业务读取尽量少使用多表 Join。

综上描述,数据切分带来的核心问题主要有三个:

  • 引入分布式事务的问题;
  • 跨节点 Join 的问题;
  • 跨节点合并排序分页问题;

Mycat 功能介绍

  • 是一个数据库代理
  • MySQL、SQL Server、Oracle、DB2、PostgreSQL 等主流数据库,也支持 MongoDB 这种新型 NoSQL 方式的存储
  • Mycat 并不存储数据,只做数据路由

Mycat 的原理中最重要的一个动词是“拦截”,它拦截了用户发送过来的 SQL 语句,首先对 SQL 语句做了一些特定的分析:如分片分析、路由分析、读写分离分析、缓存分析等,然后将此 SQL 发往后端的真实数据库,并将返回的结果做适当的处理,最终再返回给用户。 image

Mycat 中的概念

  • 逻辑库(schema):存在在 mycat 里面的虚拟库
  • 逻辑表(table):存在在 mycat 里面的虚拟表
  • 分片表:分片表,是指那些原有的很大数据的表,需要切分到多个数据库的表,这样,每个分片都有一部分数据,所有分片构成了完整的数据
  • 非分片表:不需要进行数据切分的表
  • ER 表:子表的记录与所关联的父表记录存放在同一个数据分片上,即子表依赖于父表,通过表分组(Table Group)保证数据 Join 不会跨库操作。 表分组(Table Group)是解决跨分片数据 join 的一种很好的思路,也是数据切分规划的重要一条规则
  • 全局表:例如字典表,每一个数据分片节点上有保存了一份字典表数据。数据冗余是解决跨分片数据 join 的一种很好的思路,也是数据切分规划的另外一条重要规则
  • 分片节点(dataNode):数据切分后,一个大表被分到不同的分片数据库上面,每个表分片所在的数据库就是分片节点
  • 节点主机(dataHost):数据切分后,每个分片节点(dataNode)不一定都会独占一台机器,同一机器上面可以有多个分片数据库,这样一个或多个分片节点(dataNode)所在的机器就是节点主机(dataHost),为了规避单节点主机并发数限制,尽量将读写压力高的分片节点(dataNode)均衡的放在不同的节点主机(dataHost)
  • 分片规则(rule):前面讲了数据切分,一个大表被分成若干个分片表,就需要一定的规则,这样按照某种业务规则把数据分到某个分片的规则就是分片规则,数据切分选择合适的分片规则非常重要,将极大的避免后续数据处理的难度。
  • 全局序列号(sequence):数据切分后,原有的关系数据库中的主键约束在分布式条件下将无法使用,因此需要引入外部机制保证数据唯一性标识,这种保证全局性的数据唯一标识的机制就是全局序列号 (sequence)

Mycat 源码部署

源码下载:https://codeload.github.com/MyCATApache/Mycat-Server/zip/Mycat-server-1675-release

配置启动参数: -DMYCAT_HOME=E:\idea\Mycat-Server-Mycat-server-1675-release\src\main -XX:MaxDirectMemorySize=512M image

一个简单的非分片演示

xml
<schema name="enjoyDB" checkSQLschema="true" sqlMaxLimit="100" dataNode="localdn">
    <table name="zg_goods" dataNode="localdn" primaryKey="goodCode"> </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>
    <!-- can have multi write hosts -->
    <writeHost host="hostM1" url="jdbc:mysql://localhost:3306" user="root" password="123456"> </writeHost>
</dataHost>

Mycat 的分表

xml
<schema name="enjoyDB" checkSQLschema="true" dataNode="localdn">
    <table name="t_order" dataNode="localdn" 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>
    <!-- can have multi write hosts -->
    <writeHost host="hostM1" url="jdbc:mysql://localhost:3306" user="root" password="123456"> </writeHost>
</dataHost>

Mycat 全局序列号

文件方式获取全局序列号

mycat 提供了全局序列号,配置在 sequence_conf.properties 文件中。在 sequence_conf.properties 文件中做如下配置:

GLOBAL_SEQ.HISIDS=
GLOBAL_SEQ.MINID=1001
GLOBAL_SEQ.MAXID=1000000000
GLOBAL_SEQ.CURID=1000

其中 HISIDS 表示使用过的历史分段(一般无特殊需要可不配置),MINID 表示最小 ID 值, MAXID 表示最大 ID 值,CURID 表示当前 ID 值。

server.xml 中配置:

xml
<system><property name="sequnceHandlerType">0</property></system>

注:sequnceHandlerType 需要配置为 0,表示使用本地文件方式。

使用示例: insert into table1(id,name) values(next value for MYCATSEQ_GLOBAL,‘test’);

缺点:当 MyCAT 重新发布后,配置文件中的 sequence 会恢复到初始值。 优点:本地加载,读取速度较快。

数据库方式获取全局序列号

在数据库里面执行的 SQL 语句: 创建序列表:

  • name sequence 名称
  • current_value 当前 value
  • increment 增长步长! 可理解为 mycat 在数据库中一次读取多少个 sequence. 当这些用完后, 下次再从数据库中读取.
sql
CREATE TABLE MYCAT_SEQUENCE (
    name VARCHAR(50) NOT NULL,
    current_value INT NOT NULL,
    increment INT NOT NULL DEFAULT 100,
    PRIMARY KEY(name)) ENGINE=InnoDB;

插入针对 order 表的序列

sql
INSERT INTO MYCAT_SEQUENCE(name,current_value,increment) VALUES ('ORDER', 100000, 100);

创建相关 function, 获取当前 sequence 的值 (返回当前值,增量)

sql
DELIMITER $$
CREATE FUNCTION mycat_seq_currval(seq_name VARCHAR(50))RETURNS VARCHAR(64) CHARSET 'utf8'
BEGIN
    DECLARE retval VARCHAR(64);
    SET retval='-999999999,NULL';
    SELECT CONCAT(CAST(current_value AS CHAR),',',CAST(increment AS CHAR)) INTO retval FROM MYCAT_SEQUENCE WHERE NAME = seq_name;
    RETURN retval;
END$$
DELIMITER;

设置 sequence 值

sql
DROP FUNCTION IF EXISTS mycat_seq_setval;
DELIMITER $$
CREATE FUNCTION mycat_seq_setval(seq_name VARCHAR(50),VALUE INTEGER) RETURNS VARCHAR(64) CHARSET 'utf8'
BEGIN
    UPDATE MYCAT_SEQUENCE SET current_value = VALUE WHERE NAME = seq_name;
    RETURN mycat_seq_currval(seq_name);
END$$
DELIMITER;

取下一个 sequence 的值

sql
DROP FUNCTION IF EXISTS mycat_seq_nextval;
DELIMITER $$
CREATE FUNCTION mycat_seq_nextval(seq_name VARCHAR(50)) RETURNS VARCHAR(64) CHARSET 'utf8'
BEGIN
    UPDATE MYCAT_SEQUENCE SET current_value = current_value + increment WHERE NAME = seq_name;
    RETURN mycat_seq_currval(seq_name);
END$$
DELIMITER;
  • sequence_db_conf.properties 相关配置,指定 sequence 相关配置在哪个节点上
  • T_ORDER=localdn
  • server.xml 配置
  • 测试
    • INSERT INTO MYCAT_SEQUENCE(name,current_value,increment) VALUES (‘T_ORDER‘, 100000, 100);
    • insert into t_order(order_id,order_content) values ('next value for MYCATSEQ_ORDER','96');

基于 zookeeper 生成全局序列

myid.properties配置
    loadZk=true
    zkURL=192.168.67.139:2181
    clusterId=01
    myid=mycat_fz_01
    clusterSize=1
    clusterNodes=mycat_fz_01

sequence_distributed_conf.properties配置
    #从zk中获取实例id
    INSTANCEID=ZK
    #集群id
    CLUSTERID=01

基于时间戳生成全局序列

<property name="sequenceHandlerType">2</property>

sequence_time_conf.properties配置
    # 0-31为整数,每一个mycat节点的这两个配置值都不一样
    WORKID=01
    DATAACENTERID=01

Mycat 多实例分库

xml
<schema name="enjoyDB" checkSQLschema="true" dataNode="localdn">
    <table name="t_order" dataNode="localdn" 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>
    <!-- can have multi write hosts -->
    <writeHost host="hostM1" url="jdbc:mysql://localhost:3306" user="root" password="123456"> </writeHost>
</dataHost>

Mycat 分片规则

分片枚举

应用场景: 通过在配置文件中配置可能的枚举 id,自己配置分片,本规则适用于特定的场景,比如有些业务需要按照省份或区县来做保存,不同的省份存到不同的数据节点。

固定分片 hash 算法

应用场景: 本条规则类似于十进制的求模运算,区别在于是二进制的操作,是取 id 的二进制低 10 位,即 id 二进制&1111111111。 此算法的优点在于如果按照 10 进制取模运算,在连续插入 1-10 时候 1-10 会被分到 1-10 个分片,增大了插入的事务 控制难度,而此算法根据二进制则可能会分到连续的分片,减少插入事务事务控制难度

范围约定

应用场景: 此分片适用于,提前规划好分片字段某个范围属于哪个分片

按日期(天)分片

应用场景: 此规则为按天分片

一致性 hash

应用场景: 数据均匀分布,不出现数据倾斜的情况。