1. Mycat概述
Mycat 是什么?从定义和分类来看,它是一个开源的分布式数据库系统,是一个实现了 MySQL 协议的的Server,前端用户可以把它看作是一个数据库代理,用 MySQL 客户端工具和命令行访问,而其后端可以用MySQL 原生(Native)协议与多个 MySQL 服务器通信,也可以用 JDBC 协议与大多数主流数据库服务器通信,其核心功能是分表分库,即将一个大表水平分割为 N 个小表,存储在后端 MySQL 服务器里或者其他数据库里。
1.1 Mycat原理
Mycat 的原理中最重要的一个动词是“拦截”,它拦截了用户发送过来的 SQL 语句,首先对 SQL 语句做了一些特定的分析:如分片分析、路由分析、读写分离分析、缓存分析等,然后将此 SQL 发往后端的真实数据库,并将返回的结果做适当的处理,最终再返回给用户。
1.2 Mycat应用场景
Mycat 发展到现在,适用的场景已经很丰富,而且不断有新用户给出新的创新性的方案,以下是几个典型的应用场景:
单纯的读写分离,此时配置最为简单,支持读写分离,主从切换;
分表分库,对于超过 1000 万的表进行分片,最大支持 1000 亿的单表分片;
多租户应用,每个应用一个库,但应用程序只连接 Mycat,从而不改造程序本身,实现多租户化;
报表系统,借助于 Mycat 的分表能力,处理大规模报表的统计;
替代 Hbase,分析大数据;
作为海量数据实时查询的一种简单有效方案,比如 100 亿条频繁查询的记录需要在 3 秒内查询出来结果,除了基于主键的查询,还可能存在范围查询或其他属性查询,此时 Mycat 可能是最简单有效的选择。
1.3 Mycat中的概念
1.3.1 逻辑库(schema)
通常对实际应用来说,并不需要知道中间件的存在,业务开发人员只需要知道数据库的概念,所以数据库中间件可以被看做是一个或多个数据库集群构成的逻辑库。
1.3.2 逻辑表(table)
既然有逻辑库,那么就会有逻辑表,分布式数据库中,对应用来说,读写数据的表就是逻辑表。逻辑表,可以是数据切分后,分布在一个或多个分片库中,也可以不做数据切分,不分片,只有一个表构成。
- 1.分片表
分片表,是指那些原有的很大数据的表,需要切分到多个数据库的表,这样,每个分片都有一部分数据,所有分片构成了完整的数据。
- 2.非分片表
一个数据库中并不是所有的表都很大,某些表是可以不用进行切分的,非分片是相对分片表来说的,就是那些不需要进行数据切分的表。
- 3.ER表
关系型数据库是基于实体关系模型(Entity-Relationship Model)之上,通过其描述了真实世界中事物与关系,Mycat 中的 ER 表即是来源于此。根据这一思路,提出了基于 E-R 关系的数据分片策略,子表的记录与所关联的父表记录存放在同一个数据分片上,即子表依赖于父表,通过表分组(Table Group)保证数据 Join 不会跨库操作。
表分组(Table Group)是解决跨分片数据 join 的一种很好的思路,也是数据切分规划的重要一条规则。
- 4.全局表
一个真实的业务系统中,往往存在大量的类似字典表的表,这些表基本上很少变动,字典表具有以下几个特性:变动不频繁、数据量总体变化不大、数据规模不大,很少有超过数十万条记录。对于这类的表,在分片的情况下,当业务表因为规模而进行分片以后,业务表与这些附属的字典表之间的关联,就成了比较棘手的问题,所以 Mycat 中通过数据冗余来解决这类表的 join,即所有的分片都有一份数据的拷贝,所有将字典表或者符合字典表特性的一些表定义为全局表。
数据冗余是解决跨分片数据 join 的一种很好的思路,也是数据切分规划的另外一条重要规则。
1.3.3 分片节点(dataNode)
数据切分后,一个大表被分到不同的分片数据库上面,每个表分片所在的数据库就是分片节点(dataNode)。
1.3.4 节点主机(dataHost)
数据切分后,每个分片节点(dataNode)不一定都会独占一台机器,同一机器上面可以有多个分片数据库,这样一个或多个分片节点(dataNode)所在的机器就是节点主机(dataHost),为了规避单节点主机并发数限制,尽量将读写压力高的分片节点(dataNode)均衡的放在不同的节点主机(dataHost)。
1.3.5 分片规则(rule)
前面讲了数据切分,一个大表被分成若干个分片表,就需要一定的规则,这样按照某种业务规则把数据分到某个分片的规则就是分片规则,数据切分选择合适的分片规则非常重要,将极大的避免后续数据处理的难度。
1.3.6 全局序列号(sequence)
数据切分后,原有的关系数据库中的主键约束在分布式条件下将无法使用,因此需要引入外部机制保证数据唯一性标识,这种保证全局性的数据唯一标识的机制就是全局序列号(sequence)。
2. Mycat配置
2.1 schema.xml
Schema.xml 作为 MyCat 中重要的配置文件之一,管理着 MyCat 的逻辑库、表、分片规则、DataNode 以及 DataSource。弄懂这些配置,是正确使用 MyCat 的前提。这里就一层层对该文件进行解析。
1 | <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn2"> |
schema:该标签用于定义 MyCat 实例中的逻辑库,MyCat 可以有多个逻辑库,每个逻辑库都有自己的相关配置。可以使用 schema 标签来划分这些不同的逻辑库。如果不配置 schema 标签,所有的表配置,会属于同一个默认的逻辑库。
table:该标签定义了 MyCat 中的逻辑表,所有需要拆分的表都需要在这个标签中定义。
childTable:该标签用于定义 E-R 分片的子表。通过标签上的属性与父表进行关联。
dataNode:该标签定义了 MyCat 中的数据节点,也就是我们通常说所的数据分片。一个 dataNode 标签就是一个独立的数据分片。
dataHost:该标签在 mycat 逻辑库中也是作为最底层的标签存在,直接定义了具体的数据库实例、读写分离配置和心跳语句。
heartbeat:该标签内指明用于和后端数据库进行心跳检查的语句。例如,MYSQL 可以使用 select user(),Oracle 可以使用 select 1 from dual 等。
2.2 server.xml
server.xml 几乎保存了所有 mycat 需要的系统配置信息。其在代码内直接的映射类为 SystemConfig 类。
1 | <user name="test"> |
user:这个标签主要用于定义登录 mycat 的用户和权限。
system:该标签内嵌套的所有 property 标签都与系统配置有关,请注意,下面我会省去标签 property 直接使用这个标签的 name 属性内的值来介绍这个属性的作用。
2.3 rule.xml
rule.xml 里面就定义了我们对表进行拆分所涉及到的规则定义。我们可以灵活的对表使用不同的分片算法,或者对表使用相同的算法但具体的参数不同。这个文件里面主要有 tableRule 和 function 这两个标签。在具体使用过程中可以按照需求添加 tableRule 和 function。
1 | <tableRule name="rule1"> |
tableRule:该标签定义表规则。
function:该标签用于路由算法的配置
3. Mycat分库分表
当你找到某个合适的业务字段作为分片字段以后,不必纠结于“牺牲了按主键查询记录的性能”,因为在这种情况下,MyCAT 提供了“主键到分片”的内存缓存机制,热点数据按照主键查询,丝毫不损失性能。
3.1 Mycat全局表
如果你的业务中有些数据类似于数据字典,比如配置文件的配置,常用业务的配置或者数据量不大很少变动的表,这些表往往不是特别大,而且大部分的业务场景都会用到,那么这种表适合于 Mycat 全局表,无须对数据进行切分,只要在所有的分片上保存一份数据即可。
1 | <table name="t_area" primaryKey="id" type="global" dataNode="dn1,dn2" /> |
3.2 ER分片表
有一类业务,例如订单(order)跟订单明细(order_detail),明细表会依赖于订单,也就是说会存在表的主从关系,这类似业务的切分可以抽象出合适的切分规则,比如根据用户 ID 切分,其他相关的表都依赖于用户 ID,再或者根据订单 ID 切分,总之部分业务总会可以抽象出父子关系的表。这类表适用于 ER 分片表。
子表的记录与所关联的父表记录存放在同一个数据分片上,避免数据 Join 跨库操作
1 | <table name="order" dataNode="dn$1-32" rule="mod-long"> |
3.3 多对多关联
有一类业务场景是 “主表 A+关系表+主表 B”,举例来说就是商户会员+订单+商户,对应这类业务,如何切分?目前总的原则是需要从业务角度
来看,关系表更偏向哪个表,即“A 的关系”还是“B 的关系”,来决定关系表跟从那个方向存储,未来 Mycat版本中将考虑将中间表进行双向复制,以实现从 A-关系表 以及 B-关系表的双向关联查。
3.4 常用分片规则
| 分类 | 说明 |
|---|---|
| io.mycat.route.function.PartitionByFileMap | 分片枚举 |
| io.mycat.route.function.PartitionByLong | 固定分片hash算法 |
| io.mycat.route.function.AutoPartitionByLong | 范围约定 |
| io.mycat.route.function.PartitionByMod | 取模 |
| io.mycat.route.function.PartitionByDate | 按日期(天)分片 |
| io.mycat.route.function.PartitionByPattern | 取模范围约束 |
| io.mycat.route.function.PartitionByPrefixPattern | 截取数字做hash求模范围约束 |
| io.mycat.route.function.PartitionDirectBySubString | 应用指定 |
| io.mycat.route.function.PartitionByString | 截取数字hash解析 |
| io.mycat.route.function.PartitionByMurmurHash | 一致性 hash |
| io.mycat.route.function.LatestMonthPartion | 按单月小时拆分 |
| io.mycat.route.function.PartitionByRangeMod | 范围求模分片 |
| io.mycat.route.function.PartitionByRangeDateHash | 日期范围hash分片 |
| io.mycat.route.function.PartitionByHotDate | 冷热数据分片 |
| io.mycat.route.function.PartitionByMonth | 自然月分片 |
| io.mycat.route.function.PartitionByCRC32PreSlot | 有状态分片算法 |
3.5 数据扩容方案
3.5.1 离线扩容缩容
1、 复制 schema.xml、 rule.xml 并重命名为 newSchema.xml、 newRule.xml 放于 conf 目录下。
2、 修改 newSchema.xml 和 newRule.xml 配置文件为扩容缩容后的 mycat 配置参数(表的节点数、数据源、路由规则)。
3、 修改 conf 目录下的 migrateTables.properties 配置文件, 告诉工具哪些表需要进行扩容或缩容,没有出现在此配置文件的 schema 表不会进行数据迁移。
4、 修改 bin 目录下的 dataMigrate.sh 脚本文件。
5、 停止 mycat 服务(如果可以确保扩容缩容过程中不会有写操作, 也可以不停止 mycat 服务)。
6、 通过 crt 等工具进入 mycat 根目录, 执行 bin/ dataMigrate.sh 脚本, 开始扩容/缩容过程。
7、 扩容缩容成功后, 将 newSchema.xml 和 newRule.xml 重命名为 schema.xml 和 rule.xml 并替换掉原文件, 重启 mycat 服务, 整个扩容缩容过程完成。
4. Mycat读写分离
对于MySQL来说,标准的读写分离是主从模式,一个写节点Master后面跟着多个读节点,读节点的数量取决于系统的压力。
4.1 MySQL主从模式种类

MySQL 主从复制的原理如下:
第一步是在主库上记录二进制日志(稍后介绍如何设置)。在每次准备提交事务完成数据更新前,主库将数据更新的事件记录到二进制日志中。MySQL 会按事务提交的顺序 而非每条语句的执行顺序来记录二进制日志。在记录二进制日志后,主库会告诉存储引擎可以提交事务了。下一步,备库将主库的二进制日志复制到其本地的中继日志中。首先,备库会启动一个工作线程,称为I/O线程,I/O线程跟主库建立一个普通的客户端连接,然后在主库上启 动一个特殊的二进制转储(binhg dump、线程(该线程没有对应的 SQL 命令),这个二进制转储线程会读取主库上二进制日志中的事件。它不会对事件进行轮询。如果该线程追赶上了主库,它将进入睡眠状态,直到主库发送信号量通知其有新的事件产生时才会 被唤醒,备库 I/O 线程会将接收到的事件记录到中继日志中。
备库的SQL线程执行最后一步,该线程从中继日志中读取事件并在备库执行,从而实现备库数据的更新。当SQL线程追赶上I/O 线程时,中继日志通常已经在系统缓存中,所以中继日志的开销很低。SQL线程执行的事件也可以通过配置选项来决定是否写入其自己的二进制日志中。这种复制架构实现了获取事件和重放事件的解耦,允许这两个过程异步进行。也就是说 I/o 线程能够独立于 SQL 线程之外工作。但这种架构也限制了复制的过程,其中最重要 的一点是在主库上并发运行的査询在备库只能串行化执行,因
为只有一个 SQL 线程来重 放中继日志中的事件。
进行同步复制,这将大大改善MySQL主从同步的数据延迟问题,配合Mycat分片,可以更好的将一个超级大表的数据同步的时延降低到最低。即使是并发复制机制、仍然无法避免主从数据库的数据瞬间不同步的问题,因此又有了一种增强的方案,即galera for mysql、percona-cluster或者mariadb cluster等集群机制,他们是一种多主同步复制的模式,可以在任意节点上进行读写、自动控制成员,自动删除故障节点、自动加入节点、真正给予行级别的并发复制等强大能力。
MySQL主从同步的监控
MySQL有主从同步的状态信息,可以通过命令以下获取:
show slave status:获知当前是否主从同步正常工作。其中Seconds_Behind_Master字段从字面理解,它表示当前MySQL主从数据的同步延迟,单位是秒,但这个指标从DBA的角度并不能简单的理解为延迟多少秒,但对于应用来说,简单的认为是主从同步的时间差就可以了,另外,当主从同步停止以后,重新启动同步,这个数值可能会是几万秒,取决于主从同步停止的时间长短,我们可以认为数据此时有很多天没有同步了,而这个数值越接近零,则说明主从同步延迟最小。
我们可以采集这个指标并汇聚曲线图,来分析我们的数据库的同步延迟曲线,然后根据此曲线,给出一个合理的阀值,主从同步的时延小于阀值时,我们认为从库是同步的,此时可以安全的从从库读取数据。Mycat 未来将支持这种优化,让应用更加可靠的读取到预期的从库数据。
4.2 MySQL高可用方案
1.主从复制+读写分离

对于数据实时性要求不是特别严格的应用,只需要通过廉价的 pc server 来扩展 Slave 的数量,将读压力分散到多台 Slave 的机器上面,即可通过分散单台数据库服务器的读压力来解决数据库端的读性能瓶颈,毕竟在大多数数据库应用系统中的读压力还是要比写压力大很多。这在很大程度上解决了目前很多中小型网站的数据库压力瓶颈问题,甚至有些大型网站也在使用类似方案解决数据库瓶颈。
2.MySQL Cluster

MySQL Cluster 由一组计算机构成,每台计算机上均运行着多种进程,包括 MySQL 服务器,NDB Cluster的数据节点,管理服务器,以及(可能)专门的数据访问程序。NDB”是一种“内存中”的存储引擎,它具有可用性高和数据一致性好的特点。MySQL Cluster 要实现完全冗余和容错,至少需要 4 台物理主机,其中两个为管理节点。MySQL Cluster使用不那么广泛,除了自身构架因素、适用的业务有限之外,另一个重要的原因是其安装配置管理相对复杂繁琐,总共有几十个操作步骤,需要 DBA 花费几个小时才能搭建或完成。重启 MySQLCluster 数据库的管理操作之前需要执行 46 个手动命令,需要耗费 DBA 2.5 小时的时间,而依靠 MySQLCluster Manager 只需一个命令即可完成,但 MySQL Cluster Manager 仅作为商用 MySQL Cluster 运营商级版本 (CGE) 数据库的一部分提供,需要购买。其官方的说明,若应用中的 SQL 操作为主键数据库访问,包含一些JOIN 操作而非对整个表执行常规扫描和 JOIN 而返回数万行数据,则适合 Cluster,否则不合适,从这一条限制来看,表明大多数业务场景并不合适 MySQL Cluster,业内有资深人士也凭评价:NDB 不适合大多数业务场景,而且有安全问题。
3.HeartBeat+双主复制

heartbeat 是 Linux-HA 工程的一个组件,heartbeat 最核心的包括两个部分:心跳监测和资源接管。在指定的时间内未收到对方发送的报文,那么就认为对方失效,这时需启动资源接管模块来接管运 行在对方主机上的资源或者服务。
4.HeartBeat+DRBD+MySQL

DRBD 是通过网络来实现块设备的数据镜像同步的一款开源 Cluster 软件,它自动完成网络中两个不同服务器上的磁盘同步,相对于 binlog 日志同步,它是更底层的磁盘同步,理论上 DRDB 适合很多文件型系统的高可用。
5.Lvs+Keepalived+双主复制

Lvs 是一个虚拟的服务器集群系统,可以实现 LINUX 平台下的简单负载均衡。keepalived 是一个类似于layer3, 4 & 5 交换机制的软件,主要用于主机与备机的故障转移,这是一种适用面很广的负载均衡和高可用方
案,最常用于 Web 系统。
6.Galera Cluster

这种 gluster 模式可以说是全新的一种高可用方案,前面也提到其优点,它的缺点不多,不支持 XA,不支持Lock Table,只能用 InnoDB 引擎。
4.3 Mycat读写分离支持
1 | <dataHost name="localhost1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native"> |
或者
1 | <dataHost name="localhost1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native"> |
以上两种取模第一种当写挂了读不可用,第二种可以继续使用,事务内部的一切操作都会走写节点,所以读操作不要加事务,如果读延时较大,使用根据主从延时的读写分离,或者强制走写节点。
当你是1主3从的模式的时候,可以把第一个从节点配置为writeHost 2,第2个和第三个从节点则配置为writeHost 1的readHost:
1 | <dataHost name="localhost1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native"> |
根据主从延时切换:
1.4开始支持MySQL主从复制状态绑定的读写分离机制,让读更加安全可靠,配置如下:
1 | <dataHost name="localhost1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="2" slaveThreshold="100"> |
MyCAT心跳检查语句配置为 show slave status,dataHost 上定义两个新属性: switchType=”2” 与slaveThreshold=”100”,此时意味着开启 MySQL 主从复制状态绑定的读写分离与切换机制,Mycat心跳机制通过检测 show slave status中的”Seconds_Behind_Master”, “Slave_IO_Running”,”Slave_SQL_Running” 三个字段来确定当前主从同步的状态以及Seconds_Behind_Master主从复制时延,当Seconds_Behind_Master > slaveThreshold时,读写分离筛选器会过滤掉此Slave机器,防止读到很久之前的旧数据,而当主节点宕机后,切换逻辑会检查Slave上的 Seconds_Behind_Master 是否为0,为0时则表示主从同步,可以安全切换,否则不会切换。
1.4.1开始支持MySQL 集群模式,让读更加安全可靠,配置如下:
1 | <dataHost name="localhost1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="3"> |
MyCAT 心跳检查语句配置为 show status like ‘wsrep%’,dataHost 上定义两个新属性: switchType=”3”此时意味着开启MySQL集群复制状态状态绑定的读写分离与切换机制,Mycat心跳机制通过检测集群复制时延时,如果延时过大或者集群出现节点问题不会负载改节点。
switchType字典值:
-1:表示不自动切换
1:默认值,自动切换
2:基于MySQL主从同步的状态决定是否切换,心跳语句为show slave status
3:基于MySQL galary cluster的切换机制(适合集群)(1.4.1),心跳语句为 show status like ‘wsrep%’
4.4 Mycat高可用方案
Mycat 作为一个代理层中间件,Mycat 系统的高可用涉及到 Mycat 本身的高可用以及后端 MySQL 的高可用,前面章节所讲的 MySQL 高可用方案都可以在此用来确保 Mycat 所连接的后端 MySQL 服务的高可用性。在
大多数情况下,建议采用标准的 MySQL 主从复制高可用性配置并交付给 Mycat 来完成后端 MySQL 节点的主从自动切换。

5. Mycat系统架构


6. 参考资料
Mycat权威指南:http://www.mycat.io/document/mycat-definitive-guide.pdf
Mycal社区:http://www.mycat.io/