天天看点

Mycat数据库中间件Mycat

Mycat

一MyCat 简介

1.1 mycat历史

              2013年阿里的Cobar在社区使用过程中发现存在一些比较严重的问题,及其使用限制,经过Mycat发起人第一次改良,第一代改良版——Mycat诞生。 Mycat开源以后,一些Cobar的用户参与了Mycat的开发,最终Mycat发展成为一个由众多软件公司的实力派架构师和资深开发人员维护的社区型开源软件。

2014年Mycat首次在上海的《中华架构师》大会上对外宣讲,更多的人参与进来,随后越来越多的项目采用了Mycat。

2015年5月,由核心参与者们一起编写的第一本官方权威指南《Mycat权威指南》电子版发布,累计超过500本,成为开源项目中的首创。

2015年10月为止,Mycat项目总共有16个Committer。

截至2015年11月,超过300个项目采用Mycat,涵盖银行、电信、电子商务、物流、移动应用、O2O的众多领域和公司。

截至2015年12月,超过4000名用户加群或研究讨论或测试或使用Mycat。

Mycat是基于开源cobar演变而来,我们对cobar的代码进行了彻底的重构,使用NIO重构了网络模块,并且优化了Buffer内核,增强了聚合,Join等基本特性,同时兼容绝大多数数据库成为通用的数据库中间件。1.4 版本以后 完全的脱离基本cobar内核,结合Mycat集群管理、自动扩容、智能优化,成为高性能的中间件。我们致力于开发高性能数据库中间而努力。永不收费,永不闭源,持续推动开源社区的发展。

Mycat吸引和聚集了一大批业内大数据和云计算方面的资深工程师,Mycat的发展壮大基于开源社区志愿者的持续努力,感谢社区志愿者的努力让Mycat更加强大,同时我们也欢迎社区更多的志愿者,特别是公司能够参与进来,参与Mycat的开发,一起推动社区的发展,为社区提供更好的开源中间件。

Mycat还不够强大,Mycat还有很多不足,欢迎社区志愿者的持续优化改进。

1.2 MyCat的作用

遵守Mysql原生协议,跨语言,跨平台,跨数据库的通用中间件代理。

基于心跳的自动故障切换,支持读写分离,支持MySQL主从,以及galera cluster集群。

支持Galera for MySQL集群,Percona Cluster或者MariaDB cluster

基于Nio实现,有效管理线程,高并发问题。

支持数据的多片自动路由与聚合,支持sum,count,max等常用的聚合函数。

支持单库内部任意join,支持跨库2表join,甚至基于caltlet的多表join。

支持通过全局表,ER关系的分片策略,实现了高效的多表join查询。

支持多租户方案。  

支持分布式事务(弱xa)。

支持全局序列号,解决分布式下的主键生成问题。

分片规则丰富,插件化开发,易于扩展。

强大的web,命令行监控。

支持前端作为mysq通用代理,后端JDBC方式支持Oracle、DB2、SQL Server 、 mongodb 、巨杉。

支持密码加密

支持服务降级

支持IP白名单

支持SQL黑名单、sql注入攻击拦截

支持分表(1.6)

集群基于ZooKeeper管理,在线升级,扩容,智能优化,大数据处理(2.0开发版)。

二Mycat 术语与原理

2.1 垂直分片

Mycat数据库中间件Mycat

2.2 水平分片

Mycat数据库中间件Mycat

如上所示是我们所说的数据库分片。单表可以进行水平分片或者垂直分片。具体的一般来说字段比较多,经常查询的字段集中在某几个字段的大表。我们可能需要进行垂直分片,用来提高查询的性能。但是如果是数据量比较大的话,我们尽量采用水平分片,解决单表数据量过大造成的查询基数过大,或者并发访问比较大,造成数据库连接销售比较大的场景。

关于垂直分表,一般按照常用的字段分一张表,即可。至于水平分表,这个就可以自定义分表规则来进行分表了。按照具体的业务来进行具体的分表。水平分表有两种方式,一般是分库分表,和库内分表两种。

2.2.1 分库分表

Mycat数据库中间件Mycat

如上图所示是分库分表的示意图,如图所示我们把一张很大的表拆分到不同的数据库中,相对于应用程序来说,应用程序能够看到的就是前面的逻辑库,然而真实的却是后面的三个物理库与表。分库分表又涉及到一个问题。是一台主机一个库,还是一台主机N个库的问题。这里就涉及到一个问题了,如果我们把一个库叫做一个节点的话,实际上一台物理机上是可以有多个节点的。也就是说我们既可以一台物理机N个节点,也可以一台物理机一个节点。

Mycat数据库中间件Mycat

如上图所示的配置,在mycat中的配置展示如下所示,可以看到dataNode代表的就是数据库,然而dataHost表示的是数据库所在的主机,这也充分的说明了分库分表节点与主机的关系。

<dataNode name="dn1" dataHost="localhost1" database="db1" />  

<dataNode name="dn2" dataHost="localhost1" database="db2" />  

<dataNode name="dn3" dataHost="localhost1" database="db3" />

<dataHost name="localhost1" maxCon="1000" minCon="10" balance="0“  writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">                

<heartbeat>select user()</heartbeat>          

<!-- can have multi write hosts -->          

<writeHost host="hostM1" url="localhost:3306" user="root“  password="123456">            

<!-- can have multi read hosts -->           

<writeHost host="hostS1" url="localhost:3316" user="root“ password="123456" />

</writeHost>

Mycat数据库中间件Mycat

如图是mycat的工作流程,指定mycat的分片字段,再指定mycat的分片规则,按照指定的分片规则把对应的数据放到不同的节点中去。

Mycat数据库中间件Mycat

如上分别是schema.xml配置文件中配置的表分片到两个节点上,调用了rule.xml中具体的分片规则进行分片。

2.2.2 数据库表

(1)非分片表

     非分片表是指普通的业务表,在我们的整个业务系统中,实际上毕竟也不是每一个表的数据量都很大的。这就行成了我们普通的业务表,也就是我们的非分片表。非分片表可以指定放在一个主库中,作为我们读写数据的主库用。数据量不会随着系统使用年限的暴增而暴增的那种。

<table name=”t_node” primaryKey=”vid” autoIncrement=”true” dataNode=”dn1”></table>
           
  1. 分片表

      也就是我们所说的大表,也就是说单表数据量超过1000万的表,在系统开发阶段,如果逾期单表数据量会超过1000万的表,将来一定会成为系统的鸡肋,这种情况下要对这种容易形成大表的表做分表处理,避免单表数据量过大,影响系统的查询性能。

<table name=”t_node” primaryKey=”vid” autoIncrement=”true” dataNode=”dn1,dn2” rule1></table>
           
  1. E-R表            
Mycat数据库中间件Mycat

普通E-R 

<table name=”order” dataNode=”dn$1-32” rule=”mod-long”>
    <childTable name="order_detail" primaryKey="id" joinKey=”order_id” parentKey=”order_id”/>
</table>
           

 级联E-R层级关系配置

<table name="user"    primaryKey="ID" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="mod-long" >
			<childTable name="company"  joinKey="user_id" parentKey="id">
				<childTable name="employee"  joinKey="user_id" parentKey="id"/>
			</childTable>
		</table>
           

      对于单层的E-R关系表的分表,字表可以通过父表的主键来进行分表。

      对于多层的E-R关系表的分表,可以在第三层,或者更深层次的子表中增加祖先表的id,作为冗余字段,这样的话分表的时候

就可以通过祖先表的id来作为分表的条件。这就是级联分表的方案。

多对多关系处理

有一类业务场景是”主表A + 关系表 + 主表B”,举例来说就是商户会员 + 订单 + 商户,对应这类业务,如果切分: 

从会员的角度,如果需要查询会员购买的订单,那按照会员进行切分即可,但是如果查询商户当天售出的订单,那又需要按照商户做切分,可以是如果按照会员又要按照商户切分,几乎是无法实现,这类业务如何选择切分规则非常难。目前还暂时无法很好支持这种模式下的3个表之间的关联。目前总的原则是需要从业务角度来看,关系表更偏向哪个表,即”A的关系”还是”B的关系”,来决定关系表跟从那个方向来存储,未来MyCat版本中将考虑将中间表进行双向复制,以实现从A-关系表以及B-关系表的双向关联查询如下图所示: 

Mycat数据库中间件Mycat

存在父子表的数据插入的过程中,mycat会把与主表存在关联关系的子表数据路由到父表所在的节点。从而父子表关系的查询可以下推到各个节点去完成。这就是高效的跨节点join技术。

(4)全局表

Mycat数据库中间件Mycat

每个节点同时并发插入数据,更新数据,每个节点都可以同时读取数据,提升读性能的同时,解决跨节点join的效率。

<table name=”t_area” primaryKey=”id” type=”gloab” dataNode=”dn1,dn2”/>
           
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
	<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100">
		<table name="user"    primaryKey="ID" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="mod-long" >
				<childTable name="company"  joinKey="user_id" parentKey="id">
					<childTable name="employee"  joinKey="user_id" parentKey="id"/>
				</childTable>
			    <childTable name="u_c_detail"  joinKey="user_id" parentKey="id"/>
		</table>
		
		  <table name="role"    primaryKey="ID" dataNode="dn1,dn2,dn3"  type="global"/>
		   <table name="contract"    primaryKey="ID" dataNode="dn1,dn2,dn3" rule="mod-long" >
					<childTable name="c_u_detail"  joinKey="contract_id" parentKey="id"/>
		   </table>
	</schema>
	
	<dataNode name="dn1" dataHost="localhost1" database="db1" />
	<dataNode name="dn2" dataHost="localhost1" database="db2" />
	<dataNode name="dn3" dataHost="localhost1" database="db3" />
	
	<dataHost name="localhost1" maxCon="1000" minCon="10" balance="0"
			  writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
		<heartbeat>select user()</heartbeat>
		<writeHost host="hostM1" url="127.0.0.1:3306" user="root"
				   password="">
		</writeHost>
	</dataHost>
	
</mycat:schema>
           

    如上所示配置分别有全局表,分片级联表,多对多关系表的处理配置的实例。

(5)全局主键                

由于采用了,分片的方式进行了数据的存储,所以无法保证主键的唯一性,要保证主键的唯一性,我们就要借助一张表来单独生成主键的序列。

在数据库中建立一张表,存放sequence名称(name),sequence当前值(current_value),步长(increment) int类型每次读取多少个sequence,假设为K)等信息

2.2.3 库内分表

Mycat数据库中间件Mycat

如上图所示我们把一张表在同一个库中拆成N分这样子。

三 主从复制

Mycat数据库中间件Mycat

如上图所示mycat可以帮助实现主从的架构,  影响MySQL-A数据库的操作,在数据库执行后,都会写入本地的日志系统A中。

假设,实时的将变化了的日志系统中的数据库事件操作,在MYSQL-A的3306端口,通过网络发给MYSQL-B。 MYSQL-B收到后,写入本地日志系统B,然后一条条的将数据库事件在数据库中完成。那么,MYSQL-A的变化,MYSQL-B也会变化,这样就是所谓的MYSQL的复制,即MYSQL replication。

在上面的模型中,MYSQL-A就是主服务器,即master,MYSQL-B就是从服务器,即slave。 

日志系统A,其实它是MYSQL的日志类型中的二进制日志,也就是专门用来保存修改数据库表的所有动作,即bin log。【注意MYSQL会在执行语句之后,释放锁之前,写入二进制日志,确保事务安全】

 日志系统B,并不是二进制日志,由于它是从MYSQL-A的二进制日志复制过来的,并不是自己的数据库变化产生的,有点接力的感觉,称为中继日志,即relay log。 可以发现,通过上面的机制,可以保证MYSQL-A和MYSQL-B的数据库数据一致,但是时间上肯定有延迟,即MYSQL-B的数据是滞后的。【即便不考虑什么网络的因素,MYSQL-A的数据库操作是可以并发的执行的,但是MYSQL-B只能从relay log中读一条,执行下。因此MYSQL-A的写操作很频繁,MYSQL-B很可能跟不上。】

四 读写分离

Mycat数据库中间件Mycat

  如上图所示是mycat 支持读写分离的架构的示意图,如图所示我们通过binlog实现了主从复制以后,使得主库与从库有了相同的数据副本。我们可以配置mycat从一个数据库中写数据,并且从另一个数据库中对数据进行读取。

Mycat数据库中间件Mycat

如上图所示我们应用程序直接对接,我们的数据库中间件mycat,后台的mycat采用了一主多从,读写分离的存储部署架构。可以提高数据库的读写性能与系统的并发。

事务内的SQL,默认走写节点,以注释开头,则会根据balance=“1”或“2”去获取

非事务内的SQL,开启读写分离默认根据balance=“1”或“2”去获取,以注释开头则会走写解决部分已经开启读写分离,但是需要强一致性数据实时获取的场景走写

负载均衡类型,目前的取值有3种:

1. balance="0", 不开启读写分离机制,所有读操作都发送到当前可用的writeHost上。

2. balance="1",全部的readHost与stand by writeHost参与select语句的负载均衡,简单的说,当双主双从模式(M1->S1,M2->S2,并且M1与 M2互为主备),正常情况下,M2,S1,S2都参与select语句的负载均衡。

3. balance="2",所有读操作都随机的在writeHost、readhost上分发。

4. balance="3",所有读请求随机的分发到wiriterHost对应的readhost执行,writerHost不负担读压力

五 数据连接池

Mycat数据库中间件Mycat

如上图所示以前一个应用,或者多个应用连接同一个库,分享一个库的2000个最大连接,引入mycat的以后,我们可以设置每个应用最大连接2000个。mycat后台有一个数据库连接的连接池可以用。

Mycat 基于MySQL实例而非database的连接池复用机制,可以让每个应用最大程度共享一个MySQL实例的所有连接池,让数据库的并发访问能力大大提升。

六 分片规则

Mycat数据库中间件Mycat

数据库分片的规则分两个大类,一种是连续的分片也就是我们说的范围分片,一种是离散的分片也就是我们说的取模分片。

6.1连续分片

扩容无需迁移数据,范围查询消耗资源少。 

存在数据热点的可能性,并发访问能力受限于单一或少量的dataNode。

6.2离散分片

并发访问能力强,范围条件查询性能提升。    

数据扩容比较困难,涉及数据迁移问题。

数据库连接消耗比较多。

6.3 二次分片

Mycat数据库中间件Mycat

如上图所示,先进行范围分片,再进行离散分片,既解决了热点数据,资源受限的问题。又解决了,并发访问能力的问题,也比较进行数据扩容,避免了数据迁移。

6.4 常用mycat分片                

6.4.1 自定义范围分片

自定义数字范围分片,提前规划好分片字段某个范围属于哪个分片

<function name="rang-long" class="org.opencloudb.route.function.AutoPartitionByLong">

<property name="mapFile">autopartition-long.txt</property>

<property name="defaultNode">0</property>

</function>

defaultNode 超过范围后的默认节点。

此配置非常简单,即预先制定可能的id范围到某个分片

        0-500M=0

         500M1-1000M=1

         1000M1-1500M=2

         0-10000000=0

         10000001-20000000=1

注意: 所有的节点配置都是从0开始,及0代表节点1

6.4.2 按日期分片

从开始日期算起,按照天数来分片

例如,从2014-01-01,每10天一个分片

注意事项:需要提前将分片规划好,建好,否则有可能日期超出实际配置分片数

<function name="sharding-by-date" class="org.opencloudb.route.function.PartitionByDate">

      <property name=“dateFormat”>yyyy-MM-dd</property>     <!—日期格式-->

<property name=“sBeginDate”>2014-01-01</property>       <!—开始日期-->

<property name=“sPartionDay”>10</property>

</function>    <!—每分片天数-->

6.4.3 按自然月分片

每个自然月一个分片。需要提前将分片数规划好,建好,否则有可能日期超出实际配置分片数。

<function name="sharding-by-month" class="org.opencloudb.route.function.PartitionByMonth">

<property name="dateFormat">yyyy-MM-dd</property>

<property name="sBeginDate">2014-01-01</property>

</function>

dateFormat : 日期字符串格式

sBeginDate : 开始日期

6.4.4 十进制求模分片

规则为对分片字段十进制取模运算,数据分布最均匀。

<function name="mod-long" class="org.opencloudb.route.function.PartitionByMod">

<property name="count">3</property>

</function>

6.4.5 一致性hash分片

此规则优点在于扩容时迁移数据量比较少,前提分片节点比较多,虚拟节点分配多些。

虚拟节点少的缺点是会造成数据分布不够均匀。

Mycat数据库中间件Mycat
Mycat数据库中间件Mycat

        一致性hash算法大致的意思是,创建一条2的32次幂的时间轴,跟正式的物理机一样创建N个正式的物理机节点,环上形成CN个虚拟节点,在换上实现一个虚拟的映射。在命中的时候,根据距离环最近的虚拟节点,落到真实的节点上即可。按照分片规则应该描述为寻找最近的虚拟分片,插入到真实的物理分片中。

       此规则优点在于扩容时迁移数据量比较少,前提分片节点比较多,虚拟节点分配多些。虚拟节点少的缺点是会造成数据分布不够均匀,如果实际分片数量比较少,迁移量会比较多。

<function name="murmur"

class="org.opencloudb.route.function.PartitionByMurmurHash">

<property name="seed">0</property><!-- 默认是0-->

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

<!-- 要分片的数据库节点数量,必须指定,否则没法分片-->

<property name="virtualBucketTimes">160</property><!-- 一个实际的数据库节点被映射为这么多虚拟节点,默认是160倍,也就是虚拟节点数是物理节点数的160倍-->

6.4.6 范围求模分片

先进行范围分片计算出分片组,组内再求模优点可以避免扩容时的数据迁移,又可以一定程度上避免范围分片的热点问题分片组内使用求模可以保证组内数据比较均匀,分片组之间是范围分片可以兼顾范围查询。

最好事先规划好分片的数量,数据扩容时按分片组扩容,则原有分片组的数据不需要迁移。由于分片组内数据比较均匀,所以分片组内可以避免热点数据问题。

<function name="rang-mod" class="org.opencloudb.route.function.PartitionByRangeMod">

<property name="mapFile">partition-range-mod.txt</property>

 <property name="defaultNode">21</property>

</function>

partition-range-mod.txt

以下配置一个范围代表一个分片组,=号后面的数字代表该分片组所拥有的分片的数量。

0-200M=5  //代表有5个分片节点

200M1-400M=1

400M1-600M=4

600M1-800M=4

800M1-1000M=6

6.4.7 日期范围hash分片

日期范围hash分片:

思想与范围求模一致,当由于日期在取模会有数据集中问题,所以改成hash方法。

要求日期格式尽量精确些,不然达不到局部均匀的目的

sPartionDay代表多少天分一个分片

groupPartionSize代表分片组的大小

Mycat数据库中间件Mycat

6.4.8 分片场景策略与总结

根据表数据量判断是否需要切分,确保切分后单分片表数据量为1000W左右

根据业务的情况选择合适的分片字段:最频繁的或者最重要的查询条件,需要考虑扩容数据迁移问题(范围类,范围取模类不需要迁移,哈希类需要迁移)

有关联关系的表配置相同分片规则(ER思想,为了应用join等复杂sql),一对多对应关系一般按多的那一方切分

如果配置类数据,更新频率比较少,考虑全局表

七 查询

7.1 跨分片查询

Mycat数据库中间件Mycat

在跨分片进行查询的时候,mycat是基于内存进行分片的分页与排序的,所以呢如果数据量比较大的情况下,会比较消耗mycat的整体性能。

Mycat数据库中间件Mycat

如上图是mycat对后台的查询分页的操作,如图所示mycat把查询下发到具体的mysql节点,这个过程sql的执行是并发的执行的,也就是mycat执行一次,下发到下面的n台mysql的物理机上对应的去查询。

7.2 跨分片排序

Mycat数据库中间件Mycat

Mycat排序采用的是堆排序,具体堆排序怎么玩的,自行看算法的书籍。

7.3 跨分片连接

Mycat数据库中间件Mycat

在每一个分片库里,参与join的,多个表数据符合join的分片规则。简单的说就是E-R表分片在不同的数据库中的时候。就是单独查询那个数据库的信息了。

八 高可用

Mycat数据库中间件Mycat

如上图所示可以用keepalived,映射一个VIP的IP地址来实现高可用的架构。

九 mycat使用经验分享

不支持语法

select

    跨分片的交叉查询,两个表中的分片字段不同

    跨结点的联合查询

insert

   插入的字段不包含分片字段

   插入的分片找不到对应的分片

   复制插入,insert into ... select ...

update

    更新列包含分片列

delete

    删除语句不能起别名,delete user_info a where a.main_user_id=1

全表扫描

1、复杂的sql会分发到所有库上执行,并且在mycat中进行比较、合并,要尽量避免。

2、insert/replace 必须要包含分库字段

3、select/update/delete语句如果where没有包含分库字段,会进行全表扫描。

4、支持in(...)语法,mycat 内部将其视为多个条件值的OR。

聚合函数

1、如果目标表分库,mycat可以支持任何聚合函数,实际上DRDS 是直接把原sql 传递到后端mysql 执行

2、sql语句经过mycat路由后,直接发送到后端单个mysql 库上执行。如分库字段在where条件中都是= 关系,同样可以支持任何聚合函数。

3、全表扫描:目前支持聚合函数有:count,max,min,sum 另外在全表扫描时同样支持like,order by,limit ,不支持group by。

1、order by

1)order by 中出现的列名,保证在select 后出现,

如:select c1 from tb order by c2 写成:select c1,c2 from db order by c2

2)当语句中同时有order by 和 limit 分页时,mycat 执行order by 的内部逻辑是将所有的limit offset 之前的数据从mysql 加载到mycat 服务器内存中进行排序。所以不要对大量数据进行order by + limit 翻页。

3、对于非count,max,min,sum的聚合函数,在全表扫描的场景下执行不一定会返回错误,但是执行的结果很可能是错误的。

4、mycat 支持like 查询,会进行全表扫描

mycat 的HINT支持

mycat 对自身不支持的sql 语句提供了一种解决方案,即在要执行的sql 语句前添加额外的一段注解sql,这样sql 就能正确执行,这段sql 称为“注解sql”。其作用就是通过“注解sql”找到最终执行的sql数据节点,把要执行的sql发送到节点执行。

使用方式:要执行的sql

示例:

前提:表tb_user 分片字段为user_id。

操作:通过mysql 命令连接到 mycat(连接命令mysql -h192.168.192.133 -utest -ptest -P8066 -DTESTDB)

执行sql

mysql>select * from tb_user;

ERROR 1105 (HY000): backend connect: java.net.NoRouteToHostException: No route to host

可以看到错误信息为找不到目标host

使用注解,查询某一个结点的所有tb_user 数据,查询user_id=227 上分片的所有user_id,使用注解如下:

mysql>SELECT * FROM `bill_log`;

注解查询出来的数据在那个结点,就把sql 发送到那一个结点执行并返回。

事物支持

没有跨分片的事物强一致性支持,单库内部可以保证事务的完事性。如果跨库事物,在执行的时候任何分片出错所有分片都会加滚,但是只要应用发起commit,不能保证所有的分片都能commit。