MyCat笔记

前端之家收集整理的这篇文章主要介绍了MyCat笔记前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

Mycat是数据库中间件,就是介于数据库与应用之间,进行数据处理与交互的中间服务。

Mycat是一个开源的分布式数据库系统,用户可以将它看作是一个数据库代理,Mycat后端可以支持MysqLsql SERVER、Oracle、DB2、Postgresql等主流数据库,也支持MongoDB这种Nosql方式的存储,在最终用户看来,无论是那种存储方式,在Mycat里都是一个传统的数据库支持标准的sql语句进行数据的操作。

Mycat本身并不存储数据,数据是在后端存储的,因此数据可靠性及事务等都是后端保证的,默认端口是8066,建议对于分片表,尽量使用基础的sql,因为这样能达到最佳性能

Mycat是一个强大的数据库中间件,不仅仅可以用作读写分离、以及分表分库、容灾备份,而且可以用于多租户应用开发、云平台基础设施、让我们的架构具备很强的适应性和灵活性。借助于ELK可以对数据访问瓶颈和热点一目了解然,根据这些统计分析数据,我们可以自动或手工调整后端存储,将不同的表映射到不同的存储引擎上,而整个应用的代码一行也不用改变。

Mycat拦截用户发过来的sql语句,首先去sql语句做一些特定分析:如分片分析、路由分析、读写分离分析、缓存分析等,然后将此sql发往后端的真实数据库,并将返回结果做适当的处理,最终返回给用户

Mycat应用场景

  • 单纯的读写分离,此时配置最为简单,支持读写分离、主从切换

  • 分表分库,对于超过1000万的表进行分片,最大支持1000亿的单表分片

  • 多租户应用,每个应用一个库,但应用程序只连接mycat,从而不改造程序本身,实现多租户化

  • 报表系统,借助于mycat的分表能力,处理大规模报表的统计

  • 替代Hbase,分析大数据

  • 作为海量数据实时查询的一种简单有效方案,比如100亿条频繁查询的记录需要3秒内查询出来结果,除了基于主键的查询,还可能存在范围查询或其他属性查询,此时mycat可能是最简单有效的选择。

    mycat对数据进行分片处理之后,从原有的一个库,被切分为多个分片数据库,所有分片数据库集群构成了整个完整的数据库存储,此时,应用如果需要读取数据,就需要处理多个数据源的数据,如果没有数据库中间件,那么应用将直接面对分片集群、数据源切换、事务处理、数据聚合都需要应用直接处理,原本该是专注于业务的应用,将会花大量的工作来处理分片后的问题,最重要的是每个应用处理将是完全重复造轮子,所以有了数据库中间件,应用只需要集中与业务处理,大量的通用的数据聚合、事务、数据源切换都由中间件来处理,中间件的性能与处理能力将直接决定应用的读写性能,所以一款好的数据库中间件至关重要。

在云计算时代,数据库中间件可以以多租户的形式给一个或多个应用提供服务,每个应用访问的可能是一个独立或是共享的物理库,常见的如阿里云数据库服务器RDS。

关键概念

分片表(t_node就是属于分片表,数据按照规则被分到dn1,dn2两个分片节点上):

ER表(Entity-Relationship Model) 基于E-R关系的数据分片策略,子表的记录与所关联的父表记录存放在同一个数据分片上,即子表依赖于父表,通过表分组保证数据join不会跨库操作,表分组是解决跨分片数据join的一种很好的思路,也是数据切分规划的重要一条规则

全局表
一个真实的业务系统中,往往存在大量的类似字典表的表,这些表基本很少变动,字典表具有以下几个特性:

  • 变动不频繁
  • 数据量总体变化不大
  • 数据规模不大,很少有超过数十万条记录

Mycat通过数据冗余来解决这类表的join,即所有的分片都有一份数据拷贝,所有将字典表或符合字典特性的一些表定义为全局表
数据冗余是解决跨分片数据join的一种很好思路,也是数据切分规划的另外一条重要规则

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

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

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

全局序列号(sequence)
数据切分后,原有的关系数据库中的主键约束在分布式条件下将无法使用,因此需要引入外部机制保证数据唯一性标识,这种保证全局性的数据唯一标识的机制就是全局序列号.

多租户

  • 独立数据库 - 成本高
  • 共享数据库,隔离数据架构 - 成本和安全性折衷
  • 共享数据库,共享数据架构 - 成本最低 安全性最低

配置文件
MYCAT_HOME/conf/schema.xml 定义逻辑库、表、分片节点等内容

MYCAT_HOME/conf/rule.xml 定义分片规则
MYCAT_HOME/conf/server.xml 定义用户以及系统相关变量,如端口等

schema.xml

作为MyCat中重要的配置文件之一,管理着Mycat的逻辑库、表、分片规则、Datanode以及DataSource.

schema标签用于定义Mycat实例中的逻辑库,Mycat可以有多个逻辑库,每个逻辑库都有自己的相关配置,可以使用schema标签来划分这些不同的逻辑库,如果不配置schmea标签,所有的表配置,会属于同一个默认的逻辑库。

例:

sqlschema="false" sqlMaxLimit="100">
atanode="dn1,dn2,dn3" rule="auto-sharding-long">sqlschema="false" sqlMaxLimit="100">
atanode="dn10,dn11,dn12" rule="auto-sharding-long">

如上所示的配置就配置了两个不同的逻辑库,逻辑库的概念和MysqL数据库中的Database的概念相同。

schema标签相关属性

datanode
属性用于绑定逻辑库到某个具体的database上,如果定义了这个属性,那么这个逻辑库就不能工作在分库分表模式下了,也就是说对这个逻辑库的所有操作会直接作用到绑定的datanode上,这个schema就可以用作读写分离和主从切换。

sqlschema="false" sqlMaxLimit="100" datanode="dn1">

现在USERDB就绑定到dn1所配置的具体database上,可以直接访问这个database,此属性只能绑定到一个database上,不能绑定多个dn.

checksqlschema

当该值设置为true时,如果我们执行语句
**select * from TESTDB.travelrecord;**
则mycat会把语句修改为
**select * from travelrecord;**
即把schema的字符去掉,避免发送到后端数据库执行时报
**(ERROR 1146 (42S02):Table 'testdb.travelrecord' doesn't exist)。**
不过,即使设置该值为true,如果语句所带的是并非是schema指定的名字,例
**select * from db1.travelrecord;**
那么mycat并不会删除db1这个字段,如果没有定义该库的话,则会报错。

sqlMaxLimit
当该值设置为某个数值时,每条执行的sql,如果没有加上limit语句,mycat会自动加上
注意的是,如果运行的schema为非拆分库,那么该属性不会生效,需要手动添加limit语句。

table标签

Table标签定义了MYCAT中的逻辑表,所有需要拆分的表都要在这个标签中定义

属性 数量限制
name string (1)

name定义逻辑表的表名,这个名字就如同我们在数据库中执行create table命令指的名字一样,同一个schema标签中定义的名字必须唯一。
datanode属性
定义此逻辑表所属的datanode,该属性的值需要和datanode标签中name属性的值相互对应,如果需要定义的dn过多可以使用如下的方法减少配置

<table&gt;</table&gt;
atanode name="multipleDn" dataHost="localhost1" database="db$0-99">
atanode name="multipleDn2" dataHost="localhost1" database="db$0-99">

rule属性

属性用于指定逻辑表要使用的规则名字,规则名字在rule.xml中定义,必须与tableRule标签中name属性值一一对应。

rulerequired属性

属性用于指定表是否绑定分片规则,如果配置为true,但没有配置具体rule的话,程序会报错。

primaryKey属性

该逻辑表对应真实表的主键,例:分片的规则是使用非主键进行分片的,那么在使用主键查询的时候,就会发送查询语句到所有配置的DN上,如果使用该属性配置真实表的主键,那么mycat会缓存主键与具体DN的信息,那么再次使用非主键进行查询的时候就不会进行广播式的查询,就会直接发送语句给具体的DN.

type属性

属性定义了逻辑表的类型,目前逻辑表只有全局表和普通表两种类型

  • 全局表 global

  • 普通表 不指定该值为global的所有表

autoIncrement属性

MysqL对非自增长主键,使用last_insert_id()是不会返回结果的,只会返回0.所以只有定义了自增长主键的表才可以用last_insert_id()返回主键值。

mycat目前提供了自增长主键功能,但是如果对应的MysqL节点上数据表,没有定义auto_increment,那么在mycat层调用last_insert_id()也是不会返回结果的。

由于insert操作的时候没有带入分片键,mycat会先取下这个表对应的全局序列,然后赋值给分片键,这样才能正常的插入到数据库中,最后使用last_insert_id()才会返回插入的分片键值。使用此功能,最好配合使用数据库模式的全局序列。

needAddLimit属性

属性默认true,指定表是否需要自动的在每个语句后面加上limit限制,由于使用了分库分表,数据量有时会特别巨大,这时候执行查询语句,如果恰巧又忘记了加上数量限制的话,那么查询所有数据,会比较费时,所以mycat就自动为我们加上limit 100.

childTable标签

childTable用于定义ER分片的子表,通过标签上的属性与父表进行关联

属性名 数量限制 name属性

定义子表的表名

joinKey属性

插入子表的时候会使用这个列的值查找父表存储的数据节点

parentKey属性

属性指定的值一般为与父表建立关联关系的列名,程序首先获取joinkey的值,再通过parentKey属性指定的列名产生查询语句,通过执行该语句得到父表存储在哪个分片上,从而确定子表存储的位置。

parmaryKey属性

同table标签所描述的

needAddLimit属性

同table标签所描述的

datanode标签

atanode name="dn1" dataHost="1ch3307" database="db1">

datanode标签定义了mycat中的数据节点,也就是我们通常说的数据分片,一个datanode标签就是一个独立的数据分片。

例子中所表述的意思为:使用名字为lch3307数据库实例上的db1物理数据库,这就组成一个数据分片,最后,我们使用名字dn1标识这个分片。

datanode标签的相关属性

属性名 数量限制 name属性

定义数据节点的名字,这个名字需要是唯一的,我们需要在table标签上应用这个名字,来建立表与分片对应的关系。

dataHost属性

属性用于定义该分片属于那个数据库实例的,属性值是引用dataHost标签上定义的name属性

database属性

属性用于定义该分片属于哪个具体数据库实例上的具体库,因为这里使用两个纬度来定义分片,就是实例+具体的库,因为每个库上建立的表和结构是一样的,所以这样做就可以轻松的对表进行水平拆分。

dataHost标签

标签直接定义了具体的数据库实例,读写分离配置和心跳语句

MysqL" dbDriver="native">
select user()
    




dataHost相关属性

属性名 数量限制 name属性

唯一标识dataHost标签,供上层标签使用

maxCon属性

指定每个读写实例连接池的最大连接,也就是说,标签内嵌套的writeHost、readHost标签都会使用这个属性的值来实例化出连接池的最大连接数。

minCon属性

指定每个读写实例连接池的最小连接,初始化连接池的大小

balance属性

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

  • balance="0" 所有读操作都发送到当前可用的writeHost。

  • balance="1" 所有计操作都随机的发送到readHost。

  • balance="2" 所有读操作都随机的在writeHost、readHost上分发。

writeType属性

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

  • writeType="0" 所有写操作都发送到可用的writeHost

  • writeType="1" 所有写操作都随机的发送到readHost.

  • writeType="2" 所有写操作都随机的在writeHost、readHost上分发

dbType属性

指定后端连接的数据库类型,目前支持二进制的MysqL协议,还有其他使用JDBC连接其他的数据库.

dbDriver属性

指定连接后端数据库使用的Driver,目前可选native和JDBC.使用native的话,因这个值执行的是二进制的MysqL协议。

heartbeat标签

这个标签内指明用于和后端数据库进行心跳检查的语句,例如MysqL可以使用select user(),oracle可以用select 1 from dual等
标签还有一个connectionInitsql属性..

writeHost、readHost标签

这两个标签都指定后端数据库的相关配置给mycat,用于实例化后端连接池,唯一不同的是,writeHost指定写实例,readHost指定读实例。

在一个dataHost内可以定义多个writeHost和readHost,但是如果writeHost指定的后端数据库宕机,那么这个writeHost绑定的所有readHost都将不可用。另一方面,由于这个writeHost宕机系统会自动检测到,并切换到备用的writeHost上去。

属性名 数量限制 host属性

用于标识不同实例,一般writeHost我们使用*M1,readHost我们用*S1

url属性

native 格式 ip:port
jdbc 格式 jdbc:MysqL://ip:端口

user、password属性

存储实例的用户名和密码

server.xml

server.xml几乎保存了所有mycat需要的系统配置信息,其在代码内直接映射类为SystemConfig类。

user标签


    
    

定义登录mycat的用户和权限,property标签则是具体声明的属性值,schemas控制用户可访问的schema,同时访问多个用逗号隔开。

system标签

标签内嵌套的所有property标签都与系统配置有关

defaultsqlParser属性

由于mycat最初是使用FoundationDB的sql解析器,而后才添加的Druid的解析器,所以这个属性用来指定默认的解析器,目前可用的取值有:druidparser和fdbparser。

processors属性

这个属性主要用于指定系统可用的线程数,默认值为Runtime.getRuntime().availableProcessors()方法返回的值,主要影响processorBufferPool、processorBufferLocalPercent、processorExecutor属性。NIOProcessor的个数也是由这个属性定义的,所以调优的时候可以适当的调高此属性

processorBufferChunk属性

属性指定每次分配Socket Direct Buffer的大小,默认是4096个字节,这个属性也影响buffer pool的长度。

processorBufferPool属性

属性指定bufferPool计算比例值,由于每次执行NIO读、写操作都需要使用到buffer,系统初始化的时候会建立一定长度的buffer池来加快读、写的效率,减少建立buffer的时间。

Mycat中有两个主要的buffer池

  • BufferPool

  • ThreadLocalPool

BufferPool由ThreadLocalPool组合而成,每次从BufferPool中获取buffer都会优先获取ThreadLocalPool中的buffer,未命中之后才会去获取BufferPool中的buffer.也就是说ThreadLocalPool是作为BufferPool的二级缓存,每个线程内部自己使用的。

当然,这其中还有一些限制条件需要线程的名字是由$_开头,然而BufferPool上的buffer则是每个NIOProcessor都共享的。

默认此属性的值为:默认bufferChunkSize(4096)*processors属性*1000
BufferPool的总长度=bufferPool/bufferChunk
若bufferPool不是bufferChunk的整数倍,则总长度为前面计算得出的商+1
假设系统线程数为4,其他都为属性的默认值 ,则
bufferPool = 4096*4*1000
BufferPool的总长度: 4000 = bufferPool/4096s

processorBufferLocalPercent属性

前面提到了ThreadLocalPool,这个属性就是用来控制分配这个pool的大小用的,但其也并不是一个准确的值,也是一个比例值,默认为100.

线程缓存百分比=bufferLocalPercent/processors属性

例如,系统可以同时运行4个线程,使用默认值,则根据公式每个线程的百分比为25,最后根据这个百分比来计算出具体的ThreadLocalPool的长度公式如下
ThreadLocalPool的长度=线程缓存百分比BufferPool长度/100
假设BufferPool的长度为4000,其他保持默认值
那么最后每个线程建立的ThreadLocalPool的长度为:1000 = 25
4000/100

processorExecutor属性

属性主要用于指定NIOProcessor上共享的businessExecutor固定线程池大小。mycat在需要处理一些异步逻辑的时候会把任务提交到这个线程池中,新版中这个连接池的使用频率不是很大,可以设置一个较小的值。

sequnceHandlerType属性

指定使用MYCAT全局序列的类型,0为本地文件方式,1为数据库方式,默认是本地文件方式,主要用于测试使用。

TCP连接的相关属性

  • StandardSocketOptions.SO_RCVBUF

  • StandardSocketOptions.SO_SNDBUF

  • StandardSocketOptions.TCP_NODELAY

上面的三个属性,分别由

frontSocketSoRcvbuf 默认值 1024*1024
frontSocketSoSndbuf 默认值 4*1024*1024
frontSocketNoDelay 默认值 1

backSocketSoRcvbuf 默认值 410241024
backSocketSoSndbuf 默认值 1024*1024
backSocketNoDelay 默认值 1

各自设置前后端TCP连接参数,MYCAT在每次建立前、后端连接的时候都会使用这些参数初始化连接,可以按系统要求适当调整这些buffer的大小。

MysqL连接相关属性

初始化MysqL前后端连接的涉及到的一些属性

packetHeaderSize:指定MysqL协议中的报文头长度,默认4
maxPacketSize:指定MysqL协议可以携带的数据最大长度,默认16M
idleTimeout:指定连接的空闲超时时间,某连接在发起空闲检查下,发现距离上次使用超过了空闲时间,那么这个连接会被回收,就是被直接的关闭掉,默认30分钟
charset:连接的初始化字符集 默认utf8
txIsolation:前端连接的初始化事务隔离级别,只在初始化的时候使用,后续会根据客户端传递过来的属性对后端数据库连接进行同步,默认为REPEATED_READ.
sqlExecuteTimeout:sql执行超时的时间,mycat会检查连接上最后一次执行sql的时间,若超过这个时间则会直接关闭这连接,默认时间为300秒

周期间隔相关属性

mycat中有几个周期性的任务来异步处理一些需要的工作,这些属性在系统调优的过程中是必不可少的。

processorCheckPeriod:清理NIOProcessor上前后端空闲、超时和关闭连接的间隔时间,默认是1秒
datanodeIdleCheckPeriod:对后端连接进行空闲、超时检查的时间间隔,默认是60秒
datanodeHeartbeatPeriod:对后端所有读、写库发起心跳的间隔时间,默认是10秒

服务相关属性

服务属性主要会影响外部系统对mycat的感知

bindIp:mycat服务监听的IP地址,默认值为0.0.0.0
serverPort:定义mycat的使用端口,默认值为8066
managerPort:定义mycat的管理端口,默认值为9066

rule.xml

rule.xml里定义了我们对表进行拆分所涉及到的规则定义,我们可以灵活的对表使用不同的分片算法,或者对表使用相同的算法但具体的参数不同。此文件里面主要有tableRule和function这两个标签

tableRule标签

标签定义表规则


        id
        func1
    

name属性指定唯一的名字,用于标识不同的表规则

内嵌的rule标签则指定对物理表中的哪一列进行拆分和使用什么路由算法

columns内指定要拆分的列名字

algorithm使用function标签中的name属性,连接表规则和具体路由算法,当然,多个表规则可以连接到同一个路由算法上。

function标签



name指定算法的名字

class指定路由算法具体的类名字

property为具体算法需要用到的一些属性

MyCAT的JOIN

在分布式环境中,跨分片的表关联是最复杂,最难解决的一个问题

  • inner join 内连接

  • left join 左连接

  • right join 右连接

  • cross join 交叉连接 得到的结果是两个表的乘积,即笛卡尔积

  • full join 产生所有记录

性能建议:

  • 尽量避免使用left join和right join 而是用inner join.

  • 在使用inner join时,on会优先执行,where条件在最后执行,因此在使用过程中,条件尽可能的在on语句中判断,减少where的执行

  • 少用子查询,而用join

Mycat目前版本支持跨分片的join,主要实现的方式有四种.

  • 全局表

  • ER分片

  • catletT(人工智能)

  • ShareJoin

全局表

将字典或符合字典表特性的一些表定义为全局表,从另外一个方面,很好的解决了数据join的难题。
全局表具有如下特性

  • 全局表的插入、更新操作会实时在所有节点上执行,保持各个分片的数据一致性

  • 全局表的查询操作,只从一个节点获取

  • 全局表可以跟任何一个表进行join操作

配置

全局表配置比较简单,不用写Rule规则

需要注意的是,全局表每个分片节点上都要运行创建表的DDL语句

ER JOIN

Mycat借鉴了Newsql领域的新秀FoundationDB的设计思路,FoundationDB创新性的提出了Table Group的概念,其将子表的存储位置依赖于主表,并且物理上紧邻存放,因此彻底解决了JOIN的效率和性能问题。MYCAT根据这一思路,提出了基于E-R关系的数据分片策略,子表的记录与所关联的父表记录存放在同一个数据分片上。

配置

<table&gt;
    <childTable name="orders" joinKey="customer_id" parentKey="id"/&gt;
</table&gt;

Share Join

ShareJoin是一个简单的跨分片Join,基于HBT的方式实现

目前支持2个表的join,原理就是解析sql语句,拆分成单表的sql语句执行,然后把各个节点的数据汇集。

配置

支持任意配置的A、B表
如:A,B的datanode相同

如:A,B的datanode不同

<table&gt;</table&gt;

<table></table>

<table&gt;</table&gt;

<table></table>

catlet(人工智能)

解决跨分片的sql JOIN的问题,远比想像的复杂,而且往往无法实现高效的处理,既然如此,就依靠人工智能去解决业务系统中特定的几个必须跨分片的sql的JOIN逻辑。

select a.id,a.name,b.title from a,b where a.id=b.id

其中a在分片1,2,3上,b在4,5,6上,需要把数据全部拉到本地,执行join逻辑,具体过程如下:

EngineCtx ctx = new EngineCtx();//包含MyCat.sqlEngine
String sql=,"select a.id,a.name form a";
//在a表所在的所有分片上顺序执行下面的本地sql
ctx.executeNativesqlSequnceJob(allAnodes,new DirectDBJoinHandler());

DirectDBJoinHandler是一个回调类,负责处理sql执行过程中返回的数据包,这里的这个类,主要目的是用a表返回的ID信息,去b表上查询对应的记录,做实时的关联

DirectDBJoinHandler{
    Private HashMap rows;//Key为id,value为一行记录的Column原始Byte数组,这里是a.id,b.title这三个要输出的字段
    Public Boolean onHeader(byte[] header){
        //保存Header信息,用于从Row中获取Field字段值
    }
    Public Boolean onRowData(byte[] rowData){
        String id = getColumnAsString("id");
        //放入结果集,b.title字段未知,所以先空着
        rows.put(getColumnRawBytes("id"),rowData);
        //满1000条,发送一个查询请求
        String sql = "select b.id,b.name from b where id in (...)";
        //此sql在B的所有节点上并发执行,返回的结果直接输出到客户端
        ctx.executeNativesqlParallJob(allBNodes,sql,new MyRowOutPutDataHandler(rows));
    }
    Public Boolean onRowFinished(){
    }
    Public void onJobFinished(){
        if(ctx.allJobFinished()){
            //used total time...
        }
    }
}

最后,增加一个JOB事件监听器,这里是所有JOB完成后,往客户端发送RowEnd包,结束整个流程。

ctx.setJobEventListener(new JobEventHandler(){
    public void onJobFinished(){
        client.writeRowEndPackage()
    }
});

以上提供一个sql执行框架,完全是异步的模式执行,并且以后会提供更多高质量的API,简化分布式数据处理,比如内存结合文件的数据JOIN算法,分组算法和排序算法等。

Spack/Storm对join扩展

mycat后续的功能会引入spark和storm来做跨分片的join..

全局序列号

在实现分库分表的情况下,数据库自增主键已无法保证自增主键的全局唯一。为此,MyCat 提供了全局sequence,并且提供了 包含本地配置和数据库配置等多种实现方式。

本地文件方式

配置classpath中的sequence_conf.properties中sequence当前的值

配置方式

需server.xml配置sequnceHandlerType值为0,表示使用本地文件方式

配置sequence_conf.properties如下
GLOBAL_SEQ.HISIDS=
GLOBAL_SEQ.MINID=1001
GLOBAL_SEQ.MAXID=1000000000
GLOBAL_SEQ.CURID=1000

其中HISIDS表示使用过的历史分段(一般无特殊需要可不配置),MINID表示最小ID值,MAXID表示最大ID值,CURID表示当前ID值

使用示例

insert into table1(id,name) values(next value for MYCATSEQ_GLOBAL,'test');

优点:本地加载读取速度快,缺点:当MYCAT重新发布后,配置文件中的sequence会恢复初始值.

数据库方式

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

Sequence获取步骤

  1. 当初次使用该sequence时,根据传入的sequence名称,从数据库这张表中读取current_value和increment到mycat中,并将数据库中的current_value设置为原current_value值+increment值;

  2. mycat将读取到current_value+increment作为本次要使用的sequence值,下次使用时,自动加1,当使用increment次后,执行步骤1相同的操作。

mycat负责维护这张表,用到哪些sequence,只需要在这张表中插入一条记录即可。

配置方式

需server.xml配置sequnceHandlerType值为1,表示使用本地文件方式

//1.创建数据表
– 创建存放sequence的表
DROP TABLE IF EXISTS MYCAT_SEQUENCE;
– name sequence名称
– current_value 当前value
– increment 增长步长! 可理解为mycat在数据库中一次读取多少个sequence. 当这些用完后,下次再从数据库中读取.
CREATE TABLE MYCAT_SEQUENCE (name VARCHAR(50) NOT NULL,current_value INT NOT NULL,increment INT NOT NULL DEFAULT 100,PRIMARY KEY(name)) ENGINE=InnoDB;
– 插入一条sequence
INSERT INTO MYCAT_SEQUENCE(name,current_value,increment) VALUES (‘GLOBAL’,100000,100);

//2.创建相关function
获取当前sequence的值 (返回当前值,增量)
DROP FUNCTION IF EXISTS mycat_seq_currval;
DELIMITER
CREATE FUNCTION mycat_seq_currval(seq_name VARCHAR(50)) RETURNS varchar(64) CHARSET utf-8
DETERMINISTIC
BEGIN
DECLARE retval VARCHAR(64);
SET retval=“-999999999,null”;
SELECT concat(CAST(current_value AS CHAR),“,”,CAST(increment AS CHAR)) INTO retval FROM MYCAT_SEQUENCE WHERE name = seq_name;
RETURN retval;
END
DELIMITER;
– 设置sequence值
DROP FUNCTION IF EXISTS mycat_seq_setval;
DELIMITER
CREATE FUNCTION mycat_seq_setval(seq_name VARCHAR(50),value INTEGER) RETURNS varchar(64) CHARSET utf-8 DETERMINISTIC
BEGIN
UPDATE MYCAT_SEQUENCE
SET current_value = value
WHERE name = seq_name;
RETURN mycat_seq_currval(seq_name);
END
DELIMITER;
获取下一个sequence值
DROP FUNCTION IF EXISTS mycat_seq_nextval;
DELIMITER
CREATE FUNCTION mycat_seq_nextval(seq_name VARCHAR(50)) RETURNS varchar(64) CHARSET utf-8 DETERMINISTIC
BEGIN
UPDATE MYCAT_SEQUENCE
SET current_value = current_value + increment WHERE name = seq_name;
RETURN mycat_seq_currval(seq_name);
END
DELIMITER;

//3.sequence_db_conf.properties相关配置,指定sequence相关配置在哪个节点上
例如:
USER_SEQ=test_dn1
注意:MYCAT_SEQUENCE表和以上的3个function,需要放在同一个节点上。function请直接在具体节点的数据库上执行,如 果执行的时候报:
you might want to use the less safe log_bin_trust_function_creators variable
需要对数据库做如下设置:
windows下my.ini[MysqLd]加上log_bin_trust_function_creators=1 linux下/etc/my.cnf下my.ini[MysqLd]加上log_bin_trust_function_creators=1

修改完后,即可在MysqL数据库中执行上面的函数.
使用示例:
insert into table1(id,‘test’);

其他方式

  • 使用catelet注解方式

/*!mycat:catlet=demo.catlets.BatchGetSequence */SELECT mycat_get_seq(‘GLOBAL’,100); 

注:此方法表示获取GLOBAL的100个sequence值,例如当前GLOBAL的最大sequence值为5000,则通过此方式返回的是 5001,同时更新数据库中的BLOBAL的最大sequence值为5100.

  • 利用zookeeper方式实现

自增长主键

mycat自增长主键和返回生成主键ID的实现,mycat目前提供了自增长主键功能,但是如果对应的MysqL节点上的数据表,没有定义auto_increment,那么mycat层调用last_insert_id()也是不会返回结果的。

  • MysqL本身对非自增长主键,使用last_insert_id()是不会返回结果的,只会返回0

  • MysqL只会对定义自增长主键,可以用last_insert_id()返回主键值

正确配置方式

1.MysqL定义自增主键

CREATE TABLE table1(
‘id_’ INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,‘name_’ INT(10) UNSIGNED NOT NULL,PRIMARY KEY (‘id_’)
) ENGINE=MYISAM AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

2.mycat定义主键自增

schema.xml


sqlschema="false" sqlMaxLimit="100">
        
        
        atanode="dn1,dn2" rule="mod-long"/>
        
atanode="dn1"/> atanode name="dn1" dataHost="localhost1" database="db1"/> atanode name="dn2" dataHost="localhost1" database="db2"/> MysqL" dbDriver="native"> select user()

修改mycat对应sequence_db_conf.properties增加相应设置

TABLE1=dn1

继续在数据库中mycat_sequence表中增加TABLE1表的sequence记录

Mybatis新增记录后获取last_insert_id的示例

404_821@

MyCAT分片规则

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

类似于数据字典表适合配置为全局表

类似订单和订单明细适合配置为ER分片表
<table&gt;
    <childTable name="order_detail" primaryKey="id" joinKey="order_id" parentKey="order_id"/&gt;
</table&gt;

多对多关联

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

从会员的角度,如果需要查询会员购买的订单,那按照会员进行切分即可,但是如果要查询商户当前售出的订单,那又需要按照商户做切分,可是如果即要按会员又要按商户切分,几乎是无法实现,这类业务如何选择切分规则非常难。

目前还暂时无法很好支持这种模式下的三个表之间的关系,目前总的原则是需要从业务角度来看,关系表更偏向哪个表,即A的关系还是B的关系,来决定关系表跟从哪个方向存储。

未来Mycat版本将考虑将中间表进行双向复制,以实现A-关系以及B-关系表的双向关联查询
双向复制

主健分片 vs 非主键分片

当没有任何字段可以作为分片字段的时候,主键分片就是唯一选择,其优点是按照主键的查询最快,当采用自动增长的序列号作为主键时,还能比较均匀的将数据分片在不同的节点上。

若有某个合适的业务字段比较合适作为分片字段,则建议采用此业务字段分片,选择分片字段的条件如下

  • 尽可能的比较均匀分布数据到各个节点上

  • 该业务字段是最频繁或是最重要的查询条件

常见的除了主键之外的其他可能分片字段有订单创建时间、店铺类别或所有省等,当找到某个合适的业务字段作为分片字段以后,不必纠结于“牺牲了按主键查询记录的性能”,因为在这种情况下,mycat提供了主键到分片的内存缓存机制,热点数据按照主键查询,丝毫不损失性能

本节主要讲了如何去分片,如何选择合适分片的规则,总之尽量规避跨库join是一条最重要的原则。

MyCat常用分片规则

分片枚举

通过在配置文件中配置可能的枚举id,自己配置分片,本规则适用于特定的场景,比如有些业务需要按照省或地区来做保存。


        user_id
        hash-int
    


    
    

partition-hash-int.txt配置
10000=0
10010=1
DEFAULT_NODE=1

上面columns标识将要分片的表字段,algorithm分片函数,其中分片函数配置中,mapFile标识配置文件名称,type默认值为0,0表示Integer,非零表示String。defaultNode指默认节点,小于0表示不设置默认节点,大于等于0表示设置默认节点。

默认节点的作:枚举分片时,如果碰到不识别的枚举值,就让它路由到默认节点,如果不配置,碰到不识别的枚举值就会报错。

固定分片hash算法

本规则类似于十进制的求模运算,区别在于是二进制的操作,是取ID的二进制低10位,即id二进制&1111111111。

此算法的优点在于如果按照10进制取模运算,在连接插入1-10时候,1-10会被分到1-10个分片,增大了插入的事务控制难度,而此算法根据二进制则可能会分到连续的分片,减少插入事务控制难度。


        user_id
        func1
    


    

配置说明:

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 |
// | 共2份,故count[0]=2 | 共1份,故count[1]=1 |
int[] count = new int[] { 2,1 };
int[] length = new int[] { 256,512 };
PartitionUtil pu = new PartitionUtil(count,length);
// 下面代码演示分别以offerId字段或memberId字段根据上述分区策略拆分的分配结果 int DEFAULT_STR_HEAD_LEN = 8; // cobar默认会配置为此值
long offerId = 12345;
String memberId = "qiushuo";
// 若根据offerId分配,partNo1将等于0,即按照上述分区策略,offerId为12345时将会被分配到partition0中 int partNo1 = pu.partition(offerId);
// 若根据memberId分配,partNo2将等于2,memberId为qiushuo时将会被分到partition2中 int partNo2 = pu.partition(memberId,DEFAULT_STR_HEAD_LEN);

如果需要平均分配设置:平均分为4分片 partitionCount*partitionLength=1024



范围约定

此分片适用于,提前规划好分片字段某个范围属于哪个分片

start <= range <= end

range start-end,data node index

K=1000,M=10000.


user_id
rang-long 


 

配置说明

rang-long 函数中mapFile代表配置文件路径

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

0-500M=0 
500M-1000M=1 
1000M-1500M=2 
或
0-10000000=0 
10000001-20000000=1

求模

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


user_id
mod-long 

 

配置说明

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

按日期(天)分片


create_time
sharding-by-date 

 
 

配置说明:

columns :标识将要分片的表字段
 
algorithm :分片函数
dateFormat :日期格式
sBeginDate :开始日期
sBeginDate :结束日期
sPartionDay :分区天数,即默认从开始日期算起,分隔10天一个分区
Assert.assertEquals(true,0 == partition.calculate(“2014-01-01”)); Assert.assertEquals(true,0 == partition.calculate(“2014-01-10”)); Assert.assertEquals(true,1 == partition.calculate(“2014-01-11”)); Assert.assertEquals(true,12 == partition.calculate(“2014-05-01”));

取模范围约束

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


user_id
sharding-by-pattern 





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

配置说明:

上面columns 标识将要分片的表字段,algorithm 分片函数,patternValue 即求模基数,defaoultNode 默认节点,如果配置 了默认,则不会按照求模运算

mapFile 配置文件路径

配置文件中,1-32 即代表id%256后分布的范围,如果在1-32则在分区1,其他类推,如果id非数据,则会分配在defaoultNode 默认节点

String idVal = “0”;
Assert.assertEquals(true,7 == autoPartition.calculate(idVal)); idVal = “45a”;
Assert.assertEquals(true,2 == autoPartition.calculate(idVal));

ASCII码求模范围约束

此种规则类似于取模范围约束,此规则支持数据符字母取模。


user_id
sharding-by-prefixpattern 





partition-pattern.txt
# range start-end,data node index # ASCII
# 8-57=0-9阿拉伯数字
# 64、65-90=@、A-Z
# 97-122=a-z
###### first host configuration 1-4=0
5-8=1
9-12=2
13-16=3
###### second host configuration 17-20=4
21-24=5
25-28=6
29-32=7
0-0=7

配置说明:

上面columns 标识将要分片的表字段,prefixLength ASCII 截取的位数
mapFile 配置文件路径

配置文件中,其他类推
此种方式类似方式6只不过采取的是将列种获取前prefixLength位列所有ASCII码的和进行求模sum%patternValue,获取的值,在范围内的分片数

String idVal=“gf89f9a”;
Assert.assertEquals(true,0==autoPartition.calculate(idVal));
idVal=“8df99a”;
Assert.assertEquals(true,4==autoPartition.calculate(idVal));
idVal=“8dhdf99a”;
Assert.assertEquals(true,3==autoPartition.calculate(idVal));

应用指定

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


user_id
sharding-by-substring 






配置说明:

上面columns 标识将要分片的表字段,algorithm 分片函数方法为直接根据字符子串(必须是数字)计算分区号(由应用传递参数,显式指定分区号)。

例如id=05-100000002

在此配置中代表根据id中从startIndex=0,开始,截取siz=2位数字即05,05就是获取的分区,如果没传默认分配到 defaultPartition

字符串hash解析

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


user_id
sharding-by-stringhash 

512  


配置说明

上面columns 标识将要分片的表字段,algorithm 分片函数

函数中length代表字符串hash求模基数,count分区数,hashSlice hash预算位 即根据子字符串中int值 hash运算

hashSlice : 0 means str.length(),-1 means str.length()-1 
/**
* “2” -> (0,2)
* “1:2” -> (1,2) * “1:” -> (1,0)
* “-1:” -> (-1,0) * “:-1” -> (0,-1) * “:” -> (0,0)
*/

例子:
String idVal=null; rule.setPartitionLength("512"); rule.setPartitionCount("2"); rule.init(); rule.setHashSlice("0:2");
// idVal = "0";
// Assert.assertEquals(true,0 == rule.calculate(idVal));
// idVal = "45a";
// Assert.assertEquals(true,1 == rule.calculate(idVal));
//last 4
rule = new PartitionByString(); rule.setPartitionLength("512"); rule.setPartitionCount("2");
rule.init();
//last 4 characters
rule.setHashSlice("-4:0");
idVal = "aaaabbb0000";
Assert.assertEquals(true,0 == rule.calculate(idVal)); idVal = "aaaabbb2359";
Assert.assertEquals(true,0 == rule.calculate(idVal));

一致性hash

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


user_id
murmur 



 
 
 

按单月小时拆分

此规则是单月内按照小时拆分,最小粒度是小时,可以一天最多24个分片,最少1个片,一个月完后下月从头开始循环,每个月月尾,需要手工清理数据。


create_time
sharding-by-hour 

 

配置说明

columns: 拆分字段,字符串类型(yyyymmddHH)

splitOneDay : 一天切分的分片数

LatestMonthPartion partion = new LatestMonthPartion(); 
partion.setSplitOneDay(24);
Integer val = partion.calculate("2015020100"); 
assertTrue(val == 0);
val = partion.calculate("2015020216"); 
assertTrue(val == 40);
val = partion.calculate("2015022823"); 
assertTrue(val == 27 * 24 + 23);
Integer[] span = partion.calculateRange("2015020100","2015022823"); 

assertTrue(span.length == 27 24 + 23 + 1);
assertTrue(span[0] == 0 && span[span.length - 1] == 27
24 + 23);

span = partion.calculateRange("2015020100","2015020123");
assertTrue(span.length == 24);
assertTrue(span[0] == 0 && span[span.length - 1] == 23);

自然月分片

按月份列分区,每个自然月一个分片,格式between操作解析范例


create_time
sharding-by-month 


 

配置说明:

columns: 分片字段,字符串类型

dateFormat : 日期字符串格式

sBeginDate : 开始日期

PartitionByMonth partition = new PartitionByMonth();
partition.setDateFormat("yyyy-MM-dd"); 
partition.setsBeginDate("2014-01-01");

partition.init();
Assert.assertEquals(true,0 == partition.calculate("2014-01-01"));
Assert.assertEquals(true,0 == partition.calculate("2014-01-10"));
Assert.assertEquals(true,0 == partition.calculate("2014-01-31"));
Assert.assertEquals(true,1 == partition.calculate("2014-02-01"));
Assert.assertEquals(true,1 == partition.calculate("2014-02-28"));
Assert.assertEquals(true,2 == partition.calculate("2014-03-1"));
Assert.assertEquals(true,11 == partition.calculate("2014-12-31"));
Assert.assertEquals(true,12 == partition.calculate("2015-01-31"));
Assert.assertEquals(true,23 == partition.calculate("2015-12-31"));

权限控制

远程连接配置(读、写权限)

目前Mycat对于中间件的连接控制并没有做太复杂的控制,目前只做了中间件逻辑库级别的读写权限控制。

 
 
 

配置说明

name是应用连接中间件逻辑库的用户名

mycat 中password是应用连接中间件逻辑库的密码。

order 中是应用当前连接的逻辑库中所对应的逻辑表。schemas中可以配置一个或多个。

true 中readOnly是应用连接中间件逻辑库所具有的权限。true为只读,false为读写都有,默认为false。

多租户支持

单租户就是传统的给每个租户独立部署一套web + db 。由于租户越来越多,整个web部分的机器和运维成本都非常高,因此需 要改进到所有租户共享一套web的模式(db部分暂不改变)。
基于此需求,我们对单租户的程序做了简单的改造实现web多租户共享。具体改造如下:

1.web部分修改:

a.在用户登录时,在线程变量(ThreadLocal)中记录租户的id

b.修改jdbc的实现:在提交sql时,从ThreadLocal中获取租户id,添加sql 注释,把租户的schema 放到注释中。例如:/!mycat : schema = test_01 / sql ;

2.在db前面建立proxy层,代理所有web过来的数据库请求。proxy层是用mycat实现的,web提交的sql过来时在注释中指定 schema,proxy层根据指定的schema转发sql请求.

3.Mycat配置: mycat

partition.init();
Assert.assertEquals(true,23 == partition.calculate("2015-12-31"));
order,pay
./mycat start 启动
./mycat stop 停止
./mycat console 前台运行 
./mycat restart 重启服务 
./mycat pause 暂停
./mycat status 查看启动状态

Mycat性能测试

分片表的性能测试不同于普通单,因为它的数据是分布在几个Datahost上的,因此插入和查询,都需要特定的工具,才能做到多个节点同时负载请求,通过观察每个主机的负载,能够确定是否你的测试是合理和正确的。

猜你在找的程序笔记相关文章