随着传统的数据库技术日趋成熟、计算机网络技术的飞速发展和应用范围的扩充,数据库应用已经普遍建立于计算机网络之上。这时集中式数据库系统表现出它的不足:
在这种形势下,集中式数据库将向分布式数据库发展。
MyCAT的诞生,要从其前身Amoeba和Cobar说起。Amoeba(变形虫)项目,该开源框架于2008年开始发布一款 Amoeba for Mysql软件。这个软件致力于MySQL的分布式数据库前端代理层,它主要在应用层访问MySQL的时候充当SQL路由功能,专注于分布式数据库代理层(Database Proxy)开发。座落与 Client、DB Server(s)之间,对客户端透明。具有负载均衡、高可用性、SQL过滤、读写分离、可路由相关的到目标数据库、可并发请求多台数据库合并结果。 通过Amoeba你能够完成多数据源的高可用、负载均衡、数据切片的功能,目前Amoeba已在很多企业的生产线上面使用。
阿里巴巴于2012年6月19日,正式对外开源的数据库中间件Cobar,前身是早已经开源的Amoeba,不过其作者陈思儒离职去盛大之后,阿里巴巴内部考虑到Amoeba的稳定性、性能和功能支持,以及其他因素,重新设立了一个项目组并且更换名称为Cobar。Cobar是由Alibaba开源的MySQL分布式处理中间件,它可以在分布式的环境下看上去像传统数据库一样提供海量数据服务。
Cobar自诞生之日起, 就受到广大程序员的追捧,但是自2013年后,几乎没有后续更新。在此情况下,MyCAT应运而生,它基于阿里开源的Cobar产品而研发,Cobar的稳定性、可靠性、优秀的架构和性能,以及众多成熟的使用案例使得MyCAT一开始就拥有一个很好的起点,站在巨人的肩膀上,MyCAT能看到更远。目前MyCAT的最新发布版本为1.2版本。
什么是MyCat?
简单的说,MyCAT就是:
MyCAT的目标是:低成本的将现有的单机数据库和应用平滑迁移到“云”端,解决数据存储和业务规模迅速增长情况下的数据瓶颈问题。
MyCat 的关键特性
MyCat 的优势
长期规划
在支持Mysql的基础上,后端增加更多的开源数据库和商业数据库的支持,包括原生支持PosteSQL、FireBird等开源数据库,以及通过JDBC等方式间接支持其他非开源的数据库如Oracle、DB2、SQL Server等实现更为智能的自我调节特性,如自动统计分析SQL,自动创建和调整索引,根据数据表的读写频率,自动优化缓存和备份策略等实现更全面的监控管理功能与HDFS集成,提供SQL命令,将数据库装入HDFS中并能够快速分析集成优秀的开源报表工具,使之具备一定的数据分析的能力。
总体架构
MyCAT的架构如下图所示:
MyCAT使用MySQL的通讯协议模拟成一个MySQL服务器,并建立了完整的Schema(数据库)、Table (数据表)、User(用户)的逻辑模型,并将这套逻辑模型映射到后端的存储节点DataNode(MySQL Instance)上的真实物理库中,这样一来,所有能使用MySQL的客户端以及编程语言都能将MyCAT当成是MySQLServer来使用,不必开发新的客户端协议。
当MyCAT收到一个客户端发送的SQL请求时,会先对SQL进行语法分析和检查,分析的结果用于SQL路由,SQL路由策略支持传统的基于表格的分片字段方式进行分片,也支持独有的基于数据库E-R关系的分片策略,对于路由到多个数据节点(DataNode)的SQL,则会对收到的数据集进行“归并”然后输出到客户端。
SQL执行的过程,简单的说,就是把SQL通过网络协议发送给后端的真正的数据库上进行执行,对于MySQL Server来说,是通过MySQL网络协议发送报文,并解析返回的结果,若SQL不涉及到多个分片节点,则直接返回结果,写入客户端的SOCKET流中,这个过程是非阻塞模式(NIO)。
DataNode是MyCAT的逻辑数据节点,映射到后端的某一个物理数据库的一个Database,为了做到系统高可用,每个DataNode可以配置多个引用地址(DataSource),当主DataSource被检测为不可用时,系统会自动切换到下一个可用的DataSource上,这里的DataSource即可认为是Mysql的主从服务器的地址。
逻辑库
与任何一个传统的关系型数据库一样,MyCAT也提供了“数据库”的定义,并有用户授权的功能,下面是MyCAT逻辑库相关的一些概念:
MyCAT目前通过配置文件的方式来定义逻辑库和相关配置:
下图给出了MyCAT 一个可能的逻辑库到物理库(MySQL的完整映射关系),可以看出其强大的分片能力以及灵活的Mysql集群整合能力。
基本使用教程
1.垂直切分
1.1 垂直切分定义
数据的垂直切分,也可以称为纵向切分。将数据库想象成由很多个一大块一大块的“数据块”(表)组成,垂直地将这些“数据块”切开,然后把它们分散到多台数据库主机上面。这样的切分方法就是垂直(纵向)的数据切分。
一个架构设计较好的应用系统,其总体功能肯定是由很多个功能模块所组成的,而每一个功能模块所需要的数据对应到数据库中就是一个或多个表。而在架构设计中,各个功能模块相互之间的交互点越统一、越少,系统的耦合度就越低,系统各个模块的维护性及扩展性也就越好。这样的系统,实现数据的垂直切分也就越容易。
1.2 优缺点
垂直切分优点:
垂直切分缺点:
1.3 垂直切分实现
在如下的实例中,需要编辑MYCAT_HOME/conf/schema.xml文件,修改dataHost和schema对应的连接信息,weixin、yixin和photo垂直切分后的配置如下所示:
<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://org.opencloudb/"> <schema name="weixin" checkSQLschema="false" sqlMaxLimit="100" dataNode="weixin" /> <schema name="yixin" checkSQLschema="false" sqlMaxLimit="100" dataNode="yixin" /> <schema name="photo" checkSQLschema="false" sqlMaxLimit="100" dataNode="photo" /> <dataNode name="weixin" dataHost="testhost" database="weixin" /> <dataNode name="yixin" dataHost="testhost" database="yixin" /> <dataNode name="photo" dataHost="testhost" database="photo" /> <dataHost name="testhost" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native"> <heartbeat>select user()</heartbeat> <!-- can have multi write hosts --> <writeHost host="hostM1" url="localhost:3306" user="root" password="" /> <writeHost host="hostM2" url="10.18.96.133:3306" user="test" password="test" /> </dataHost> </mycat:schema>
注意:writeHost/readHost中的location,user,password的值需要根据实际的MySQL的连接信息进行修改。
查看conf/server.xml文件,该文件是Mycat服务器参数调整和用户授权的配置文件,默认的MyCat的数据库连接的用户名/密码为test/test,文件内容参考如下:
<?xml version="1.0" encoding="UTF-8"?> <!-- - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. --> <!DOCTYPE mycat:server SYSTEM "server.dtd"> <mycat:server xmlns:mycat="http://org.opencloudb/"> <system> <property name="sequnceHandlerType">0</property> </system> <user name="test"> <property name="password">test</property> <property name="schemas">weixin,yixin,photo</property> </user> </mycat:server>
上述文件中的schemas属性需要配置对应的schema(在schema.xml)中进行配置。重启MyCAT,使用MySQL客户端连接MyCAT,需要注意的是,默认数据端口为8066,管理端口为9066,在MySQL客户端连接MyCAT时,注意填写端口为8066,用户名/密码根据server.xml中的配置进行填写。连接后可查看后端连接的三个数据库。
2. 水平分库
2.1 水平切分定义
水平切分所指的是通过一系列的切分规则将数据水平分布到不同的DB或table中,在通过相应的DB路由 或者table路由规则找到需要查询的具体的DB或者table以进行Query操作,比如根据用户ID将用户表切分到多台数据库上。将某个访问极其频繁的表再按照某个字段的某种规则来分散到多个表之中,每个表中包含一部分数据。例如,所有数据都是和用户关联的,那么我们就可以根据用户来进行水平拆分,将不同用户的数据切分到不同的数据库中。
现在互联网非常火爆的web 2.0类型的网站,基本上大部分数据都能够通过会员用户信息关联上,可能很多核心表都非常适合通过会员ID来进行数据的水平切分。而像论坛社区讨论系统,就更容易切分了,非常容易按照论坛编号来进行数据的水平切分。切分之后基本上不会出现各个库之间的交互。
2.2 优缺点
水平切分的优点:
水平切分的缺点:
应用系统各模块耦合度较高,可能会对后面数据的迁移拆分造成一定的困难。
2.3 水平切分实现
在一般的应用系统中,用户表及其密切相关的关联表,可根据“用户表”(eg:t_user)中的“用户ID”(user_id)进行水平切分,并基于MyCAT的E-R关系分片策略将其密切相关的表(eg:t_user_class_rel)也分到对应的库中。
1)创建表结构
在user0~user2创建同样的表结构,t_user和t_user_class_rel的建表语句参考如下:
DROP TABLE IF EXISTS `t_user_ext`; CREATE TABLE `t_user_ext` ( `user_id` int(11) NOT NULL COMMENT '用户ID', `receive_address` varchar(256) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '收货地址', `create_time` datetime NOT NULL, `province_code` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL, PRIMARY KEY (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='用户信息表'; DROP TABLE IF EXISTS `t_user_class_rel`; CREATE TABLE `t_user_class_rel` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', `caller` varchar(16) CHARACTER SET utf8 NOT NULL COMMENT '调用方系统表示', `province_code` varchar(10) CHARACTER SET utf8 DEFAULT NULL COMMENT '省份编码', `user_id` int(11) NOT NULL COMMENT '用户ID', `class_id` int(11) NOT NULL COMMENT '班级ID', `role_type` int(11) DEFAULT NULL COMMENT '用户在该班的角色(1学生2家长3教师)', `create_time` datetime NOT NULL COMMENT '创建时间', `modify_time` datetime DEFAULT NULL COMMENT '修改时间', PRIMARY KEY (`id`), UNIQUE KEY `idx_rel_user_class_id` (`user_id`,`class_id`,`role_type`), KEY `idx_rel_user_id` (`user_id`) USING BTREE, KEY `idx_rel_class_id` (`class_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
2)配置schema.xml文件
首先配置schema.xml文件,添加user0~user3数据库的dataNode设置,并添加t_user和t_user_class_rel表的schema设置,修改后的schema.xml文件内容如下所示:
<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://org.opencloudb/"> <schema name="test_mycat" checkSQLschema="false" sqlMaxLimit="100"> <!-- auto sharding by id (long) --> <table name="t_user" dataNode="user0,user1,user2,user3" rule="rule1"> <childTable name="t_user_class_rel" primaryKey="id" joinKey="user_id" parentKey="user_id" /> </table> </schema> <schema name="weixin" checkSQLschema="false" sqlMaxLimit="100" dataNode="weixin" /> <schema name="yixin" checkSQLschema="false" sqlMaxLimit="100" dataNode="yixin" /> <schema name="photo" checkSQLschema="false" sqlMaxLimit="100" dataNode="photo" /> <dataNode name="weixin" dataHost="testhost" database="weixin" /> <dataNode name="yixin" dataHost="testhost" database="yixin" /> <dataNode name="photo" dataHost="testhost" database="photo" /> <dataNode name="user0" dataHost="testhost" database="user0" /> <dataNode name="user1" dataHost="testhost" database="user1" /> <dataNode name="user2" dataHost="testhost" database="user2" /> <dataNode name="user3" dataHost="testhost" database="user3" /> <dataHost name="testhost" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native"> <heartbeat>select user()</heartbeat> <!-- can have multi write hosts --> <writeHost host="hostM1" url="localhost:3306" user="root" password="" /> <writeHost host="hostM2" url="10.18.96.133:3306" user="test" password="test" /> </dataHost> </mycat:schema>
3)配置rule.xml文件
在schema.xml的文件内容中可看到t_user表指定的分片规则是rule1,需要在conf/rule.xml文件中设置rule1的规则为根据user_id进行分片,并按照类“org.opencloudb.route.function.PartitionByLong”的规则进行分片,即将user_id模除1024后每256内分到一个数据库中,即模除后0~255到user0数据库库,256~511到user1数据库,512~767到user2数据库,768~1023到user3数据库。该文件的参考内容如下所示:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mycat:rule SYSTEM "rule.dtd"> <mycat:rule xmlns:mycat="http://org.opencloudb/"> <tableRule name="rule1"> <rule> <columns>user_id</columns> <algorithm>func1</algorithm> </rule> </tableRule> <function name="func1" class="org.opencloudb.route.function.PartitionByLong"> <property name="partitionCount">4</property> <property name="partitionLength">256</property> </function> </mycat:rule>
4)配置server.xml文件
在server.xml文件中的schemas属性中添加test_mycat的schema。修改后的文件如下所示:
<!DOCTYPE mycat:server SYSTEM "server.dtd"> <mycat:server xmlns:mycat="http://org.opencloudb/"> <system> <property name="sequnceHandlerType">0</property> </system> <user name="test"> <property name="password">test</property> <property name="schemas">weixin,yixin,photo,test_mycat</property> </user> </mycat:server>
5)水平切分测试
重启MyCAT,使用MySQL客户端连接后,连接后可在test_mycat数据库下看到t_user和t_user_class_rel表,在MySQL客户端连接的MyCat的test_mycat数据库的t_user表运行如下插入语句,插入2000条数据:
INSERT INTO `t_user` VALUES ('1', '广州市越秀区广州大道中599号', '2014-07-17 10:53:15', 'GD'); INSERT INTO `t_user` VALUES ('2', '广州市越秀区广州大道中599号', '2014-07-17 10:53:17', 'GD'); INSERT INTO `t_user` VALUES ('3', '广州市越秀区广州大道中599号', '2014-07-17 10:53:17', 'GD'); INSERT INTO `t_user` VALUES ('4', '广州市越秀区广州大道中599号', '2014-07-17 10:53:17', 'GD'); INSERT INTO `t_user` VALUES ('5', '广州市越秀区广州大道中599号', '2014-07-17 10:53:17', 'GD'); …… INSERT INTO `t_user` VALUES (2000, '广州市越秀区广州大道中599号', '2014-07-17 10:54:37', 'GD');
而后在MyCAT的test_mycat数据库的t_user表运行select查看记录执行情况。进入localhost的user0~user3数据库,查看数据是否按照之前确定的rule1的规则写入不同的数据库。读者可在test_mycat数据库的t_user表执行update和delete等语句,并去分库查看执行结果,可得知MyCAT对MySQL客户端基本透明,对程序也几乎透明,在select语句运行时,MyCAT会自行去各个分库按照规则获取合并结果。接着测试按照ER关系策略分片的t_user_class_rel表是否按照user_id的分片策略,同样user_id的数据分布在同一个user库的t_user表和t_user_class_rel表。
在MyCAT的test_mycat数据库的t_user_class_rel表运行如下语句:
INSERT INTO `t_user_class_rel` VALUES ('257', 'eip', 'GD', '2', '35', '3', '2012-08-05 17:32:13', '2013-12-27 16:07:32'); INSERT INTO `t_user_class_rel` VALUES ('512', 'eip', 'GD', '1', '35', '3', '2012-08-05 17:32:13', '2013-12-27 16:07:32'); INSERT INTO `t_user_class_rel` VALUES ('1', 'eip', 'GD', '257', '35', '3', '2012-08-05 17:32:13', '2013-12-27 16:07:32'); INSERT INTO `t_user_class_rel` VALUES ('2', 'eip', 'GD', '513', '35', '3', '2012-08-05 17:32:13', '2013-12-27 16:07:32'); INSERT INTO `t_user_class_rel` VALUES ('3', 'eip', 'GD', '769', '35', '3', '2012-08-05 17:32:13', '2013-12-27 16:07:32');
而后在MyCAT的test_mycat数据库的t_user_class_rel表运行select查看记录执行情况。进入localhost的user0~user3数据库,查看数据是否按照之前确定的rule1的规则和ER分片策略写入不同的数据库。
3.读写分离
3.1 读写分离定义
为了确保数据库产品的稳定性,很多数据库拥有双机热备功能。也就是,第一台数据库服务器,是对外提供增删改查业务的生产服务器;第二台数据库服务器,仅仅接收来自第一台服务器的备份数据。一般来说,为了配置方便,以及稳定性,这两台数据库服务器,都用的是相同的配置。在实际运行中,第一台数据库服务器的压力,远远大于第二台数据库服务器。因此,很多人希望合理利用第二台数据库服务器的空闲资源。
从数据库的基本业务来看,数据库的操作无非就是增删改查这4个操作。但对于“增删改”这三个操作,如果是双机热备的环境中做,一台机器做了这三个操作的某一个之后,需要立即将这个操作,同步到另一台服务器上。出于这个原因,第二台备用的服务器,就只做了查询操作。进一步,为了降低第一台服务器的压力,干脆就把查询操作全部丢给第二台数据库服务器去做,第一台数据库服务器就只做增删改了。
3.2 优缺点
优点:合理利用从数据库服务器的空闲资源。
缺点:本来第二台数据库服务器,是用来做热备的,它就应该在一个压力非常小的环境下,保证运行的稳定性。而读写分离,却增加了它的压力,也就增加了不稳定性。因此,读写分离,实质上是一个在资金比较缺乏,但又需要保证数据安全的需求下,在双机热备方案上,做出的一种折中的扩展方案。
3.3 读写分离实现
MyCAT的读写分离机制如下:
例如将本机作为写库,10.18.96.133作为读库,MyCAT的读写分离的配置如下:
<dataHost name="testhost" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native"> <heartbeat>select user()</heartbeat> <!-- can have multi write hosts --> <writeHost host="hostM1" url="localhost:3306" user="root" password=""> <readHost host="hostM2" url="10.18.96.133:3306" user="test" password="test" /> </writeHost> </dataHost>
dataHost的balance属性设置为:
在没有配置数据同步复制的情况下,重启后进行测试,可使用MySQL客户端直接连接读库,插入几条数据后,使用MySQL客户端连接MyCat,运行select语句验证是否在读库上执行。
4.全局表
4.1全局表定义
一个真实的业务系统中,往往存在大量的类似字典表的表格,它们与业务表之间可能有关系,这种关系,可以理解为“标签”,而不应理解为通常的“主从关系”,这些表基本上很少变动,可以根据主键ID进行缓存,在分片的情况下,当业务表因为规模而进行分片以后,业务表与这些附属的字典表之间的关联,就成了比较棘手的问题,考虑到字典表具有以下几个特性:
鉴于此,MyCAT定义了一种特殊的表,称之为“全局表”,全局表具有以下特性:
将字典表或者符合字典表特性的一些表定义为全局表,则从另外一个方面,很好的解决了数据JOIN的难题。通过全局表+基于E-R关系的分片策略,MyCAT可以满足80%以上的企业应用开发。
4.2 全局表实现
1)创建表结构
在各个库分别创建全局表(例如:t_area)的表结构,表结构保持一致,例如:
DROP TABLE IF EXISTS `t_area`; CREATE TABLE `t_area` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', `caller` varchar(16) CHARACTER SET utf8 DEFAULT NULL COMMENT '调用方系统表示', `province_code` varchar(10) CHARACTER SET utf8 NOT NULL COMMENT '省份编码', `area_code` varchar(10) CHARACTER SET utf8 NOT NULL COMMENT '区域编码', `area_name` varchar(100) CHARACTER SET utf8 DEFAULT NULL COMMENT '区域名称', `parent_area_code` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '父区域编码', `create_time` datetime NOT NULL COMMENT '创建时间', `modify_time` datetime DEFAULT NULL COMMENT '修改时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3792 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
2)配置schema.xml
全局表配置比较简单,不用写Rule规则,在schema.xml中修改test_schema,添加t_area的table子元素,参考如下配置即可:
<schema name="test_mycat" checkSQLschema="false" sqlMaxLimit="100"> <!-- auto sharding by id (long) --> <table name="t_user" dataNode="user0,user1,user2,user3" rule="rule1"> <childTable name="t_user_class_rel" primaryKey="id" joinKey="user_id" parentKey="user_id" /> </table> <table name="t_area" primaryKey="id" type="global" dataNode="weixin,yixin,photo,user0,user1,user2,user3" /> </schema>
3)全局表测试
运行如下insert语句,往test_mycat的t_area表插入10条数据,如下所示:
INSERT INTO `t_area` VALUES ('100', 'test', 'ZX', '1', '全国', '0', '2012-09-25 08:30:23', null); INSERT INTO `t_area` VALUES ('101', 'test', 'BJ', '110000', '北京市', '1', '2012-09-25 08:30:23', null); INSERT INTO `t_area` VALUES ('102', 'test', 'BJ', '110100', '市辖区', '110000', '2012-09-25 08:30:23', null); INSERT INTO `t_area` VALUES ('103', 'test', 'BJ', '110101', '东城区', '110100', '2012-09-25 08:30:23', null); INSERT INTO `t_area` VALUES ('104', 'test', 'BJ', '110102', '西城区', '110100', '2012-09-25 08:30:23', null); INSERT INTO `t_area` VALUES ('105', 'test', 'BJ', '110103', '崇文区', '110100', '2012-09-25 08:30:23', null); INSERT INTO `t_area` VALUES ('106', 'test', 'BJ', '110104', '宣武区', '110100', '2012-09-25 08:30:23', null); INSERT INTO `t_area` VALUES ('107', 'test', 'BJ', '110105', '朝阳区', '110100', '2012-09-25 08:30:23', null); INSERT INTO `t_area` VALUES ('108', 'test', 'BJ', '110106', '丰台区', '110100', '2012-09-25 08:30:23', null); INSERT INTO `t_area` VALUES ('109', 'test', 'BJ', '110107', '石景山区', '110100', '2012-09-25 08:30:23', null);
插入后去user0~user3数据库中查找,可看到这4个库中的t_area表都被插入10条数据。执行select语句能返回t_area表的对应记录,执行update和delete语句能对应对全局表相关的4个库中的记录进行更新和删除操作。
参考文档:
Related posts: