概述

Mycat是一个开源的分布式数据库中间件,是个实现了MySQL协议的的Server,也可以理解为是数据库代理。在架构体系中是位于数据库和应用层之间的一个组件,并且对于应用层是透明的,即数据库感受不到mycat的存在,认为是直接连接的mysql数据库。其核心功能是分表分库,即将一个大表水平分割为N个小表,存储在后端MySQL服务器里或者其他数据库里。

原理

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

 

MyCat技术分享

orders表被分为三个分片,这三个分片是分布在两台MySQL Server上。

应用场景

  • 单纯的读写分离,此时配置最为简单,支持读写分离,主从切换
  • 分表分库,对于超过1000万的表进行分片,最大支持1000亿的单表分片
  • 替代Hbase,分析大数据

Mycat中的概念

  • 逻辑库(schema)

          与MySQL中的Database(数据库)对应,一个逻辑库中定义了所包括的Table (逻辑表)。

  • 逻辑表

          分布式数据库中,对应用来说,读写数据的表就是逻辑表。逻辑表,可以是数据切分后,分布在一个或多个分片库中,也可以不做数据切分,不分片,只有一个表构成。

    • 分片表

                分片表,是指那些原有的很大数据的表,需要切分到多个数据库的表,这样,每个分片都有一部分数据,所有分片构成了完整的数据。
                例如在mycat配置中的t_node就属于分片表,数据按照规则被分到dn1,dn2两个分片节点(dataNode)上。

<table name="t_node" primaryKey="id" dataNode="dn1,dn2" rule="rule1" />

 

    • 非分片表

                 一个数据库中并不是所有的表都很大,某些表是可以不用进行切分的,非分片是相对分片表来说的,就是那些不需要进行数据切分的表。

<table name="t_node" primaryKey="id" dataNode="dn1" />

 

    • ER表

                关系型数据库是基于实体关系模型(Entity-Relationship Model)之上,通过其描述了真实世界中事物与关系,Mycat中的ER表即是来源于此。根据这一思路,提出了基于E-R关系的数据分片策略,子表的记录与所关联的父表记录存放在同一个数据分片上,即子表依赖于父表,通过表分组(Table Group)保证数据Join不会跨库操作。
                表分组(Table Group)是解决跨分片数据join的一种很好的思路,也是数据切分规划的重要一条规则。

<table name="customer" primaryKey="id" dataNode="dn1" >
	<childTable name="orders" primaryKey="id" joinKey="customer_id" parentKey="id" />
</table>
    • 全局表

                一个真实的业务系统中,往往存在大量的类似字典表的表,这些表基本上很少变动,对于这类的表,在分片的情况下,当业务表因为规模而进行分片以后,业务表与这些附属的字典表之间的关联,就成了比较棘手的问题,所以Mycat中通过数据冗余来解决这类表的join,即所有的分片都有一份数据的拷贝,所有将字典表或者符合字典表特性的一些表定义为全局表。

                数据冗余是解决跨分片数据join的一种很好的思路,也是数据切分规划的另外一条重要规则。

<table name="company" primaryKey="id" type="global" dataNode="dn1,dn2,dn3" />
  • 分片节点(dataNode)

         数据切分后,一个大表被分到不同的分片数据库上面,每个表分片所在的数据库就是分片节点(dataNode)。

<dataNode name="dn1" dataHost="localhost1" database="db1" />
  • 节点主机(dataHost)

          数据切分后,每个分片节点(dataNode)不一定都会独占一台机器,同一机器上面可以有多个分片数据库,这样一个或多个分片节点(dataNode)所在的机器就是节点主机(dataHost),为了规避单节点主机并发数限制,尽  量将读写压力高的分片节点(dataNode)均衡的放在不同的节点主机(dataHost)。

	<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="root">
			<!-- can have multi read hosts -->
			<readHost host="hostS2" url="localhost:3307" user="root" password="root" />
		</writeHost>
	</dataHost>
  • 分片规则(rule)

          前面讲了数据切分,一个大表被分成若干个分片表,就需要一定的规则,这样按照某种业务规则把数据分到某个分片的规则就是分片规则,数据切分选择合适的分片规则非常重要,将极大的避免后续数据处理的难度。

 

 Mycat一个逻辑库到物理库(mysql)的映射关系:

MyCat技术分享MyCat技术分享MyCat技术分享MyCat技术分享MyCat技术分享MyCat技术分享MyCat技术分享MyCat技术分享

分片规则概述

在数据切分处理中,特别是水平切分中,中间件最终要的两个处理过程就是数据的切分、数据的聚合。选择合适的切分规则,至关重要,因为它决定了后续数据聚合的难易程度,甚至可以避免跨库的数据聚合处理。

数据库分片

  • 垂直拆分
    MyCat技术分享MyCat技术分享MyCat技术分享
    垂直切分是指按照业务将表进行分类,分布到不同的数据库上面。
  • 水平拆分
  • MyCat技术分享

 

相对于垂直拆分,水平拆分不是将表做分类,而是按照某个字段的某种规则来分散到多个库之中,每个表中包含一部分数据。

分片规则设计架构

分布式数据库系统中,分片规则用于定义数据与分片的路由关系,也就是insert,deleted,update,select 的基本sql操作中,如何将sql路由到对应的分片去执行。

分片规则是最终解析sql到那个分片执行的规则,Mycat分片是根据分片字段来确定数据的分布,即根据预先配置好的分片字段(只有一个)到分片规则中解析该字段对应的值应该路由到哪个分片,然后确认sql到哪个分片执行。

MyCat技术分享

Mycat路由分发流程

从原理上看,可以把mycat看成一个sql转发器。mycat接收前端发过来的sql,然后转发到后台的mysql服务器上去执行。后台有很多台mysql节点如dn1,dn2,dn3,;由mycat按照一定的路由规则转发到具体的节点上。

路由计算流程

无表路由计算:sql路由到任意一个datanode即可。

全局表路由计算:sql路由到所有节点。

单表路由计算:

 

多表路由计算:

 

 

分库分表原则

分表分库虽然能解决大表对数据库系统的压力,但它并不是万能的,也有一些不利之处,因此首要问题是,分不分库,分哪些库,什么规则分,分多少分片。

  1. 能不分就不分,1000万以内的表,不建议分片,通过合适的索引,读写分离等方式,可以很好的解决性能问题。
  2. 分片数量尽量少,分片尽量均匀分布在多个DataHost上,因为一个查询SQL跨分片越多,则总体性能越差,虽然要好于所有数据在一个分片的结果,只在必要的时候进行扩容,增加分片数量。
  3. 分片规则需要慎重选择,分片规则的选择,需要考虑数据的增长模式,数据的访问模式,分片关联性问题,以及分片扩容问题,最近的分片策略为范围分片,枚举分片,一致性Hash分片,这几种分片都有利于扩容。
  4. 尽量不要在一个事务中的SQL跨越多个分片,分布式事务一直是个不好处理的问题。
  5. 查询条件尽量优化,尽量避免select * 的方式,大量数据结果集下,会消耗大量带宽和CPU资源,查询尽量避免返回大量结果集,并且尽量为频繁使用的查询语句建立索引。
     

数据拆分原则

  1. 达到一定数量级才拆分(800万)
  2. 不到800万但跟大表(超800万的表)有关联查询的表也要拆分,在此称为大表关联表
  3. 大表关联表如何拆:小于100万的使用全局表;大于100万小于800万跟大表使用同样的拆分策略;无法跟大表使用相同规则的,可以考虑从java代码上分步骤查询,不用关联查询,或者破例使用全局表。
  4. 破例的全局表:如item_sku表250万,跟大表关联了,又无法跟大表使用相同拆分策略,也做成了全局表。破例的全局表必须满足的条件:没有太激烈的并发update,如多线程同时update同一条id=1的记录。虽有多线程update,但不是操作同一行记录的不在此列。多线程update全局表的同一行记录会死锁。批量insert没问题。
  5. 拆分字段是不可修改的
  6. 拆分字段只能是一个字段,如果想按照两个字段拆分,必须新建一个冗余字段,冗余字段的值使用两个字段的值拼接而成(如大区+年月拼成zone_yyyymm字段)。
  7. 拆分算法的选择和合理性评判:按照选定的算法拆分后每个库中单表不得超过800万
  8. 能不拆的就尽量不拆。如果某个表不跟其他表关联查询,数据量又少,直接不拆分,使用单库即可。

Mycat常用的分片规则

分片枚举

通过在配置文件中配置可能的枚举id,自己配置分片,本规则适用于特定的场景,比如有些业务需要按照省份或区县来做保存,而全国省份区县固定的,这类业务使用本条规则,配置如下:

<tableRule name="sharding-by-intfile"> 
	<rule> 
		<columns>city</columns> 
		<algorithm>hash-int</algorithm> 
	</rule> 
</tableRule> 
<function name="hash-int" class="io.mycat.route.function.PartitionByFileMap"> 
	<property name="mapFile">partition-hash-int.txt</property> 
	<property name="type">1</property> 
	<property name="defaultNode">0</property> 
</function> 
partition-hash-int.txt 配置: 
北京=0
上海=1 
DEFAULT_NODE=1

上面columns 标识将要分片的表字段,algorithm 分片函数, 其中分片函数配置中,mapFile标识配置文件名称,type默认值为0,0表示Integer,非零表示String, 所有的节点配置都是从0开始,及0代表节点1

范围约定

此分片适用于,提前规划好分片字段某个范围属于哪个分片, start <= range <= end.
range start-end ,data node index

<tableRule name="auto-sharding-long"> 
	<rule> 
		<columns>id</columns> 
		<algorithm>rang-long</algorithm> 
	</rule> 
</tableRule>
<function name="rang-long" class="io.mycat.route.function.AutoPartitionByLong"> 
	<property name="mapFile">autopartition-long.txt</property> 
	<property name="defaultNode">0</property> 
</function>
autopartition-long.txt 配置:
#K=1000条记录,M=10000条记录
0-500M=0 
500M-1000M=1 
1000M-1500M=2

配置说明: mapFile代表配置文件路径 defaultNode 超过范围后的默认节点。 所有的节点配置都是从0开始,及0代表节点1,此配置非常简单,即预先制定可能的id范围到某个分片

 

固定分片hash算法

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

<tableRule name="rule1"> 
	<rule> 
		<columns>id</columns> 
		<algorithm>func1</algorithm> 
	</rule> 
</tableRule>
<function name="func1" class="io.mycat.route.function.PartitionByLong"> 
	<property name="partitionCount">2,1</property> 
	<property name="partitionLength">256,512</property> 
</function>

配置说明:  partitionCount 分片个数列表,partitionLength 分片范围列表 分区长度:默认为最大2^n=1024 ,即最大支持1024分区 约束 : count,length两个数组的长度必须是一致的。 1024 = sum((count[i]*length[i])). count和length两个向量点积恒等于1024
用法例子: 本例的分区策略:希望将数据水平分成3份,前两份各占25%,第三份占50%。(故本例非均匀分区)

// |<———————1024———————————>|

// |<—-256—>|<—-256—>|<———-512————->|

// | partition0 | partition1 | partition2 |

 

取模

此规则为对分片字段求摸运算。

<tableRule name="mod-long"> 
	<rule> 
		<columns>id</columns> 
		<algorithm>mod-long</algorithm> 
	</rule> 
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
	 <!-- how many data nodes --> 
	<property name="count">3</property> 
</function>

配置说明:  此种配置非常明确即根据id进行十进制求模预算,相比固定分片hash,此种在批量插入时可能存在批量插入单事务插入多数据分片,增大事务一致性难度。

 

按日期(天)分片

此规则为按天分片。

<tableRule name="sharding-by-date"> 
	<rule> 
		<columns>create_time</columns> 
		<algorithm>sharding-by-date</algorithm>
	</rule> 
</tableRule>
<function name="sharding-by-date" class="io.mycat.route.function.PartitionByDate">
	<property name="dateFormat">yyyy-MM-dd</property>
	<property name="sBeginDate">2018-01-01</property> 
	<property name="sEndDate">2018-01-30</property> 
	<property name="sPartionDay">10</property> 
</function>

配置说明:  dateFormat :日期格式; sBeginDate :开始日期; sEndDate:结束日期; sPartionDay :分区天数,即默认从开始日期算起,分隔10天一个分区

自然月分片

按月份列分区 ,每个自然月一个分片

<tableRule name="sharding-by-month"> 
	<rule> 
		<columns>create_time</columns> 
		<algorithm>sharding-by-month</algorithm> 
	</rule> 
</tableRule> 
<function name="sharding-by-month" class="org.opencloudb.route.function.PartitionByMonth"> 
	<property name="dateFormat">yyyy-MM-dd</property> 
	<property name="sBeginDate">2018-01-01</property> 
</function>

 

取模范围约束

此种规则是取模运算与范围约束的结合,主要为了后续数据迁移做准备,即可以自主决定取模后数据的节点分布。

<tableRule name="sharding-by-pattern"> 
	<rule> 
		<columns>user_id</columns> 
		<algorithm>sharding-by-pattern</algorithm> 
	</rule> 
</tableRule>
<function name="sharding-by-pattern" class="io.mycat.route.function.PartitionByPattern"> 
	<property name="patternValue">256</property> 
	<property name="defaultNode">2</property> 
	<property name="mapFile">partition-pattern.txt</property>
</function>
partition-pattern.txt 配置:
# id partition range start-end ,data node index 
###### first host configuration
1-32=0 
33-64=1
65-96=2 
97-128=3
######## second host configuration
129-160=4 
161-192=5 
193-224=6 
225-256=7 
0-0=7

配置说明: patternValue 即求模基数,defaoultNode 默认节点,mapFile 配置文件路径 配置文件中,1-32 即代表id%256后分布的范围,如果在1-32则在分区1,其他类推,如果id非数据,则会分配在defaoultNode 默认节点

 

应用指定

此规则是在运行阶段有应用自主决定路由到那个分片。

<tableRule name="sharding-by-substring"> 
	<rule> 
		<columns>id</columns> 
		<algorithm>sharding-by-substring</algorithm> 
	</rule> 
</tableRule>
<function name="sharding-by-substring" class="io.mycat.route.function.PartitionDirectBySubString"> 
	<property name="startIndex">0</property><!-- zero-based -->
	<property name="size">2</property> 
	<property name="partitionCount">8</property> 
	<property name="defaultPartition">0</property> 
</function>

配置说明: 此方法为直接根据字符子串(必须是数字)计算分区号(由应用传递参数,显式指定分区号)。
例如id=05-10000 在此配置中代表根据id中从startIndex=0,开始,截取siz=2位数字即05,05就是获取的分区,如果没传默认分配到defaultPartition

 

截取数字hash解析

此规则是截取字符串中的int数值hash分片。

<tableRule name="sharding-by-stringhash"> 
	<rule> 
		<columns>id</columns> 
		<algorithm>sharding-by-stringhash</algorithm> 
	</rule> 
</tableRule> 
<function name="sharding-by-stringhash" class="io.mycat.route.function.PartitionByString"> 
	<property name="partitionLength">512</property><!-- zero-based --> 
	<property name="partitionCount">2</property> 
	<property name="hashSlice">0:2</property> 
</function>

配置说明: partitionLength代表字符串hash求模基数,partitionCount分区数,
hashSlice hash预算位,即根据子字符串中int值 hash运算

/**

* “2” -> (0,2)

* “1:2” -> (1,2)

* “1:” -> (1,0)

* “-1:” -> (-1,0)

* “:-1” -> (0,-1)

* “:” -> (0,0)

*/

一致性hash

一致性hash预算有效解决了分布式数据的扩容问题。

<tableRule name="sharding-by-murmur"> 
	<rule> 
		<columns>id</columns> 
		<algorithm>murmur</algorithm> 
	</rule> 
</tableRule>
<function name="murmur" class="io.mycat.route.function.PartitionByMurmurHash"> 
	<property name="seed">0</property><!-- 默认是0--> 
	<property name="count">2</property><!-- 要分片的数据库节点数量,必须指定,否则没法分片--> 
	<property name="virtualBucketTimes">160</property>
	<!-- 一个实际的数据库节点被映射为这么多虚拟节点,默认是160倍,也就是虚拟节点数是物理节点数的160倍-->
</function>

将数据均匀分布在各个节点中。
对其进行哈希,取值在 0 ~ 232-1 闭环中定位到顺时针第一个节点,将此数据分配其中。
由于节点有限,可能取哈希分布不均。
设置虚拟节点比如160,先将哈希分布在160节点上,然后把对应的节点聚合到真实节点中。

 

 

相关文章:

  • 2021-07-29
  • 2021-12-15
  • 2022-12-23
  • 2022-12-23
  • 2021-12-21
  • 2021-07-27
  • 2021-11-14
  • 2021-12-09
猜你喜欢
  • 2021-06-21
  • 2021-10-28
  • 2021-10-05
  • 2021-11-25
  • 2021-05-23
  • 2021-06-17
  • 2021-10-25
相关资源
相似解决方案