随着传统的数据库技术日趋成熟、计算机网络技术的飞速发展和应用范围的扩充,数据库应用已经普遍建立于计算机网络之上。这时集中式数据库系统表现出它的不足:
(1)集中式处理,势必造成性能瓶颈;
(2)应用程序集中在一台计算机上运行,一旦该计算机发生故障,则整个系统受到影响,可靠性不高;
(3)集中式处理引起系统的规模和配置都不够灵活,系统的可扩充性差。
在这种形势下,集中式数据库将向分布式数据库发展。
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逻辑库相关的一些概念:
安装MySQL服务器和MySQL客户端,笔者使用的MySQL服务器是免安装版本:mysql-noinstall-5.1.73-winx64,MySQL客户端是:Navicat for MySQL,免安装版本安装方法请参考:http://blog.csdn.net/q98842674/article/details/12094777,不再赘述。
创建weixin、yixin和sms三个数据库,并分别建立表结构。
数据的垂直切分,也可以称为纵向切分。将数据库想象成由很多个一大块一大块的“数据块”(表)组成,垂直地将这些“数据块”切开,然后把它们分散到多台数据库主机上面。这样的切分方法就是垂直(纵向)的数据切分。
一个架构设计较好的应用系统,其总体功能肯定是由很多个功能模块所组成的,而每一个功能模块所需要的数据对应到数据库中就是一个或多个表。而在架构设计中,各个功能模块相互之间的交互点越统一、越少,系统的耦合度就越低,系统各个模块的维护性及扩展性也就越好。这样的系统,实现数据的垂直切分也就越容易。
垂直切分优点:
(1)数据库的拆分简单明了,拆分规则明确;
(2)应用程序模块清晰明确,整合容易;
(3)数据维护方便易行,容易定位。
垂直切分缺点:
(1)部分表关联无法在数据库级别完成,要在程序中完成;
(2)对于访问极其频繁且数据量超大的表仍然存在性能瓶颈,不一定能满足要求;
(3)事务处理相对复杂;
(4)切分达到一定程度之后,扩展性会受到限制;
(5)过度切分可能会带来系统过于复杂而难以维护。
在如下的实例中,需要将
编辑MYCAT_HOME/conf/schema.xml文件,修改dataHost和schema对应的连接信息,weixin、yixin和photo垂直切分后的配置如下所示:
<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>
<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,文件内容参考如下:
<mycat:server xmlns:mycat="http://org.opencloudb/">
<system>
<property name="sequnceHandlerType">0property>
system>
<user name="test">
<property name="password">testproperty>
<property name="schemas">weixin,yixin,photoproperty>
user>
mycat:server>
上述文件中的schemas属性需要配置对应的schema(在schema.xml)中进行配置。
重启MyCAT,使用MySQL客户端连接MyCAT,需要注意的是,默认数据端口为8066,管理端口为9066,在MySQL客户端连接MyCAT时,注意填写端口为8066,用户名/密码根据server.xml中的配置进行填写。
水平切分所指的是通过一系列的切分规则将数据水平分布到不同的DB或table中,在通过相应的DB路由 或者table路由规则找到需要查询的具体的DB或者table以进行Query操作,比如根据用户ID将用户表切分到多台数据库上。
将某个访问极其频繁的表再按照某个字段的某种规则来分散到多个表之中,每个表中包含一部分数据。
例如,所有数据都是和用户关联的,那么我们就可以根据用户来进行水平拆分,将不同用户的数据切分到不同的数据库中。
现在互联网非常火爆的web 2.0类型的网站,基本上大部分数据都能够通过会员用户信息关联上,可能很多核心表都非常适合通过会员ID来进行数据的水平切分。而像论坛社区讨论系统,就更容易切分了,非常容易按照论坛编号来进行数据的水平切分。切分之后基本上不会出现各个库之间的交互。
水平切分的优点:
在一般的应用系统中,用户表及其密切相关的关联表,可根据“用户表”(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文件内容如下所示:
<mycat:schema xmlns:mycat="http://org.opencloudb/">
<schema name="test_mycat" checkSQLschema="false" sqlMaxLimit="100">
<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>
<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数据库。
该文件的参考内容如下所示:
<mycat:rule xmlns:mycat="http://org.opencloudb/">
<tableRule name="rule1">
<rule>
<columns>user_idcolumns>
<algorithm>func1algorithm>
rule>
tableRule>
<function name="func1" class="org.opencloudb.route.function.PartitionByLong">
<property name="partitionCount">4property>
<property name="partitionLength">256property>
function>
mycat:rule>
(4)配置server.xml文件
在server.xml文件中的schemas属性中添加test_mycat的schema。修改后的文件如下所示:
<mycat:server xmlns:mycat="http://org.opencloudb/">
<system>
<property name="sequnceHandlerType">0property>
system>
<user name="test">
<property name="password">testproperty>
<property name="schemas">weixin,yixin,photo,test_mycatproperty>
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');
为了确保数据库产品的稳定性,很多数据库拥有双机热备功能。也就是,第一台数据库服务器,是对外提供增删改查业务的生产服务器;第二台数据库服务器,仅仅接收来自第一台服务器的备份数据。一般来说,为了配置方便,以及稳定性,这两台数据库服务器,都用的是相同的配置。
在实际运行中,第一台数据库服务器的压力,远远大于第二台数据库服务器。因此,很多人希望合理利用第二台数据库服务器的空闲资源。
从数据库的基本业务来看,数据库的操作无非就是增删改查这4个操作。但对于“增删改”这三个操作,如果是双机热备的环境中做,一台机器做了这三个操作的某一个之后,需要立即将这个操作,同步到另一台服务器上。出于这个原因,第二台备用的服务器,就只做了查询操作。进一步,为了降低第一台服务器的压力,干脆就把查询操作全部丢给第二台数据库服务器去做,第一台数据库服务器就只做增删改了。
优点:合理利用从数据库服务器的空闲资源。
缺点:本来第二台数据库服务器,是用来做热备的,它就应该在一个压力非常小的环境下,保证运行的稳定性。而读写分离,却增加了它的压力,也就增加了不稳定性。因此,读写分离,实质上是一个在资金比较缺乏,但又需要保证数据安全的需求下,在双机热备方案上,做出的一种折中的扩展方案。
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>
<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属性设置为:
一个dataHost元素,表明进行了数据同步的一组数据库,DBA需要保证这一组数据库服务器是进行了数据同步复制的。writeHost相当于Master DB Server,而其下的readHost则是与从数据库同步的Slave DB Server。当dataHost配置了多个writeHost的时候,任何一个writeHost宕机,Mycat 都会自动检测出来,并尝试切换到下一个可用的writeHost。
在没有配置数据同步复制的情况下,重启后进行测试,可使用MySQL客户端直接连接读库,插入几条数据后,使用MySQL客户端连接MyCat,运行select语句验证是否在读库上执行。
一个真实的业务系统中,往往存在大量的类似字典表的表格,它们与业务表之间可能有关系,这种关系,可以理解为“标签”,而不应理解为通常的“主从关系”,这些表基本上很少变动,可以根据主键ID进行缓存,下面这张图说明了一个典型的“标签关系”图:
在分片的情况下,当业务表因为规模而进行分片以后,业务表与这些附属的字典表之间的关联,就成了比较棘手的问题,考虑到字典表具有以下几个特性:
鉴于此,MyCAT定义了一种特殊的表,称之为“全局表”,全局表具有以下特性:
将字典表或者符合字典表特性的一些表定义为全局表,则从另外一个方面,很好的解决了数据JOIN的难题。通过全局表+基于E-R关系的分片策略,MyCAT可以满足80%以上的企业应用开发。
(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">
<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个库中的记录进行更新和删除操作。