Skip to content

一、概述

  • 一个彻底开源的,面向企业应用开发的大数据库集群
  • 支持事务、ACID、可以替代MySQL的加强版数据库
  • 一个可以视为MySQL集群的企业级数据库,用来替代昂贵的Oracle集群
  • 一个融合内存缓存技术、NoSQL技术、HDFS大数据的新型SQL Server
  • 结合传统数据库和新型分布式数据仓库的新一代企业级数据库产品
  • 一个新颖的数据库中间件产品

优势:

基于阿里开源的Cobar产品而研发,Cobar的稳定性、可靠性、优秀的架构和性能以及众多成熟的使用案例使得MYCAT一开始就拥有一个很好的起点,站在巨人的肩膀上,我们能看到更远。业界优秀的开源项目和创新思路被广泛融入到MYCAT的基因中,使得MYCAT在很多方面都领先于目前其他一些同类的开源项目,甚至超越某些商业产品。

MYCAT背后有一支强大的技术团队,其参与者都是5年以上资深软件工程师、架构师、DBA等,优秀的技术团队保证了MYCAT的产品质量。

MYCAT并不依托于任何一个商业公司,因此不像某些开源项目,将一些重要的特性封闭在其商业产品中,使得开源项目成了一个摆设。

关键特性

  • 支持SQL92标准
  • 支持MySQL、Oracle、DB2、SQL Server、PostgreSQL等DB的常见SQL语法
  • 遵守Mysql原生协议,跨语言,跨平台,跨数据库的通用中间件代理。
  • 基于心跳的自动故障切换,支持读写分离,支持MySQL主从,以及galera cluster集群。
  • 支持Galera for MySQL集群,Percona Cluster或者MariaDB cluster
  • 基于Nio实现,有效管理线程,解决高并发问题。
  • 支持数据的多片自动路由与聚合,支持sum,count,max等常用的聚合函数,支持跨库分页。
  • 支持单库内部任意join,支持跨库2表join,甚至基于caltlet的多表join。
  • 支持通过全局表,ER关系的分片策略,实现了高效的多表join查询。
  • 支持多租户方案。
  • 支持分布式事务(弱xa)。
  • 支持XA分布式事务(1.6.5)。
  • 支持全局序列号,解决分布式下的主键生成问题。
  • 分片规则丰富,插件化开发,易于扩展。
  • 强大的web,命令行监控。
  • 支持前端作为MySQL通用代理,后端JDBC方式支持Oracle、DB2、SQL
  • Server 、 mongodb 、巨杉。
  • 支持密码加密
  • 支持服务降级
  • 支持IP白名单
  • 支持SQL黑名单、sql注入攻击拦截
  • 支持prepare预编译指令(1.6)
  • 支持非堆内存(Direct Memory)聚合计算(1.6)
  • 支持PostgreSQL的native协议(1.6)
  • 支持mysql和oracle存储过程,out参数、多结果集返回(1.6)
  • 支持zookeeper协调主从切换、zk序列、配置zk化(1.6)
  • 支持库内分表(1.6)
  • 集群基于ZooKeeper管理,在线升级,扩容,智能优化,大数据处理(2.0开发版)。

1.1 引用场景

1). 高可用性与MySQL读写分离

高可用:利用MyCat可以轻松实现热备份,当一台服务器停机时,可以由集群中的另一台服务器自动接管业务,无需人工干预,从而保证高可用。

读写分离:通过MySQL数据库的binlog日志完成主从复制,并可以通过MyCat轻松实现读写分离,实现insert、update、delete走主库,而在select时走从库,从而缓解单台服务器的访问压力。

2). 业务数据分级存储保障

企业的数据量总是无休止的增长,这些数据的格式不一样,访问效率不一样,重要性也不一样。可以针对不同级别的数据,采用不同的存储设备,通过分级存储管理软件实现数据客体在存储设备之间自动迁移及自动访问切换。

3). 大表水平拆分,集群并行计算

数据切分是MyCat的核心功能,是指通过某种特定的条件,将存放在同一个数据库的数据,分散存储在多个数据库中,以达到分散单台设备负载的效果。当数据库量超过800万行且需要做分片时,就可以考虑使用MyCat实现数据切分。

4). 数据库路由器

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

5). 整合多种数据源

当一个项目中使用了多个数据库(Oracle,MySQL,SQL Server,PostgreSQL),并配置了多个数据源,操作起来就比较烦锁,这时就可以使用MyCat进行整合,最终我们的应用程序只需要访问一个数据源即可。

1.2 相关概念

分片

简单来说,就是指通过某种特定的条件,将我们存放在同一个数据库中的数据分散存放到多个数据库(主机)上面,以达到分散单台设备负载的效果。 数据的切分(Sharding)根据其切分规则的类型,可以分为两种切分模式。

1). 一种是按照不同的表(或者Schema)来切分到不同的数据库(主机)之上,这种切分可以称之为数据的垂直(纵向)切分。

2). 另外一种则是根据表中的数据的逻辑关系,将同一个表中的数据按照某种条件拆分到多台数据库(主机)上面,这种切分称之为数据的水平(横向)切分。

逻辑库(schema)

MyCat是一个数据库中间件,通常对实际应用来说,并不需要知道中间件的存在,业务开发人员只需要知道数据库的概念,所以数据库中间件可以被看做是一个或多个数据库集群构成的逻辑库。

逻辑表(table)

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

1). 分片表

是指那些原有的很大数据的表,需要切分到多个数据库的表,这样,每个分片都有一部分数据,所有分片构成了完整的数据。 总而言之就是需要进行分片的表。如 :tb_order 表是一个分片表, 数据按照规则被切分到dn1、dn2两个节点。

2). 非分片表

一个数据库中并不是所有的表都很大,某些表是可以不用进行切分的,非分片是相对分片表来说的,就是那些不需要进行数据切分的表。如: tb_city是非分片表 , 数据只存于其中的一个节点 dn1 上。

3). ER表

关系型数据库是基于实体关系模型(Entity Relationship Model)的, MyCat中的ER表便来源于此。 MyCat提出了基于ER关系的数据分片策略 , 字表的记录与其所关联的父表的记录存放在同一个数据分片中, 通过表分组(Table Group)保证数据关联查询不会跨库操作。

4). 全局表

在一个大型的项目中,会存在一部分字典表(码表) , 在其中存储的是项目中的一些基础的数据 , 而这些基础的数据 , 数据量都不大 , 在各个业务表中可能都存在关联 。当业务表由于数据量大而分片后 , 业务表与附属的数据字典表之间的关联查询就变成了比较棘手的问题 , 在MyCat中可以通过数据冗余来解决这类表的关联查询 , 即所有分片都复制这一份数据(数据字典表),因此可以把这些冗余数据的表定义为全局表。

分片节点(dataNode)

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

节点主机(dataHost)

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

分片规则(rule)

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

1.3 原理

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

user表被分为三个分片节点dn1、dn2、dn3, 他们分布式在三个MySQL Server(dataHost)上,因此可以使用1-N台服务器来分片,分片规则(sharding rule)为典型的字符串枚举分片规则, 一个规则的定义是分片字段+分片函数。这里的分片字段为 status,分片函数则为字符串枚举方式。

MyCat收到一条SQL语句时,首先解析SQL语句涉及到的表,接着查看此表的定义,如果该表存在分片规则,则获取SQL语句里分片字段的值,并匹配分片函数,得到该SQL语句对应的分片列表,然后将SQL语句发送到相应的分片去执行,最后处理所有分片返回的数据并返回给客户端。以"select * from user where status='0'" 为例, 查找 status='0' ,按照分片函数, '0' 值存放在dn1,于是SQL语句被发送到第一个节点中执行, 然后再将查询的结果返回给用户。

如果发送的SQL语句为 "select * from user where status in ('0','1')" , 那么SQL语句会被发送到dn1,dn2对应的主机上执行, 然后将结果集合并后输出给用户。

二、配置文件

2.1 server.xml

system 标签

属性取值含义
charsetutf8设置Mycat的字符集, 字符集需要与MySQL的字符集保持一致
nonePasswordLogin0,10为需要密码登陆、1为不需要密码登陆 ,默认为0,设置为1则需要指定默认账户
useHandshakeV100,1使用该选项主要的目的是为了能够兼容高版本的jdbc驱动, 是否采用HandshakeV10Packet来与client进行通信, 1:是, 0:否
useSqlStat0,1开启SQL实时统计, 1 为开启 , 0 为关闭 ;
开启之后, MyCat会自动统计SQL语句的执行情况 ;
mysql -h 127.0.0.1 -P 9066 -u root -p
查看MyCat执行的SQL, 执行效率比较低的SQL , SQL的整体执行情况、读写比例等 ;
show @@sql ; show @@sql.slow ; show @@sql.sum ;
useGlobleTableCheck0,1是否开启全局表的一致性检测。1为开启 ,0为关闭 。
sqlExecuteTimeout1000SQL语句执行的超时时间 , 单位为 s ;
sequnceHandlerType0,1,2用来指定Mycat全局序列类型,0 为本地文件,1 为数据库方式,2 为时间戳列方式,默认使用本地文件方式,文件方式主要用于测试
sequnceHandlerPattern正则表达式必须带有MYCATSEQ_或者 mycatseq_进入序列匹配流程 注意MYCATSEQ_有空格的情况
subqueryRelationshipChecktrue,false子查询中存在关联查询的情况下,检查关联字段中是否有分片字段 .默认 false
useCompression0,1开启mysql压缩协议 , 0 : 关闭, 1 : 开启
fakeMySQLVersion5.5,5.6设置模拟的MySQL版本号
defaultSqlParser由于MyCat的最初版本使用了FoundationDB的SQL解析器, 在MyCat1.3后增加了Druid解析器, 所以要设置defaultSqlParser属性来指定默认的解析器; 解析器有两个 : druidparser 和 fdbparser, 在MyCat1.4之后,默认是druidparser, fdbparser已经废除了
processors1,2....指定系统可用的线程数量, 默认值为CPU核心 x 每个核心运行线程数量; processors 会影响processorBufferPool, processorBufferLocalPercent, processorExecutor属性, 所有, 在性能调优时, 可以适当地修改processors值
processorBufferChunk指定每次分配Socket Direct Buffer默认值为4096字节, 也会影响BufferPool长度, 如果一次性获取字节过多而导致buffer不够用, 则会出现警告, 可以调大该值
processorExecutor指定NIOProcessor上共享 businessExecutor固定线程池的大小; MyCat把异步任务交给 businessExecutor线程池中, 在新版本的MyCat中这个连接池使用频次不高, 可以适当地把该值调小
packetHeaderSize指定MySQL协议中的报文头长度, 默认4个字节
maxPacketSize指定MySQL协议可以携带的数据最大大小, 默认值为16M
idleTimeout30指定连接的空闲时间的超时长度;如果超时,将关闭资源并回收, 默认30分钟
txIsolation1,2,3,4初始化前端连接的事务隔离级别,默认为 REPEATED_READ , 对应数字为3
READ_UNCOMMITED=1;
READ_COMMITTED=2;
REPEATED_READ=3;
SERIALIZABLE=4;
sqlExecuteTimeout300执行SQL的超时时间, 如果SQL语句执行超时,将关闭连接; 默认300秒;
serverPort8066定义MyCat的使用端口, 默认8066
managerPort9066定义MyCat的管理端口, 默认9066

user 标签

xml
<user name="root" defaultAccount="true">
    <property name="password">123456</property>
    <property name="schemas">ITCAST</property>
    <property name="readOnly">true</property>
    <property name="benchmark">1000</property>
    <property name="usingDecrypt">0</property>
    
    <!-- 表级 DML 权限设置 -->
    <!-- 		
    <privileges check="false">
        <schema name="TESTDB" dml="0110" >
            <table name="tb01" dml="0000"></table>
            <table name="tb02" dml="1111"></table>
        </schema>
    </privileges>		
    -->
</user>

user标签主要用于定义登录MyCat的用户和权限 :

1). <user name="root" defaultAccount="true"> : name 属性用于声明用户名 ;

2). <property name="password">123456\</property> : 指定该用户名访问MyCat的密码 ;

3). <property name="schemas">ITCAST\</property> : 能够访问的逻辑库, 多个的话, 使用 "," 分割

4). <property name="readOnly">true\</property> : 是否只读

5). <property name="benchmark">11111\</property> : 指定前端的整体连接数量 , 0 或不设置表示不限制

6). <property name="usingDecrypt">0\</property> : 是否对密码加密默认 0 否 , 1 是

java -cp Mycat-server-1.6.7.3-release.jar io.mycat.util.DecryptUtil 0:root:123456

7). <privileges check="false">

A. 对用户的 schema 及 下级的 table 进行精细化的 DML 权限控制;

B. privileges 节点中的 check 属性是用 于标识是否开启 DML 权限检查, 默认 false 标识不检查,当然 privileges 节点不配置,等同 check=false, 由于 Mycat 一个用户的 schemas 属性可配置多个 schema ,所以 privileges 的下级节点 schema 节点同样 可配置多个,对多库多表进行细粒度的 DML 权限控制;

C. 权限修饰符四位数字(0000 - 1111),对应的操作是 IUSD ( 增,改,查,删 )。同时配置了库跟表的权限,就近原则。以表权限为准。

firewall 标签

firewall标签用来定义防火墙;firewall下whitehost标签用来定义 IP白名单 ,blacklist用来定义 SQL黑名单。

xml
<firewall>
    <!-- 白名单配置 -->
    <whitehost>
        <host user="root" host="127.0.0.1"></host>
    </whitehost>
    <!-- 黑名单配置 -->
    <blacklist check="true">
        <property name="selelctAllow">false</property>
    </blacklist>
</firewall>

黑名单拦截明细配置:

配置项缺省值描述
selelctAllowtrue是否允许执行 SELECT 语句
selectAllColumnAllowtrue是否允许执行 SELECT * FROM T 这样的语句。如果设置为 false,不允许执行 select * from t,但可以select * from (select id, name from t) a。这个选项是防御程序通过调用 select * 获得数据表的结构信息。
selectIntoAllowtrueSELECT 查询中是否允许 INTO 字句
deleteAllowtrue是否允许执行 DELETE 语句
updateAllowtrue是否允许执行 UPDATE 语句
insertAllowtrue是否允许执行 INSERT 语句
replaceAllowtrue是否允许执行 REPLACE 语句
mergeAllowtrue是否允许执行 MERGE 语句,这个只在 Oracle 中有用
callAllowtrue是否允许通过 jdbc 的 call 语法调用存储过程
setAllowtrue是否允许使用 SET 语法
truncateAllowtruetruncate 语句是危险,缺省打开,若需要自行关闭
createTableAllowtrue是否允许创建表
alterTableAllowtrue是否允许执行 Alter Table 语句
dropTableAllowtrue是否允许修改表
commentAllowfalse是否允许语句中存在注释,Oracle 的用户不用担心,Wall 能够识别 hints和注释的区别
noneBaseStatementAllowfalse是否允许非以上基本语句的其他语句,缺省关闭,通过这个选项就能够屏蔽 DDL。
multiStatementAllowfalse是否允许一次执行多条语句,缺省关闭
useAllowtrue是否允许执行 mysql 的 use 语句,缺省打开
describeAllowtrue是否允许执行 mysql 的 describe 语句,缺省打开
showAllowtrue是否允许执行 mysql 的 show 语句,缺省打开
commitAllowtrue是否允许执行 commit 操作
rollbackAllowtrue是否允许执行 roll back 操作
拦截配置-永真条件
selectWhereAlwayTrueChecktrue检查 SELECT 语句的 WHERE 子句是否是一个永真条件
selectHavingAlwayTrueChecktrue检查 SELECT 语句的 HAVING 子句是否是一个永真条件
deleteWhereAlwayTrueChecktrue检查 DELETE 语句的 WHERE 子句是否是一个永真条件
deleteWhereNoneCheckfalse检查 DELETE 语句是否无 where 条件,这是有风险的,但不是 SQL 注入类型的风险
updateWhereAlayTrueChecktrue检查 UPDATE 语句的 WHERE 子句是否是一个永真条件
updateWhereNoneCheckfalse检查 UPDATE 语句是否无 where 条件,这是有风险的,但不是SQL 注入类型的风险
conditionAndAlwayTrueAllowfalse检查查询条件(WHERE/HAVING 子句)中是否包含 AND 永真条件
conditionAndAlwayFalseAllowfalse检查查询条件(WHERE/HAVING 子句)中是否包含 AND 永假条件
conditionLikeTrueAllowtrue检查查询条件(WHERE/HAVING 子句)中是否包含 LIKE 永真条件
其他拦截配置
selectIntoOutfileAllowfalseSELECT ... INTO OUTFILE 是否允许,这个是 mysql 注入攻击的常见手段,缺省是禁止的
selectUnionChecktrue检测 SELECT UNION
selectMinusChecktrue检测 SELECT MINUS
selectExceptChecktrue检测 SELECT EXCEPT
selectIntersectChecktrue检测 SELECT INTERSECT
mustParameterizedfalse是否必须参数化,如果为 True,则不允许类似 WHERE ID = 1 这种不参数化的 SQL
strictSyntaxChecktrue是否进行严格的语法检测,Druid SQL Parser 在某些场景不能覆盖所有的SQL 语法,出现解析 SQL 出错,可以临时把这个选项设置为 false,同时把 SQL 反馈给 Druid 的开发者。
conditionOpXorAllowfalse查询条件中是否允许有 XOR 条件。XOR 不常用,很难判断永真或者永假,缺省不允许。
conditionOpBitwseAllowtrue查询条件中是否允许有"&"、"~"、"|"、"^"运算符。
conditionDoubleConstAllowfalse查询条件中是否允许连续两个常量运算表达式
minusAllowtrue是否允许 SELECT * FROM A MINUS SELECT * FROM B 这样的语句
intersectAllowtrue是否允许 SELECT * FROM A INTERSECT SELECT * FROM B 这样的语句
constArithmeticAllowtrue拦截常量运算的条件,比如说 WHERE FID = 3 - 1,其中"3 - 1"是常量运算表达式。
limitZeroAllowfalse是否允许 limit 0 这样的语句
禁用对象检测配置
tableChecktrue检测是否使用了禁用的表
schemaChecktrue检测是否使用了禁用的 Schema
functionChecktrue检测是否使用了禁用的函数
objectChecktrue检测是否使用了“禁用对对象”
variantChecktrue检测是否使用了“禁用的变量”
readOnlyTables指定的表只读,不能够在 SELECT INTO、DELETE、UPDATE、INSERT、MERGE 中作为"被修改表"出现

2.2 schema.xml

schema.xml 作为MyCat中最重要的配置文件之一 , 涵盖了MyCat的逻辑库 、 表 、 分片规则、分片节点及数据源的配置。

schema 标签

xml
<schema name="ITCAST" checkSQLschema="false" sqlMaxLimit="100">
	<table name="TB_TEST" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
</schema>

schema 标签用于定义 MyCat实例中的逻辑库 , 一个MyCat实例中, 可以有多个逻辑库 , 可以通过 schema 标签来划分不同的逻辑库。MyCat中的逻辑库的概念 , 等同于MySQL中的database概念 , 需要操作某个逻辑库下的表时, 也需要切换逻辑库:

use ITCAST;

属性

schema 标签的属性如下 :

1). name

指定逻辑库的库名 , 可以自己定义任何字符串 ;

2). checkSQLschema

取值为 true / false ;

如果设置为true时 , 如果执行的语句为 "select * from ITCAST.TB_TEST;" , 则MyCat会自动把schema字符去掉, 把SQL语句修改为 "select * from TB_TEST;" 可以避免SQL发送到后端数据库执行时, 报table不存在的异常 。

不过当在编写SQL语句时, 指定了一个不存在schema, MyCat是不会帮我们自动去除的 ,这个时候数据库就会报错, 所以在编写SQL语句时,最好不要加逻辑库的库名, 直接查询表即可。

3). sqlMaxLimit

当该属性设置为某个数值时,每次执行的SQL语句如果没有加上limit语句, MyCat也会自动在limit语句后面加上对应的数值 。也就是说, 如果设置了该值为100,则执行 select * from TB_TEST 与 select * from TB_TEST limit 100 是相同的效果 。

所以在正常的使用中, 建立设置该值 , 这样就可以避免每次有过多的数据返回。

子标签table

table 标签定义了MyCat中逻辑库schema下的逻辑表 , 所有需要拆分的表都需要在table标签中定义 。

xml
<table name="TB_TEST" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />

1). name

定义逻辑表的表名 , 在该逻辑库下必须唯一。

2). dataNode

定义的逻辑表所属的dataNode , 该属性需要与dataNode标签中的name属性的值对应。 如果一张表拆分的数据,存储在多个数据节点上,多个节点的名称使用","分隔 。

3). rule

该属性用于指定逻辑表的分片规则的名字, 规则的名字是在rule.xml文件中定义的, 必须与tableRule标签中name属性对应。

4). ruleRequired

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

5). primaryKey

逻辑表对应真实表的主键

如: 分片规则是使用主键进行分片, 使用主键进行查询时, 就会发送查询语句到配置的所有的datanode上; 如果使用该属性配置真实表的主键, 那么MyCat会缓存主键与具体datanode的信息, 再次使用主键查询就不会进行广播式查询了, 而是直接将SQL发送给具体的datanode。

6). type

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

全局表:type的值是 global , 代表 全局表 。

普通表:无

7). autoIncrement

mysql对非自增长主键,使用last_insert_id() 是不会返回结果的,只会返回0。所以,只有定义了自增长主键的表,才可以用last_insert_id()返回主键值。 mycat提供了自增长主键功能,但是对应的mysql节点上数据表,没有auto_increment,那么在mycat层调用last_insert_id()也是不会返回结果的。

如果使用这个功能, 则最好配合数据库模式的全局序列。使用 autoIncrement="true" 指定该表使用自增长主键,这样MyCat才不会抛出 "分片键找不到" 的异常。 autoIncrement的默认值为 false。

8). needAddLimit

指定表是否需要自动在每个语句的后面加上limit限制, 默认为true。

dataNode 标签

xml
<dataNode name="dn1" dataHost="host1" database="db1" />

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

具体的属性 :

属性含义描述
name数据节点的名称需要唯一 ; 在table标签中会引用这个名字, 标识表与分片的对应关系
dataHost数据库实例主机名称引用自 dataHost 标签中name属性
database定义分片所属的数据库

dataHost 标签

xml
<dataHost name="host1" maxCon="1000" minCon="10" balance="0"
          writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
    <heartbeat>select user()</heartbeat>
    <writeHost host="hostM1" url="192.168.192.147:3306" user="root" password="itcast"></writeHost>
</dataHost>

该标签在MyCat逻辑库中作为底层标签存在, 直接定义了具体的数据库实例、读写分离、心跳语句。

属性

属性含义描述
name数据节点名称唯一标识, 供上层标签使用
maxCon最大连接数内部的writeHost、readHost都会使用这个属性
minCon最小连接数内部的writeHost、readHost初始化连接池的大小
balance负载均衡类型取值0,1,2,3 ; 后面章节会详细介绍;
writeType写操作分发方式0 : 写操作都转发到第1台writeHost, writeHost1挂了, 会切换到writeHost2上;
1 : 所有的写操作都随机地发送到配置的writeHost上 ;
dbType后端数据库类型mysql, mongodb , oracle
dbDriver数据库驱动指定连接后端数据库的驱动,目前可选值有 native和JDBC。native执行的是二进制的MySQL协议,可以使用MySQL和MariaDB。其他类型数据库需要使用JDBC(需要在MyCat/lib目录下加入驱动jar)
switchType数据库切换策略取值 -1,1,2,3 ; 后面章节会详细介绍;

子标签heartbeat

配置MyCat与后端数据库的心跳,用于检测后端数据库的状态。heartbeat用于配置心跳检查语句。例如 : MySQL中可以使用 select user(), Oracle中可以使用 select 1 from dual等。

子标签writeHost、readHost

指定后端数据库的相关配置, 用于实例化后端连接池。 writeHost指定写实例, readHost指定读实例。

在一个dataHost中可以定义多个writeHost和readHost。但是,如果writeHost指定的后端数据库宕机, 那么这个writeHost绑定的所有readHost也将不可用。

属性:

属性名含义取值
host实例主机标识对于writeHost一般使用 *M1;对于readHost,一般使用 *S1;
url后端数据库连接地址如果是native,一般为 ip:port ; 如果是JDBC, 一般为jdbc:mysql://ip:port/
user数据库用户名root
password数据库密码itcast
weight权重在readHost中作为读节点权重
usingDecrypt密码加密默认 0 否 , 1 是

2.3 rule.xml

rule.xml中定义所有拆分表的规则, 在使用过程中可以灵活的使用分片算法, 或者对同一个分片算法使用不同的参数, 它让分片过程可配置化。

tableRule标签

xml
<tableRule name="auto-sharding-long">
    <rule>
        <columns>id</columns>
        <algorithm>rang-long</algorithm>
    </rule>
</tableRule>

A. name : 指定分片算法的名称

B. rule : 定义分片算法的具体内容

C. columns : 指定对应的表中用于分片的列名

D. algorithm : 对应function中指定的算法名称

Function标签

xml
<function name="rang-long" class="io.mycat.route.function.AutoPartitionByLong">
	<property name="mapFile">autopartition-long.txt</property>
</function>

A. name : 指定算法名称, 该文件中唯一

B. class : 指定算法的具体类

C. property : 根据算法的要求执行

2.4 sequence 配置文件

在分库分表的情况下 , 原有的自增主键已无法满足在集群中全局唯一的主键 ,因此, MyCat中提供了全局sequence来实现主键 , 并保证全局唯一。那么在MyCat的配置文件 sequence_conf.properties 中就配置的是序列的相关配置。

主要包含以下几种形式:

1). 本地文件方式

2). 数据库方式

3). 本地时间戳方式

4). 其他方式

5). 自增长主键

三、基础知识

3.1 主从复制

主从复制原理

复制是指将主数据库的DDL 和 DML 操作通过二进制日志传到从库服务器中,然后在从库上对这些日志重新执行(也叫重做),从而使得从库和主库的数据保持同步。

MySQL支持一台主库同时向多台从库进行复制, 从库同时也可以作为其他从服务器的主库,实现链状复制。

复制分成三步:

  • Master 主库在事务提交时,会把数据变更作为时间 Events 记录在二进制日志文件 Binlog 中。

  • 主库推送二进制日志文件 Binlog 中的日志事件到从库的中继日志 Relay Log 。

  • slave重做中继日志中的事件,将改变反映它自己的数据。

MySQL 复制的优点:

  • 主库出现问题,可以快速切换到从库提供服务。
  • 可以在从库上执行查询操作,从主库中更新,实现读写分离,降低主库的访问压力。
  • 可以在从库中执行备份,以避免备份期间影响主库的服务。

读写分离原理

读写分离,简单地说是把对数据库的读和写操作分开,以对应不同的数据库服务器。主数据库提供写操作,从数据库提供读操作,这样能有效地减轻单台数据库的压力。

通过MyCat即可轻易实现上述功能,不仅可以支持MySQL,也可以支持Oracle和SQL Server。

MyCat控制后台数据库的读写分离和负载均衡由schema.xml文件datahost标签的balance属性控制。

主机端口容器名称角色
192.168.1.183306percona-master01master
192.168.1.183306percona-slave01slave

server.xml:

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:server SYSTEM "server.dtd">
<mycat:server xmlns:mycat="http://io.mycat/">
<system>
<property name="nonePasswordLogin">0</property>
<property name="useHandshakeV10">1</property>
<property name="useSqlStat">0</property>
<property name="useGlobleTableCheck">0</property>
<property name="sequnceHandlerType">2</property>
<property name="subqueryRelationshipCheck">false</property>
<property name="processorBufferPoolType">0</property>
<property name="handleDistributedTransactions">0</property>
<property name="useOffHeapForMerge">1</property>
<property name="memoryPageSize">64k</property>
<property name="spillsFileBufferSize">1k</property>
<property name="useStreamOutput">0</property>
<property name="systemReserveMemorySize">384m</property>
<property name="useZKSwitch">false</property>
</system>
<!--这里是设置的itcast用户和虚拟逻辑库-->
<user name="itcast" defaultAccount="true">
<property name="password">itcast123</property>
<property name="schemas">itcast</property>
</user>
</mycat:server>

schema.xml:

xml
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!--配置数据表-->
<schema name="itcast" checkSQLschema="false" sqlMaxLimit="100">
<table name="tb_ad" dataNode="dn1" rule="mod-long" />
</schema>
<!--配置分片关系-->
<dataNode name="dn1" dataHost="cluster1" database="itcast" />
<!--配置连接信息-->
<dataHost name="cluster1" maxCon="1000" minCon="10" balance="3"
writeType="1" dbType="mysql" dbDriver="native" switchType="1"
slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="W1" url="192.168.1.18:3306" user="root"
password="root">
<readHost host="W1R1" url="192.168.1.18:3307" user="root"
password="root" />
</writeHost>
</dataHost>
</mycat:schema>

checkSQLschema 当该值设置为true时, 如果我们执行语句"select * from test01.user ;" 语句时, MyCat则会把schema字符去掉 , 可以避免后端数据库执行时报错 ;

balance属性说明:

负载均衡类型,目前的取值有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 不负担读压 力, 注意balance=3 只在1.4 及其以后版本有,1.3 没有。

rule.xml:

<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<property name="count">1</property>
</function>

3.2 数据分片

垂直拆分

按照不同的表(或者Schema)来切分到不同的数据库(主机)之上,这种切分可以称之为数据的垂直(纵向)切分。

mycat不支持SQL语句涉及到跨域的join操作 ;

schema.xml的配置

xml
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
	
	<schema name="ITCAST_DB" checkSQLschema="false" sqlMaxLimit="100">
		<table name="tb_areas_city" dataNode="dn1" primaryKey="id" />
		<table name="tb_areas_provinces" dataNode="dn1" primaryKey="id" />
		<table name="tb_areas_region" dataNode="dn1" primaryKey="id" />
		<table name="tb_user" dataNode="dn1" primaryKey="id" />
		<table name="tb_user_address" dataNode="dn1" primaryKey="id" />
		
		<table name="tb_goods_base" dataNode="dn2" primaryKey="id" />
		<table name="tb_goods_desc" dataNode="dn2" primaryKey="goods_id" />
		<table name="tb_goods_item_cat" dataNode="dn2" primaryKey="id" />
		
		<table name="tb_order_item" dataNode="dn3" primaryKey="id" />
		<table name="tb_order_master" dataNode="dn3" primaryKey="order_id" />
		<table name="tb_order_pay_log" dataNode="dn3" primaryKey="out_trade_no" />
	</schema>
	
	
	<dataNode name="dn1" dataHost="host1" database="user_db" />
	<dataNode name="dn2" dataHost="host2" database="goods_db" />
	<dataNode name="dn3" dataHost="host3" database="order_db" />
	
    
	<dataHost name="host1" maxCon="1000" minCon="10" balance="0"
		writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
		<heartbeat>select user()</heartbeat>
		<writeHost host="hostM1" url="192.168.192.157:3306" user="root" password="itcast"></writeHost>
	</dataHost>	
    
    <dataHost name="host2" maxCon="1000" minCon="10" balance="0"
		writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
		<heartbeat>select user()</heartbeat>
		<writeHost host="hostM2" url="192.168.192.158:3306" user="root" password="itcast"></writeHost>
	</dataHost>	
    
    <dataHost name="host3" maxCon="1000" minCon="10" balance="0"
		writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
		<heartbeat>select user()</heartbeat>
		<writeHost host="hostM3" url="192.168.192.159:3306" user="root" password="itcast"></writeHost>
	</dataHost>	
    
</mycat:schema>

server.xml的配置

xml
<user name="root" defaultAccount="true">
    <property name="password">123456</property>
    <property name="schemas">ITCAST_DB</property>
</user>

<user name="test">
    <property name="password">123456</property>
    <property name="schemas">ITCAST_DB</property>
</user>

<user name="user">
    <property name="password">123456</property>
    <property name="schemas">ITCAST_DB</property>
    <property name="readOnly">true</property>
</user>

水平拆分

MySQL集群1:

主机端口容器名称角色
192.168.1.183306percona-master01master
192.168.1.183306percona-slave01slave

MySQL集群2:

主机端口容器名称角色
192.168.1.183316percona-master02master
192.168.1.183317percona-slave02slave

配置master

#搭建master
#创建目录
mkdir /data/mysql/master02
cd /data/mysql/master02
mkdir conf data
chmod 777 * -R
#创建配置文件
cd /data/mysql/master02/conf
vim my.cnf
#输入如下内容
[mysqld]
log-bin=mysql-bin #开启二进制日志
server-id=1 #服务id,不可重复
sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'

#创建容器
docker create --name percona-master02 -v /data/mysql/master02/data:/var/lib/mysql -v
/data/mysql/master02/conf:/etc/my.cnf.d -p 3316:3306 -e MYSQL_ROOT_PASSWORD=root percona:5.7.23
#启动
docker start percona-master02 && docker logs -f percona-master02
#创建同步账户以及授权
create user 'itcast'@'%' identified by 'itcast';
grant replication slave on *.* to 'itcast'@'%';
flush privileges;
#查看master状态
show master status;

配置slave

#搭建从库
#创建目录
mkdir /data/mysql/slave02
cd /data/mysql/slave02
mkdir conf data
chmod 777 * -R
#创建配置文件
cd /data/mysql/slave02/conf
vim my.cnf
#输入如下内容
[mysqld]
server-id=2 #服务id,不可重复
sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO
,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'
#创建容器
docker create --name percona-slave02 -v /data/mysql/slave02/data:/var/lib/mysql -v
/data/mysql/slave02/conf:/etc/my.cnf.d -p 3317:3306 -e MYSQL_ROOT_PASSWORD=root
percona:5.7.23
#启动
docker start percona-slave02 && docker logs -f percona-slave02
#设置master相关信息
CHANGE MASTER TO
master_host='192.168.1.18',
master_user='itcast',
master_password='itcast',
master_port=3316,
master_log_file='xxxxxx',
master_log_pos=xxxx;

#启动同步
start slave;
#查看master状态
show slave status;

配置MyCat

schema.xml:

xml
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!--配置数据表-->
<schema name="itcast" checkSQLschema="false" sqlMaxLimit="100">
<table name="tb_ad" dataNode="dn1,dn2" rule="mod-long" />
</schema>
<!--配置分片关系-->
<dataNode name="dn1" dataHost="cluster1" database="itcast" />
<dataNode name="dn2" dataHost="cluster2" database="itcast" />
<!--配置连接信息-->
<dataHost name="cluster1" maxCon="1000" minCon="10" balance="3"
writeType="1" dbType="mysql" dbDriver="native" switchType="1"
slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="W1" url="192.168.1.18:3306" user="root"
password="root">
<readHost host="W1R1" url="192.168.1.18:3307" user="root"
password="root" />
</writeHost>
</dataHost>
<dataHost name="cluster2" maxCon="1000" minCon="10" balance="3"
writeType="1" dbType="mysql" dbDriver="native" switchType="1"
slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="W2" url="192.168.1.18:3316" user="root"
password="root">
<readHost host="W2R1" url="192.168.1.18:3317" user="root"
password="root" />
</writeHost>
</dataHost>
</mycat:schema>

rule.xml:

该分片属于取模分片

<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<property name="count">2</property>
</function>

重新启动mycat进行测试:

./startup_nowrap.sh && tail -1 f ../logs/mycat.log

3.3 全局表

1). 将数据节点user_db中的关联的字典表 tb_areas_provinces , tb_areas_city , tb_areas_region中的数据备份 ;

sql
mysqldump -uroot -pitcast user_db tb_areas_provinces  > provinces;
mysqldump -uroot -pitcast user_db tb_areas_city > city;
mysqldump -uroot -pitcast user_db tb_areas_region > region;

2). 将备份的表结构及数据信息, 远程同步到其他两个数据节点的数据库中;

sql
scp city root@192.168.192.158:/root
scp city root@192.168.192.159:/root

scp provinces root@192.168.192.158:/root
scp provinces root@192.168.192.159:/root

scp region root@192.168.192.158:/root
scp region root@192.168.192.159:/root

3). 导入到对应的数据库中

mysql -uroot -p goods_db < city
mysql -uroot -p goods_db < provinces 
mysql -uroot -p goods_db < region

4). MyCat逻辑表中的配置

xml
<table name="tb_areas_city" dataNode="dn1,dn2,dn3" primaryKey="id" type="global"/>
<table name="tb_areas_provinces" dataNode="dn1,dn2,dn3" primaryKey="id"  type="global"/>
<table name="tb_areas_region" dataNode="dn1,dn2,dn3" primaryKey="id"  type="global"/>

5). 重启MyCat

bin/mycat restart

3.4 分片规则

MyCat的分片规则配置在conf目录下的rule.xml文件中定义 ;

取模分片

xml
<tableRule name="mod-long">
    <rule>
        <columns>id</columns>
        <algorithm>mod-long</algorithm>
    </rule>
</tableRule>

<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
    <property name="count">3</property>
</function>

配置说明 :

属性描述
columns标识将要分片的表字段
algorithm指定分片函数与function的对应关系
class指定该分片算法对应的类
count数据节点的数量

范围分片

根据指定的字段及其配置的范围与数据节点的对应情况, 来决定该数据属于哪一个分片 , 配置如下:

xml
<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 配置如下:

properties
# range start-end ,data node index


<NolebasePageProperties />




# K=1000,M=10000.
0-500M=0
500M-1000M=1
1000M-1500M=2

含义为 : 0 - 500 万之间的值 , 存储在0号数据节点 ; 500万 - 1000万之间的数据存储在1号数据节点 ; 1000万 - 1500 万的数据节点存储在2号节点 ;

配置说明:

属性描述
columns标识将要分片的表字段
algorithm指定分片函数与function的对应关系
class指定该分片算法对应的类
mapFile对应的外部配置文件
type默认值为0 ; 0 表示Integer , 1 表示String
defaultNode默认节点
默认节点的所用:枚举分片时,如果碰到不识别的枚举值, 就让它路由到默认节点 ; 如果没有默认值,碰到不识别的则报错 。

枚举分片

通过在配置文件中配置可能的枚举值, 指定数据分布到不同数据节点上, 本规则适用于按照省份或状态拆分数据等业务 , 配置如下:

xml
<tableRule name="sharding-by-intfile">
    <rule>
        <columns>status</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">0</property>
    <property name="defaultNode">0</property>
</function>

partition-hash-int.txt ,内容如下 :

1=0
2=1
3=2

配置说明:

属性描述
columns标识将要分片的表字段
algorithm指定分片函数与function的对应关系
class指定该分片算法对应的类
mapFile对应的外部配置文件
type默认值为0 ; 0 表示Integer , 1 表示String
defaultNode默认节点 ; 小于0 标识不设置默认节点 , 大于等于0代表设置默认节点 ;
默认节点的所用:枚举分片时,如果碰到不识别的枚举值, 就让它路由到默认节点 ; 如果没有默认值,碰到不识别的则报错 。

范围求模算法

该算法为先进行范围分片, 计算出分片组 , 再进行组内求模。

优点: 综合了范围分片和求模分片的优点。 分片组内使用求模可以保证组内的数据分布比较均匀, 分片组之间采用范围分片可以兼顾范围分片的特点。

缺点: 在数据范围时固定值(非递增值)时,存在不方便扩展的情况,例如将 dataNode Group size 从 2 扩展为 4 时,需要进行数据迁移才能完成 ;

配置如下:

xml
<tableRule name="auto-sharding-rang-mod">
	<rule>
		<columns>id</columns>
		<algorithm>rang-mod</algorithm>
	</rule>
</tableRule>

<function name="rang-mod" class="io.mycat.route.function.PartitionByRangeMod">
	<property name="mapFile">autopartition-range-mod.txt</property>
    <property name="defaultNode">0</property>
</function>

autopartition-range-mod.txt 配置格式 :

#range  start-end , data node group size
0-500M=1
500M1-2000M=2

在上述配置文件中, 等号前面的范围代表一个分片组 , 等号后面的数字代表该分片组所拥有的分片数量;

配置说明:

属性描述
columns标识将要分片的表字段名
algorithm指定分片函数与function的对应关系
class指定该分片算法对应的类
mapFile对应的外部配置文件
defaultNode默认节点 ; 未包含以上规则的数据存储在defaultNode节点中, 节点从0开始

固定分片hash算法

该算法类似于十进制的求模运算,但是为二进制的操作,例如,取 id 的二进制低 10 位 与 1111111111 进行位 & 运算。

优点: 这种策略比较灵活,可以均匀分配也可以非均匀分配,各节点的分配比例和容量大小由partitionCount和partitionLength两个参数决定

缺点:和取模分片类似。

配置如下 :

xml
<tableRule name="sharding-by-long-hash">
    <rule>
        <columns>id</columns>
        <algorithm>func1</algorithm>
    </rule>
</tableRule>

<function name="func1" class="org.opencloudb.route.function.PartitionByLong">
    <property name="partitionCount">2,1</property>
    <property name="partitionLength">256,512</property>
</function>

在示例中配置的分片策略,希望将数据水平分成3份,前两份各占 25%,第三份占 50%。

配置说明:

属性描述
columns标识将要分片的表字段名
algorithm指定分片函数与function的对应关系
class指定该分片算法对应的类
partitionCount分片个数列表
partitionLength分片范围列表

约束 :

1). 分片长度 : 默认最大2^10 , 为 1024 ;

2). count, length的数组长度必须是一致的 ;

3). 两组数据的对应情况: (partitionCount[0]partitionLength[0])=(partitionCount[1]partitionLength[1])

以上分为三个分区:0-255,256-511,512-1023

取模范围算法

该算法先进行取模,然后根据取模值所属范围进行分片。

优点:可以自主决定取模后数据的节点分布

缺点:dataNode 划分节点是事先建好的,需要扩展时比较麻烦。

配置如下:

xml
<tableRule name="sharding-by-pattern">
	<rule>
		<columns>id</columns>
		<algorithm>sharding-by-pattern</algorithm>
	</rule>
</tableRule>

<function name="sharding-by-pattern" class="io.mycat.route.function.PartitionByPattern">
	<property name="mapFile">partition-pattern.txt</property>
    <property name="defaultNode">0</property>
    <property name="patternValue">96</property>
</function>

partition-pattern.txt 配置如下:

0-32=0
33-64=1
65-96=2

在mapFile配置文件中, 1-32即代表id%96后的分布情况。如果在1-32, 则在分片0上 ; 如果在33-64, 则在分片1上 ; 如果在65-96, 则在分片2上。

配置说明:

属性描述
columns标识将要分片的表字段
algorithm指定分片函数与function的对应关系
class指定该分片算法对应的类
mapFile对应的外部配置文件
defaultNode默认节点 ; 如果id不是数字, 无法求模, 将分配在defaultNode上
patternValue求模基数

取模范围算法只能针对于数字类型进行取模运算 ; 如果是字符串则无法进行取模分片 ;

字符串hash求模范围算法

与取模范围算法类似, 该算法支持数值、符号、字母取模,首先截取长度为 prefixLength 的子串,在对子串中每一个字符的 ASCII 码求和,然后对求和值进行取模运算(sum%patternValue),就可以计算出子串的分片数。

优点:可以自主决定取模后数据的节点分布

缺点:dataNode 划分节点是事先建好的,需要扩展时比较麻烦。

配置如下:

xml
<tableRule name="sharding-by-prefixpattern">
	<rule>
		<columns>id</columns>
		<algorithm>sharding-by-prefixpattern</algorithm>
	</rule>
</tableRule>

<function name="sharding-by-prefixpattern" class="io.mycat.route.function.PartitionByPrefixPattern">
	<property name="mapFile">partition-prefixpattern.txt</property>
    <property name="prefixLength">5</property>
    <property name="patternValue">96</property>
</function>

partition-prefixpattern.txt 配置如下:

# range start-end ,data node index
# ASCII
# 48-57=0-9
# 64、65-90=@、A-Z
# 97-122=a-z
###### first host configuration
0-32=0
33-64=1
65-96=2

配置说明:

属性描述
columns标识将要分片的表字段
algorithm指定分片函数与function的对应关系
class指定该分片算法对应的类
mapFile对应的外部配置文件
prefixLength截取的位数; 将该字段获取前prefixLength位所有ASCII码的和, 进行求模sum%patternValue ,获取的值,在通配范围内的即分片数 ;
patternValue求模基数

应用指定算法

由运行阶段由应用自主决定路由到那个分片 , 直接根据字符子串(必须是数字)计算分片号 , 配置如下 :

xml
<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">3</property>
	<property name="defaultPartition">0</property>
</function>

配置说明:

属性描述
columns标识将要分片的表字段
algorithm指定分片函数与function的对应关系
class指定该分片算法对应的类
startIndex字符子串起始索引
size字符长度
partitionCount分区(分片)数量
defaultPartition默认分片(在分片数量定义时, 字符标示的分片编号不在分片数量内时,使用默认分片)

示例说明 :

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

字符串hash解析算法

截取字符串中的指定位置的子字符串, 进行hash算法, 算出分片 , 配置如下:

xml
<tableRule name="sharding-by-stringhash">
	<rule>
		<columns>user_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>

配置说明:

属性描述
columns标识将要分片的表字段
algorithm指定分片函数与function的对应关系
class指定该分片算法对应的类
partitionLengthhash求模基数 ; length*count=1024 (出于性能考虑)
partitionCount分区数
hashSlicehash运算位 , 根据子字符串的hash运算 ; 0 代表 str.length() , -1 代表 str.length()-1 , 大于0只代表数字自身 ; 可以理解为substring(start,end),start为0则只表示0

一致性hash算法

一致性Hash算法有效的解决了分布式数据的拓容问题 , 配置如下:

xml
<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>
    <property name="count">3</property><!--  -->
    <property name="virtualBucketTimes">160</property>
    <!-- <property name="weightMapFile">weightMapFile</property> -->
    <!-- <property name="bucketMapPath">/etc/mycat/bucketMapPath</property> -->
</function>

配置说明:

属性描述
columns标识将要分片的表字段
algorithm指定分片函数与function的对应关系
class指定该分片算法对应的类
seed创建murmur_hash对象的种子,默认0
count要分片的数据库节点数量,必须指定,否则没法分片
virtualBucketTimes一个实际的数据库节点被映射为这么多虚拟节点,默认是160倍,也就是虚拟节点数是物理节点数的160倍;virtualBucketTimes*count就是虚拟结点数量 ;
weightMapFile节点的权重,没有指定权重的节点默认是1。以properties文件的格式填写,以从0开始到count-1的整数值也就是节点索引为key,以节点权重值为值。所有权重值必须是正整数,否则以1代替
bucketMapPath用于测试时观察各物理节点与虚拟节点的分布情况,如果指定了这个属性,会把虚拟节点的murmur hash值与物理节点的映射按行输出到这个文件,没有默认值,如果不指定,就不会输出任何东西

日期分片算法

按照日期来分片

xml
<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">2020-01-01</property>
	<property name="sEndDate">2020-12-31</property>
    <property name="sPartionDay">10</property>
</function>

配置说明:

属性描述
columns标识将要分片的表字段
algorithm指定分片函数与function的对应关系
class指定该分片算法对应的类
dateFormat日期格式
sBeginDate开始日期
sEndDate结束日期,如果配置了结束日期,则代码数据到达了这个日期的分片后,会重复从开始分片插入
sPartionDay分区天数,默认值 10 ,从开始日期算起,每个10天一个分区

注意:配置规则的表的 dataNode 的分片,必须和分片规则数量一致,例如 2020-01-01 到 2020-12-31 ,每10天一个分片,一共需要37个分片。

单月小时算法

单月内按照小时拆分, 最小粒度是小时 , 一天最多可以有24个分片, 最小1个分片, 下个月从头开始循环, 每个月末需要手动清理数据。

配置如下 :

xml
<tableRule name="sharding-by-hour">
    <rule>
        <columns>create_time</columns>
        <algorithm>sharding-by-hour</algorithm>
    </rule>
</tableRule>

<function name="sharding-by-hour" class="io.mycat.route.function.LatestMonthPartion">
	<property name="splitOneDay">24</property>
</function>

配置说明:

属性描述
columns标识将要分片的表字段 ; 字符串类型(yyyymmddHH), 需要符合JAVA标准
algorithm指定分片函数与function的对应关系
splitOneDay一天切分的分片数

自然月分片算法

使用场景为按照月份列分区, 每个自然月为一个分片, 配置如下:

xml
<tableRule name="sharding-by-month">
    <rule>
        <columns>create_time</columns>
        <algorithm>sharding-by-month</algorithm>
    </rule>
</tableRule>

<function name="sharding-by-month" class="io.mycat.route.function.PartitionByMonth">
	<property name="dateFormat">yyyy-MM-dd</property>
	<property name="sBeginDate">2020-01-01</property>
	<property name="sEndDate">2020-12-31</property>
</function>

配置说明:

属性描述
columns标识将要分片的表字段
algorithm指定分片函数与function的对应关系
class指定该分片算法对应的类
dateFormat日期格式
sBeginDate开始日期
sEndDate结束日期,如果配置了结束日期,则代码数据到达了这个日期的分片后,会重复从开始分片插入

日期范围hash算法

其思想和范围取模分片一样,先根据日期进行范围分片求出分片组,再根据时间hash使得短期内数据分布的更均匀 ;

优点 : 可以避免扩容时的数据迁移,又可以一定程度上避免范围分片的热点问题

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

xml
<tableRule name="range-date-hash">
    <rule>
        <columns>create_time</columns>
        <algorithm>range-date-hash</algorithm>
    </rule>
</tableRule>

<function name="range-date-hash" class="io.mycat.route.function.PartitionByRangeDateHash">
	<property name="dateFormat">yyyy-MM-dd HH:mm:ss</property>
	<property name="sBeginDate">2020-01-01 00:00:00</property>
	<property name="groupPartionSize">6</property>
    <property name="sPartionDay">10</property>
</function>

配置说明:

属性描述
columns标识将要分片的表字段
algorithm指定分片函数与function的对应关系
class指定该分片算法对应的类
dateFormat日期格式 , 符合Java标准
sBeginDate开始日期 , 与 dateFormat指定的格式一致
groupPartionSize每组的分片数量
sPartionDay代表多少天为一组

四、核心知识

4.1 MyCat 性能监控

MyCat-web

Mycat-web 是 Mycat 可视化运维的管理和监控平台,弥补了 Mycat 在监控上的空白。帮 Mycat 分担统计任务和配置管理任务。Mycat-web 引入了 ZooKeeper 作为配置中心,可以管理多个节点。Mycat-web 主要管理和监控 Mycat 的流量、连接、活动线程和内存等,具备 IP 白名单、邮件告警等模块,还可以统计 SQL 并分析慢 SQL 和高频 SQL 等。为优化 SQL 提供依据。

下载地址

需安装zookeeper,解压后即可启动。

如与zookeeper不在同一服务器,需要修改配置中的地址mycat-web/mycat-web/WEB-INF/classes/mycat.properties

启动后访问添加mycat的地址信息后即可进行监控。

性能监控

在 Mycat-web 上可以进行 Mycat 性能监控,例如:内存分享、流量分析、连接分析、活动线程分析等等。

MyCat的内存分析 , 反映了当前的内存使用情况与历史时间段的峰值、平均值。

MyCat流量分析统计了历史时间段的流量峰值、当前值、平均值,是MyCat数据传输的重要指标, In代表输入, Out代表输出

MyCat连接分析, 反映了MyCat的连接数

MyCat TPS 是并发性能的重要参数指标, 指系统在每秒内能够处理的请求数量。 MyCat TPS的值越高 , 代表MyCat单位时间内能够处理的请求就越多, 并发能力也就越高。

E. MyCat活动线程分析反映了MyCat线程的活动情况。

F. MyCat缓存队列分析, 反映了当前在缓存队列中的任务数量。

MySQL性能监控指标

可以通过MySQL服务监控, 检测每一个MySQL节点的运行状态, 包含缓存命中率 、增删改查比例、流量统计、慢查询比例、线程、临时表等相关性能数据。

SQL监控

SQL 统计

SQL表分析

SQL监控

高频SQL

慢SQL统计

SQL解析

4.2 分布式事务实现

MyCat在1.6版本以后已经支持XA分布式事务类型了。具体的使用流程如下:

1). 在应用层需要设置事务不能自动提交

set autocommit=0;

2). 在SQL中设置XA为开启状态

set xa = on;

3). 执行SQL

insert into user(id,name,sex) values(1,'Tom','1'),(2,'Rose','2'),(3,'Leo','1'),(4,'Lee','1');

4). 对事务进行提交或回滚

commit/rollback

完整流程如下:

image-20200129223657058

4.3 SQL路由实现

MyCat的路由是和SQL解析组件息息相关的, SQL路由模块是MyCat数据库中间件最重要的模块之一, 使用MyCat主要是为了分库分表, 而分库分表的核心就是路由。

作用

MyCat接收到应用系统发来的查询语句, 要将其发送到后端连接的MySQL数据库去执行, 但是后端有三个数据库服务器,具体要查询那一台数据库服务器呢, 这就是路由需要实现的功能。

SQL的路由既要保证数据的完整 , 也不能造成资源的浪费, 还要保证路由的效率。

SQL解析器

Mycat1.3版本之前模式使用的是Fdbparser的foundationdb的开源SQL解析器,在2015年被apple收购后,从开源变为闭源了。

目前版本的MyCat采用的是Druid的SQL解析器, 性能比采用Fdbparser整体性能提高20%以上。

4.4 跨库Join

全局表

每个企业级的系统中, 都会存在一些系统的基础信息表, 类似于字典表、省份、城市、区域、语言表等, 这些表与业务表之间存在关系, 但不是业务主从关系,而是一种属性关系。

当我们对业务表进行分片处理时, 可以将这些基础信息表设置为全局表, 也就是在每个节点中都存在该表。

全局表的特性如下:

A. 全局表的insert、update、delete操作会实时地在所有节点同步执行, 保持各个节点数据的一致性

B. 全局表的查询操作会从任意节点执行,因为所有节点的数据都一致

C. 全局表可以和任意表进行join操作

ER表

关系型数据库是基于实体关系模型(Entity Relationship Model)的, MyCat中的ER表便来源于此。 MyCat提出了基于ER关系的数据分片策略 , 子表的记录与其所关联的父表的记录存放在同一个数据分片中, 通过表分组(Table Group)保证数据关联查询不会跨库操作。

catlet

catlet是MyCat为了解决跨分片Join提出的一种创新思路, 也叫做人工智能(HBT)。MyCat参考了数据库中存储过程的实现方式,提出类似的跨库解决方案,用户可以根据系统提供的API接口实现跨分片Join。

采用这种方案开发时,必须要实现Catlet接口的两个方法

route 方法: 路由的方法, 传递系统配置和schema配置等 ;

processSQL方法: EngineCtx执行SQL并给客户端返回结果集 ;

当我们自定义Catlet完成之后, 需要将Catlet的实现类进行编译,并将其字节码文件 XXXCatlet.class存放在mycat_home/catlet目录下, 系统会加载相关Class, 而且每隔1分钟扫描一次文件是否更新, 若更新则自动重新加载,因此无需重启服务。

ShareJoin

ShareJoin 是Catlet的实现, 是一个简单的跨分片Join, 目前支持两个表的Join,原理就是解析SQL语句, 拆分成单表的语句执行, 单后把各个节点的数据进行汇集。

要想使用Catlet完成join, 还需要借助于MyCat中的注解, 在执行SQL语句时,使用catlet注解:

/*!mycat:catlet=demo.catlets.ShareJoin */ select a.id as aid , a.id , b.id as bid , b.name as name from customer a, company b where a.company_id=b.id and a.id = 1;

4.5 数据汇聚与排序

通过MyCat实现数据汇聚和排序,不仅可以减少各分片与客户端之间的数据传输IO, 也可以帮助开发者总复杂的数据处理中解放出来,从而专注于开发业务代码。

在MySQL中存在两种排序方式: 一种利用有序索引获取有序数据, 另一种通过相应的排序算法将获取到的数据在内存中进行排序。 而MyCat中数据排序采用堆排序法对多个分片返回有序数据,并在合并、排序后再返回给客户端。

五、MyCat架构剖析

5.1 总体架构

MyCat在逻辑上由几个模块组成: 通信协议、路由解析、结果集处理、数据库连接、监控等模块。

1). 通信协议模块: 通信协议模块承担底层的收发数据、线程回调处理工作, MyCat通信协议默认采用Reactor模式,在协议层采用MySQL协议;

2). 路由解析模块: 负责对传入的SQL语句进行语法解析, 解析语句的条件、类型、关键字等,并进行优化;

3). SQL执行模块: 负责从连接池中获取连接, 再根据路由解析的结果, 把SQL语句分发到相应的节点执行;

4). 数据库连接模块: 负责创建、管理、维护后端的连接池。为减少每次建立数据库连接的开销,数据库使用连接池机制对连接声明周期进行管理;

5). 结果集处理模块: 负责对跨分片的查询结果进行汇聚、排序、截取等;

6). 监控管理模块: 负责MyCat的连接、内存等资源进行监控和管理。监控主要通过管理指令及监控服务展现一些监控数据; 管理则主要通过轮询事件来检测和释放不适用的资源;

5.2 网络I/O架构及实现

BIO、NIO与AIO

1). BIO

BIO(同步阻塞I/O) 通常由一个单独的Acceptor线程负责监听客户端的连接, 接收到客户端的连接请求后, 会为每个客户端创建一个新的线程进行处理, 处理完成之后, 再给客户端返回结果, 销毁线程 。

每个客户端请求接入时, 都需要开启一个线程进行处理, 一个线程只能处理一个客户端连接。 当客户端变多时,会创建大量的处理线程, 每个线程都需要分配栈空间和CPU, 并且频繁的线程上下文切换也会造成性能的浪费。所以该模式, 无法满足高性能、高并发接入的需求。

2). NIO

NIO(同步非阻塞I/O)基于Reactor模式作为底层通信模型,Reactor模式可以将事件驱动的应用进行事件分派, 将客户端发送过来的服务请求分派给合适的处理类(handler)。当Socket有流可读或可写入Socket时, 操作系统会通知相应的应用程序进行处理, 应用程序再将流读取到缓冲区或写入操作系统。 这时已经不是一个连接对应一个处理线程了, 而是一个有效的请求对应一个线程, 当没有数据时, 就没有工作线程来处理。

NIO 的最大优点体现在线程轮询访问Selector, 当read或write到达时则处理, 未到达时则继续轮询。

3). AIO

AIO,全程 Asynchronous IO(异步非阻塞的IO), 是一种非阻塞异步的通信模式。在NIO的基础上引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。AIO中客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。

AIO与NIO的主要区别在于回调与轮询, 客户端不需要关注服务处理事件是否完成, 也不需要轮询, 只需要关注自己的回调函数。

通信架构

在MyCat中实现了NIO与AIO两种I/O模式, 可以通过配置文件server.xml进行指定 :

xml
<property name="usingAIO">1</property>

usingAIO为1代表使用AIO模型 , 为0表示使用NIO模型;

MyCat的AIO架构

image-20200108103954458

1). MyCatStartUp是整个MyCat服务启动的入口;

2). 在获取到MyCat的home目录后, 把主要的任务交给MyCatServer , 并调用其startup方法;

3). 初始化系统配置, 获取配置文件中的usingAIO的配置, 如果配置为1, 说明使用AIO模型 , 进入到AIO的分支, 并创建两个连接, 一个是管理后台连接(9066), 一个server的连接(8066);

4). 进入AIO分支 , 主要有AIOAcceptor接收客户端请求, 绑定端口, 创建服务端的异步Socket ;在accept方法中完成两件事: ①. FrontedConnection的创建, 这是前段连接的关键; ②. register注册事件, MySQL协议握手包就在此时发送;

MyCat的NIO架构

如果设置的usingAIO为0 ,那么将走NIOAcceptor通道 , 流程如下:

image-20200108111153230

1). 如果走NIO分支 , 将首先创建NIOAcceptor对象, 并调用其start方法;

2). NIOAcceptor 负责处理Accept事件, 服务端接收客户端的连接事件, 就是MyCat作为服务端去处理前端业务程序发过来的连接请求, 建立链接后, 调用NIOAcceptor的 NIOReactor.postRegister方法进行注册(并没有注解注册, 而是放入缓冲队列, 避免加锁的竞争)。

5.3 实现MySQL协议

MySQL协议处于应用层之下、TCP/IP之上, 在MySQL客户端和服务端之间使用。包含了链接器、MySQL代理、主从复制服务器之间通信,并支持SSL加密、传输数据的压缩、连接和身份验证及数据交互等。其中,握手认证阶段和命令执行阶段是MySQL协议中的两个重要阶段。

A. 握手认证阶段是客户端连接服务器的必经之路, 客户端与服务端完成TCP的三次握手以后, 服务端会向客户端发送一个初始化握手包, 握手包中包含了协议版本、MySQLServer版本、线程ID、服务器的权能标识和字符集等信息。

B. 客户端在接收到服务端的初始化握手包之后, 会发送身份验证包给服务端(AuthPacket), 该包中包含用户名、密码等信息。

C. 服务端接收到客户端的登录验证包之后,需要进行逻辑校验,校验该登录信息是否正确。如果信息都符合,则返回一个OKPacket,表示登录成功,否则返回ERR_Packet,表示拒绝。

1). 初始化握手包

Packet Length : 包的长度;

Packet Number : 包的序号;

Server Greeting : 消息体, 包含了协议版本、MySQLServer版本、线程ID和字符集等信息。

2). 登录认证包

客户端在接收到服务端发来的初始握手包之后, 向服务端发出认证请求, (由Wireshark抓获)

3). OK包或ERROR包

服务端接收到客户端的登录认证包之后,如果通过认证,则返回一个OKPacket,如果未通过认证,则返回一个ERROR包。

命令执行阶段

在握手认证阶段通过并完成以后, 客户端可以向服务端发送各种命令来请求数据, 此阶段的流程是: 命令请求->返回结果集。

MySQL协议实现

握手认证实现

在MyCat中同时实现了NIO和AIO, 通过配置可以选择NIO和AIO。MyCat Server在启动阶段已经选择好采用NIO还是AIO,因此建立I/O通道后,MyCat服务端一直等待客户端端的连接,当有连接到来的时候,MyCat首先发送握手包。

1). 握手包源码实现

MyCat中的源码中io.mycat.net.FrontendConnection类的实现 握手包信息组装完毕后, 通过FrontedConnection写回客户端。

2). 认证包源码实现

客户端接收到握手包后, 紧接着向服务端发起一个认证包, MyCat封装为类 AuthPacket:客户端发送的认证包转由 FrontendAuthenticator 的Handler来处理, 主要操作就是 拆包, 检查用户名、密码合法性, 检查连接数是够超出限制。

认证失败, 调用failure方法, 认证成功调用success方法。

命令执行实现

命令执行阶段就是SQL命令和SQL语句执行阶段, 在该阶段MyCat主要需要做的事情, 就是对客户端发来的数据包进行拆包, 并判断命令的类型, 并解析SQL语句, 执行响应的SQL语句, 最后把执行结果封装在结果集包中, 返回给客户端。

从客户端发来的命令交给 FrontendCommandHandler 中的handle方法处理

query 处理具体的请求, 返回客户端结果集数据包

5.4 线程架构

线程池实现

在MyCat中大量用到了线程池, 通过线程池来避免频繁的创建和销毁线程而造成的系统性能的浪费。在MyCat中使用的线程池是JDK中提供的线程池 ThreadPoolExecutor 的子类 NameableExecutor

构造参数含义:

corePoolSize : 核心池大小

maximumPoolSize : 最大线程数

keepAliveTime: 线程没有任务执行时, 最多能够存活多久

timeUnit: 时间单位

workQueue: 阻塞任务队列

threadFactory: 线程工厂, 用来创建线程

线程架构

在MyCat中主要有两大线程池: timerExecutor 和 businessExecutor。

1). timerExecutor 线程池主要完成系统时间定时更新、处理器定时检查、数据节点定时连接空闲超时检查、数据节点定时心跳检测等任务。

2). businessExecutor是MyCat最重要的线程资源池, 该资源池的线程使用的范围非常广, 涵盖以下方面:

A. 后端用原生协议连接数据

B. JDBC执行SQL语句

C. SQL拦截

D. 数据合并服务

E. 批量SQL作业

F. 查询结果的异步分发

G. 基于guava实现异步回调

5.5 内存管理

内存管理指的是MyCat缓冲区管理, 众所周知设置缓冲区的唯一目的是提高系统的性能, 缓冲区通常是部分常用的数据存放在缓冲池中以便系统直接访问, 避免使用磁盘IO访问磁盘数据, 从而提高性能。

1). 缓冲池组成

缓冲池的最小单位为chunk, 默认的chunk大小为4096字节(DEFAULT_BUFFER_CHUNK_SIZE), BufferPool的总大小为4096 x processors x 1000(其中processors为处理器数量)。对I/O进程而言, 他们共享一个缓冲池。缓冲池有两种类型: 本地缓存线程(以$_开头的线程)缓冲区和其他缓冲区, 分配buffer时, 优先获取ThreadLocalPool中的buffer, 没有命中时会获取BufferPool中的buffer。

2). 分配MyCat缓冲池

分配缓冲池时, 可以指定大小, 也可以用默认值。

A. allocate(): 先检测是否为本地线程, 当执行线程为本地缓存线程时, localBufferPool取出一个可用的buffer。如果不是, 则从ConcurrentLinkedQueue队列中取出一个buffer进行分配, 如果队列没有可用的buffer, 则创建一个直接缓冲区。

B. allocate(size): 如果用户指定的size不大于chunkSize, 则调用allocate()进行分配; 反之则调用createTempBuffer(size)创建临时非直接缓冲区。

3). MyCat缓冲池的回收

回收时先判断buffer是否有效, 有如下情况时缓冲池不回收。

A. 不是直接缓冲区

B. buffer是空的

C. buffer的容量大于chunkSize

5.6 缓存架构

1). 缓存框架选择

MyCat支持ehcache、mapdb、leveldb缓存, 可通过配置文件cacheserver.properties来进行配置;

2). 缓存内容

MyCat有路由缓存、表主键到datanode缓存、ER关系缓存。

A. 路由缓存: 即SQLRouteCache, 根据SQL语句查找路由信息的缓存, 该缓存只是针对select语句, 如果执行了之前已经执行过的某个SQL语句(缓存命中), 那么路由信息就不需要重复计算了, 直接从缓存中获取。

B. 表主键到datanode缓存: 当分片字段与主键字段不一致时, 直接通过主键值查询时无法定位具体分片的(只能全分片下发), 所以设置该缓存之后, 就可以利用主键值查找到分片名, 缓存的key是ID值, value是节点名。

C. ER关系缓存: 在ER分片时使用, 而且在insert查询中才会使用缓存, 当字表插入数据时, 根据父子关联字段确定子表分片, 下次可以直接从缓存中获取所在的分片。

查看缓存指令: show @@cache;

5.7 连接池架构

连接池是MyCat的后端连接池, 也就是MyCat后端与各个数据库节点之间的连接架构。

1). 连接池创建

MyCat按照每个dataHost创建一个连接池, 根据schema.xml文件的配置取得最小的连接数minCon, 并初始化minCon个连接。在初始化连接时, 还需要判定用户选择的是JDBC还是原生的MySQL协议, 以便于创建对应的连接。

2). 连接池分配

分配连接就是从连接池队列中取出一个连接, 在取出一个连接时, MyCat需要根据负载均衡(balance属性)的类型选择不同的数据源, 因为连接和数据源绑在一起,所以需要知道MyCat读写的是那些数据源, 才能分配响应的连接。

3). 架构

image-20200108162456464

5.8 主从切换架构

MyCat实现MySQL读写分离的目的在于降低单节点数据库的访问压力, 原理就是让主数据库执行增删改操作, 从数据库执行查询操作, 利用MySQL数据库的复制机制将Master的数据同步到slave上。

当master宕机后,slave承载的业务如何切换到master继续提供服务,以及slave宕机后如何将master切换到slave上。手动切换数据源很简单, 但不是运维工作的首选,本节重点就是讲解如何实现自动切换。

MyCat的读写分离依赖于MySQL的主从同步, 也就是说MyCat没有实现数据的主从同步功能, 但是实现了自动切换功能。

1). 自动切换

自动切换是MyCat主从复制的默认配置 , 当主机或从机宕机后, MyCat自动切换到可用的服务器上。 假设写服务器为M, 读服务器为S, 则:

正常时, 写M读S;

当M宕机后, 读写S ; 恢复M后, 写S, 读M ;

当S宕机后, 读写M ; 恢复S后, 写M, 读S ;

2). 基于MySQL主从同步状态的切换

这种切换方式与自动切换不同, MyCat检测到主从数据同步延迟时, 会自动切换到拥有最新数据的MySQL服务器上, 防止读到很久以前的数据。

原理就是通过检查MySQL的主从同步状态(show slave status)中的Seconds_Behind_Master、Slave_IO_Running、Slave_SQL_Running三个字段,来确定当前主从同步的状态以及主从之间的数据延迟。 Seconds_Behind_Master为0表示没有延迟,数值越大,则说明延迟越高。

MyCat主从切换实现

基于延迟的切换, 则判断结果集中的Slave_IO_Running、Slave_SQL_Running两个个字段是否都为yes,以及Seconds_Behind_Master 是否小于配置文件中配置的 slaveThreshold的值, 如果有其中任何一个条件不满足, 则切换。

主要流程如下:

image-20200128005840029

六、安装

6.1 常规安装

下载地址 最新下载地址

前提:安装mysql和jdk

6.2 负载均衡集群

MyCat集群

mycat做了数据库的代理,在高并发的情况下,必然也会面临单节点性能问题,所以需要部署多个mycat节点。

image-20210125233223833

搭建多节点mycat:

cp mycat mycat2 -R
vim wrapper.conf
#设置jmx端口
wrapper.java.additional.7=-Dcom.sun.management.jmxremote.port=1985
vim server.xml
#设置服务端口以及管理端口
<property name="serverPort">8067</property>
<property name="managerPort">9067</property>
#重新启动服务
./startup_nowrap.sh
tail -f ../logs/mycat.log

负载均衡

对mycat做了集群,保障了mycat的可靠性,但是,应用程序需要连接到多个mycat,缺少负载均衡的组件。

官网:http://www.haproxy.org/ 关于并发性能,haproxy可以做到千万级的并发。(当然了,运行环境不同,测试结果也不相同的)

架构

image-20210125234142721

部署安装HAProxy

1). HAProxy 实现了 MyCat 多节点的集群高可用和负载均衡,而 HAProxy 自身的高可用则可以通过Keepalived 来实现。因此,HAProxy 主机上要同时安装 HAProxy 和 Keepalived,Keepalived 负责为该服务器抢占 vip(虚拟 ip),抢占到 vip 后,对该主机的访问可以通过原来的 ip访问,也可以直接通过 vip访问。

2). Keepalived 抢占 vip 有优先级,在 keepalived.conf 配置中的 priority 属性决定。但是一般哪台主机上的Keepalived服务先启动就会抢占到vip,即使是slave,只要先启动也能抢到(要注意避免Keepalived的资源抢占问题)。

3). HAProxy 负责将对 vip 的请求分发到 MyCat 集群节点上,起到负载均衡的作用。同时 HAProxy 也能检测到 MyCat 是否存活,HAProxy 只会将请求转发到存活的 MyCat 上。

4). 如果 Keepalived+HAProxy 高可用集群中的一台服务器宕机,集群中另外一台服务器上的 Keepalived会立刻抢占 vip 并接管服务,此时抢占了 vip 的 HAProxy 节点可以继续提供服务。

5). 如果一台 MyCat 服务器宕机,HAPorxy 转发请求时不会转发到宕机的 MyCat 上,所以 MyCat 依然可用。

综上:MyCat 的高可用及负载均衡由 HAProxy 来实现,而 HAProxy 的高可用,由 Keepalived 来实现。

keepalived介绍:

Keepalived是一种基于VRRP协议来实现的高可用方案,可以利用其来避免单点故障。 通常有两台甚至多台服务器运行Keepalived,一台为主服务器(Master), 其他为备份服务器, 但是对外表现为一个虚拟IP(VIP), 主服务器会发送特定的消息给备份服务器, 当备份服务器接收不到这个消息时, 即认为主服务器宕机, 备份服务器就会接管虚拟IP, 继续提供服务, 从而保证了整个集群的高可用。 VRRP(虚拟路由冗余协议-Virtual Router Redundancy Protocol)协议是用于实现路由器冗余的协议,VRRP 协议将两台或多台路由器设备虚拟成一个设备,对外提供虚拟路由器 IP(一个或多个),而在路由器组内部,如果实际拥有这个对外 IP 的路由器如果工作正常的话就是 MASTER,或者是通过算法选举产生。MASTER 实现针对虚拟路由器 IP 的各种网络功能,如 ARP 请求,ICMP,以及数据的转发等;其他设备不拥有该虚拟 IP,状态是 BACKUP,除了接收 MASTER 的VRRP 状态通告信息外,不执行对外的网络功能。当主机失效时,BACKUP 将接管原先 MASTER 的网络功能。VRRP 协议使用多播数据来传输 VRRP 数据,VRRP 数据使用特殊的虚拟源 MAC 地址发送数据而不是自身网卡的 MAC 地址,VRRP 运行时只有 MASTER 路由器定时发送 VRRP 通告信息,表示 MASTER 工作正常以及虚拟路由器 IP(组),BACKUP 只接收 VRRP 数据,不发送数据,如果一定时间内没有接收到 MASTER 的通告信息,各 BACKUP 将宣告自己成为 MASTER,发送通告信息,重新进行 MASTER 选举状态。

通过docker进行安装。

#拉取镜像
docker pull haproxy:1.9.3
#创建目录,用于存放配置文件
mkdir /haoke/haproxy
#创建容器
docker create --name haproxy --net host -v /haoke/haproxy:/usr/local/etc/haproxy
haproxy:1.9.3

常规安装

1). 准备好HAProxy安装包,传到/root目录下

haproxy-1.5.16.tar.gz

2). 解压到/usr/local/src目录下

tar -zxvf haproxy-1.5.16.tar.gz -C /usr/local/src

3). 进入解压后的目录,查看内核版本,进行编译

cd /usr/local/src/haproxy-1.5.16
uname -r
make TARGET=linux2632 PREFIX=/usr/local/haproxy ARCH=x86_64

# TARGET=linux310,内核版本,使用uname -r查看内核,如:2.6.32-431.el6.x86_64,此时该参数就为linux2632;
# ARCH=x86_64,系统位数;
# PREFIX=/usr/local/haprpxy #/usr/local/haprpxy,为haprpxy安装路径。

4). 编译完成后,进行安装

make install PREFIX=/usr/local/haproxy

编写配置文件

#创建文件
vim /haoke/haproxy/haproxy.cfg
#输入如下内容
global
log 127.0.0.1 local2
maxconn 4000
daemon
defaults
mode http
log global
option httplog
option dontlognull
option http-server-close
option forwardfor except 127.0.0.0/8
option redispatch
retries 3
timeout http-request 10s
timeout queue 1m
timeout connect 10s
timeout client 1m
timeout server 1m
timeout http-keep-alive 10s
timeout check 10s
maxconn 3000
listen admin_stats
bind 0.0.0.0:4001
mode http
stats uri /dbs
stats realm Global\ statistics
stats auth admin:admin123
listen proxy-mysql
bind 0.0.0.0:4002
mode tcp
balance roundrobin
option tcplog
#代理mycat服务
server mycat_1 192.168.1.18:8066 check port 8066 maxconn 2000
server mycat_2 192.168.1.18:8067 check port 8067 maxconn 2000

配置解析

sh
#global 配置中的参数为进程级别的参数,通常与其运行的操作系统有关
global
	#定义全局的syslog服务器, 最多可定义2个; local0 是日志设备, 对应于/etc/rsyslog.conf中的配置 , 默认收集info级别日志
	log 127.0.0.1 local0 
	#log 127.0.0.1 local1 notice
	#log loghost local0 info
	#设定每个haproxy进程所接受的最大并发连接数 ;
	maxconn 4096 
	#修改HAproxy工作目录至指定的目录并在放弃权限之前执行chroot操作, 可以提升haproxy的安全级别
	chroot /usr/local/haproxy 
	#进程ID保存文件
	pidfile /usr/data/haproxy/haproxy.pid
	#指定用户ID
	uid 99
	#指定组ID
	gid 99
	#设置HAproxy以守护进程方式运行
	daemon
	#debug
	#quiet
	node mysql-haproxy-01  ## 定义当前节点的名称,用于 HA 场景中多 haproxy 进程共享同一个 IP 地址时
	description mysql-haproxy-01 ## 当前实例的描述信息
	
#defaults:用于为所有其他配置段提供默认参数,这默认配置参数可由下一个"defaults"所重新设定
defaults
	#继承global中的log定义
	log global
	#所使用的处理模式(tcp:四层 , http:七层, health:状态检查,只返回OK)
	### tcp: 实例运行于纯 tcp 模式,在客户端和服务器端之间将建立一个全双工的连接,且不会对 7 层报文做任何类型的检查,此为默认模式
	### http:实例运行于 http 模式,客户端请求在转发至后端服务器之前将被深度分析,所有不与 RFC 模式兼容的请求都会被拒绝
	### health:实例运行于 health 模式,其对入站请求仅响应“OK”信息并关闭连接,且不会记录任何日志信息 ,此模式将用于相应外部组件的监控状态检测请求
	mode tcp
	#当服务器负载很高的时候,自动结束掉当前队列处理时间比较长的连接
	option abortonclose
		
	#当使用了cookie时,haproxy将会将请求的后端服务器的serverID插入到cookie中,以保证会话的session持久性,而此时,后端服务器宕机,但是客户端的cookie不会刷新,设置此参数,将会将客户请求强制定向到另外一个后端server上,以保证服务的正常。
	option redispatch
	retries 3
	# 前端的最大并发连接数(默认为 2000)
	maxconn 2000
	# 连接超时(默认是毫秒,单位可以设置 us,ms,s,m,h,d)
	timeout connect 5000
	# 客户端超时时间
	timeout client 50000
	# 服务器超时时间
	timeout server 50000

#listen: 用于定义通过关联“前端”和“后端”一个完整的代理,通常只对 TCP 流量有用
listen proxy_status
	bind 0.0.0.0:48066 # 绑定端口
		mode tcp
		balance roundrobin # 定义负载均衡算法,可用于"defaults"、"listen"和"backend"中,默认为轮询
		#格式: server <name> <address> [:[port]] [param*]
		# weight : 权重,默认为 1,最大值为 256,0 表示不参与负载均衡
        # backup : 设定为备用服务器,仅在负载均衡场景中的其他 server 均不可以启用此 server
        # check  : 启动对此 server 执行监控状态检查,其可以借助于额外的其他参数完成更精细的设定
        # inter  : 设定监控状态检查的时间间隔,单位为毫秒,默认为 2000,也可以使用 fastinter 和 downinter 来根据服务器端专题优化此事件延迟
        # rise   : 设置 server 从离线状态转换至正常状态需要检查的次数(不设置的情况下,默认值为 2)
        # fall   : 设置 server 从正常状态转换至离线状态需要检查的次数(不设置的情况下,默认值为 3)
        # cookie : 为指定 server 设定 cookie 值,此处指定的值将会在请求入站时被检查,第一次为此值挑选的 server 将会被后续的请求所选中,其目的在于实现持久连接的功能
        # maxconn: 指定此服务器接受的最大并发连接数,如果发往此服务器的连接数目高于此处指定的值,其将被放置于请求队列,以等待其他连接被释放
		server mycat_1 192.168.192.157:8066 check inter 10s
		server mycat_2 192.168.192.158:8066 check inter 10s

# 用来匹配接收客户所请求的域名,uri等,并针对不同的匹配,做不同的请求处理
# HAProxy 的状态信息统计页面
frontend admin_stats
	bind 0.0.0.0:8888
		mode http
		stats enable
		option httplog
		maxconn 10
		stats refresh 30s
		stats uri /admin
		stats auth admin:123123
		stats hide-version
		stats admin if TRUE

HAProxy的负载均衡策略:

策略含义
roundrobin表示简单的轮循,即客户端每访问一次,请求轮循跳转到后端不同的节点机器上
static-rr基于权重轮循,根据权重轮循调度到后端不同节点
leastconn加权最少连接,表示最少连接者优先处理
source表示根据请求源IP,这个跟Nginx的IP_hash机制类似,使用其作为解决session问题的一种方法
uri表示根据请求的URL,调度到后端不同的服务器
url_param表示根据请求的URL参数来进行调度
hdr(name)表示根据HTTP请求头来锁定每一次HTTP请求
rdp-cookie(name)表示根据cookie(name)来锁定并哈希每一次TCP请求

启动容器:

#启动容器
docker restart haproxy && docker logs -f haproxy

常规启动

1). 启动HAProxy

/usr/local/haproxy/sbin/haproxy -f /usr/local/haproxy/haproxy.conf

2). 查看HAProxy进程

ps -ef|grep haproxy

Keepalived安装配置

安装配置

1). 上传安装包到Linux

alt + p --------> put D:/tmp/keepalived-1.4.5.tar.gz

2). 解压安装包到目录 /usr/local/src

tar -zxvf keepalived-1.4.5.tar.gz -C /usr/local/src

3). 安装依赖插件

yum install -y gcc openssl-devel popt-devel

4). 进入解压后的目录,进行配置,进行编译

 cd /usr/local/src/keepalived-1.4.5
 
 ./configure --prefix=/usr/local/keepalived

5). 进行编译,完成后进行安装

make && make install

6). 运行前配置

cp /usr/local/src/keepalived-1.4.5/keepalived/etc/init.d/keepalived /etc/init.d/
mkdir /etc/keepalived
cp /usr/local/keepalived/etc/keepalived/keepalived.conf /etc/keepalived/
cp /usr/local/src/keepalived-1.4.5/keepalived/etc/sysconfig/keepalived /etc/sysconfig/
cp /usr/local/keepalived/sbin/keepalived /usr/sbin/

7). 修改配置文件 /etc/keepalived/keepalived.conf

Master:

global_defs {
	notification_email {
		javadct@163.com
	}
	notification_email_from keepalived@showjoy.com
	smtp_server 127.0.0.1
	smtp_connect_timeout 30
	router_id haproxy01
	vrrp_skip_check_adv_addr
	vrrp_garp_interval 0
	vrrp_gna_interval 0
}

vrrp_script chk_haproxy {
	script "/etc/keepalived/haproxy_check.sh"
	interval 2
	weight 2
}

vrrp_instance VI_1 {
	#主机配MASTER,备机配BACKUP
	state MASTER
	#所在机器网卡
	interface eth1
	virtual_router_id 51
	#数值越大优先级越高
	priority 120
	advert_int 1
	authentication {
		auth_type PASS
		auth_pass 1111
	}
	## 将 track_script 块加入 instance 配置块
    track_script {
    	chk_haproxy ## 检查 HAProxy 服务是否存活
    }
	virtual_ipaddress {
		#虚拟IP
		192.168.192.200
	}
}

BackUP:

conf
global_defs {
	notification_email {
		javadct@163.com
	}
	notification_email_from keepalived@showjoy.com
	smtp_server 127.0.0.1
	smtp_connect_timeout 30
	#标识本节点
	router_id haproxy02
	vrrp_skip_check_adv_addr
	vrrp_garp_interval 0
	vrrp_gna_interval 0
}

# keepalived 会定时执行脚本并对脚本执行的结果进行分析,动态调整 vrrp_instance 的优先级
vrrp_script chk_haproxy {
	# 检测 haproxy 状态的脚本路径
	script "/etc/keepalived/haproxy_check.sh"
	#检测时间间隔
	interval 2
	#如果条件成立,权重+2
	weight 2
}

vrrp_instance VI_1 {
	#主机配MASTER,备机配BACKUP
	state BACKUP
	#所在机器网卡
	interface eth1
	virtual_router_id 51
	#数值越大优先级越高
	priority 100
	advert_int 1
	authentication {
		auth_type PASS
		auth_pass 1111
	}
	## 将 track_script 块加入 instance 配置块
    track_script {
    	chk_haproxy ## 检查 HAProxy 服务是否存活
    }
	virtual_ipaddress {
		#虚拟IP
		192.168.192.200
	}
}

8). 编写检测haproxy的shell脚本 haproxy_check.sh

shell
#!/bin/bash

A=`ps -C haproxy --no-header | wc -l`

if [ $A -eq 0 ];then

  /usr/local/haproxy/sbin/haproxy -f /usr/local/haproxy/haproxy.conf

  echo "haproxy restart ..." &> /dev/null

  sleep 1

  if [ `ps -C haproxy --no-header | wc -l` -eq 0 ];then

    /etc/init.d/keepalived stop

    echo "stop keepalived" &> /dev/null

  fi

fi

启动测试

1). 启动Keepalived

service keepalived start

2). 登录验证

mysql -uroot -p123456 -h 192.168.192.200 -P 48066

6.3 PXC集群

简介

Percona XtraDB Cluster(简称PXC)是针对MySQL用户的高可用性和扩展性解决方案,基于Percona Server 。其包括了Write Set REPlication补丁,使用Galera 2.0库,这是一个针对事务性应用程序的同步多主机复制插件。

Percona Server是MySQL的改进版本,使用 XtraDB 存储引擎,在功能和性能上较 MySQL 有着很显著的提升,如提升了在高负载情况下的 InnoDB 的性能,为 DBA 提供了一些非常有用的性能诊断工具,另外有更多的参数和命令来控制服务器行为。

Percona XtraDB Cluster提供了:

  • 同步复制,事务可以在所有节点上提交。
  • 多主机复制,你可以写到任何节点。
  • 从(slave)服务器上的并行应用事件,真正的“并行复制”。
  • 自动节点配置。
  • 数据一致性,不再有未同步的从服务器。

image-20210125235053738

架构

image-20210125235141368

部署安装

节点端口容器名称数据卷
node113306pxc_node1v1
node213307pxc_node2v2
node313308pxc_node3v3

实施

#创建数据卷(存储路径:/var/lib/docker/volumes)
docker volume create v1
docker volume create v2
docker volume create v3
#拉取镜像
docker pull percona/percona-xtradb-cluster:5.7
#重命名
docker tag percona/percona-xtradb-cluster:5.7 pxc
#创建网络
docker network create --subnet=172.30.0.0/24 pxc-network
#创建容器
#第一节点
docker create -p 13306:3306 -v v1:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root -e
CLUSTER_NAME=pxc --name=pxc_node1 --net=pxc-network --ip=172.30.0.2 pxc
#第二节点(增加了CLUSTER_JOIN参数)
docker create -p 13307:3306 -v v2:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root -e
CLUSTER_NAME=pxc --name=pxc_node2 -e CLUSTER_JOIN=pxc_node1 --net=pxc-network --
ip=172.30.0.3 pxc
#第三节点(增加了CLUSTER_JOIN参数)
docker create -p 13308:3306 -v v3:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root -e
CLUSTER_NAME=pxc --name=pxc_node3 -e CLUSTER_JOIN=pxc_node1 --net=pxc-network --
ip=172.30.0.4 pxc
#查看集群节点
show status like 'wsrep_cluster%';

需要注意的是:先启动第一个节点,等到mysql客户端可以连接到服务后再启动其它节点。

说明

  • 尽可能的控制PXC集群的规模,节点越多,数据同步速度越慢
  • 所有PXC节点的硬件配置要一致,如果不一致,配置低的节点将拖慢数据同步速度
  • PXC集群只支持InnoDB引擎,不支持其他的存储引擎

PXC集群方案与Replication区别

  • PXC集群方案所有节点都是可读可写的,Replication从节点不能写入,因为主从同步是单向的,无法从slave节点向master点同步。
  • PXC同步机制是同步进行的,这也是它能保证数据强一致性的根本原因,Replication同步机制是异步进行的,它如果从节点停止同步,依然可以向主节点插入数据,正确返回,造成数据主从数据的不一致性。
  • PXC是用牺牲性能保证数据的一致性,Replication在性能上是高于PXC的。所以两者用途也不一致。PXC是用于重要信息的存储,例如:订单、用户信息等。Replication用于一般信息的存储,能够容忍数据丢失,例如:购物车,用户行为日志等。

6.4 综合应用

架构

image-20210125235727686

说明:

  • HAProxy作为负载均衡器
  • 部署了2个Mycat节点作为数据库中间件
  • 部署了2个PXC集群节点,作为2个Mycat分片,每个PXC集群中有2个节点,作为数据的同步存储
  • 部署了1个主从复制集群
  • 房源数据保存到PXC分片中,其余数据保存到主从架构中

部署PXC集群

集群一:

节点端口容器名称数据卷
node113306pxc_node1haoke-v1
node213307pxc_node2haoke-v2

集群二:

节点端口容器名称数据卷
node313308pxc_node3haoke-v3
node413309pxc_node4haoke-v4
#创建数据卷(存储路径:/var/lib/docker/volumes)
docker volume create haoke-v1
docker volume create haoke-v2
docker volume create haoke-v3
docker volume create haoke-v4
#拉取镜像
docker pull percona/percona-xtradb-cluster:5.7
#创建网络
docker network create --subnet=172.30.0.0/24 pxc-network
#创建容器
#集群1,第一节点
docker create -p 13306:3306 -v haoke-v1:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root -e
CLUSTER_NAME=pxc --name=pxc_node1 --net=pxc-network --ip=172.30.0.2 pxc
#第二节点(增加了CLUSTER_JOIN参数)
docker create -p 13307:3306 -v haoke-v2:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root -e
CLUSTER_NAME=pxc --name=pxc_node2 -e CLUSTER_JOIN=pxc_node1 --net=pxc-network --
ip=172.30.0.3 pxc
#集群2
#第一节点
docker create -p 13308:3306 -v haoke-v3:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root -e
CLUSTER_NAME=pxc --name=pxc_node3 --net=pxc-network --ip=172.30.0.4 pxc
#第二节点(增加了CLUSTER_JOIN参数)
docker create -p 13309:3306 -v haoke-v4:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root -e
CLUSTER_NAME=pxc --name=pxc_node4 -e CLUSTER_JOIN=pxc_node3 --net=pxc-network --
ip=172.30.0.5 pxc
#启动
docker start pxc_node1 && docker logs -f pxc_node1
docker start pxc_node2 && docker logs -f pxc_node2
docker start pxc_node3 && docker logs -f pxc_node3
docker start pxc_node4 && docker logs -f pxc_node4
#查看集群节点
show status like 'wsrep_cluster%';

部署主从复制集群

master:

#创建目录
mkdir /data/mysql/haoke/master01 -p
cd /data/mysql/haoke/master01
mkdir conf data
chmod 777 * -R
#创建配置文件
cd /data/mysql/haoke/master01/conf
vim my.cnf
#输入如下内容
[mysqld]
log-bin=mysql-bin #开启二进制日志
server-id=1 #服务id,不可重复
sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO
,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'
#创建容器
docker create --name percona-haoke-master01 -v
/data/mysql/haoke/master01/data:/var/lib/mysql -v
/data/mysql/haoke/master01/conf:/etc/my.cnf.d -p 23306:3306 -e
MYSQL_ROOT_PASSWORD=root percona:5.7.23
#启动
docker start percona-haoke-master01 && docker logs -f percona-haoke-master01
#创建同步账户以及授权
create user 'itcast'@'%' identified by 'itcast';
grant replication slave on *.* to 'itcast'@'%';
flush privileges;
#查看master状态
show master status;

slave:

#创建目录
mkdir /data/mysql/haoke/slave01 -p
cd /data/mysql/haoke/slave01
mkdir conf data
chmod 777 * -R
#创建配置文件
cd /data/mysql/haoke/slave01/conf
vim my.cnf
#输入如下内容
[mysqld]
server-id=2 #服务id,不可重复
sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO
,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'
#创建容器
docker create --name percona-haoke-slave01 -v
/data/mysql/haoke/slave01/data:/var/lib/mysql -v
/data/mysql/haoke/slave01/conf:/etc/my.cnf.d -p 23307:3306 -e
MYSQL_ROOT_PASSWORD=root percona:5.7.23
#启动
docker start percona-haoke-slave01 && docker logs -f percona-haoke-slave01
#设置master相关信息
CHANGE MASTER TO
master_host='192.168.1.18',
master_user='itcast',
master_password='itcast',
master_port=23306,
master_log_file='mysql-bin.000002',
master_log_pos=648;
#启动同步
start slave;
#查看master状态
show slave status;

部署Mycat

节点一

cd /data/
mkdir mycat
cp /haoke/mycat . -R
mv mycat/ mycat-node1
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:server SYSTEM "server.dtd">
<mycat:server xmlns:mycat="http://io.mycat/">
<system>
<property name="nonePasswordLogin">0</property>
<property name="useHandshakeV10">1</property>
<property name="useSqlStat">0</property>
<property name="useGlobleTableCheck">0</property>
<property name="sequnceHandlerType">2</property>
<property name="subqueryRelationshipCheck">false</property>
<property name="processorBufferPoolType">0</property>
<property name="handleDistributedTransactions">0</property>
<property name="useOffHeapForMerge">1</property>
<property name="memoryPageSize">64k</property>
<property name="spillsFileBufferSize">1k</property>
<property name="useStreamOutput">0</property>
<property name="systemReserveMemorySize">384m</property>
<property name="useZKSwitch">false</property>
</system>
<!--这里是设置的itcast用户和虚拟逻辑库-->
<user name="itcast" defaultAccount="true">
<property name="password">itcast123</property>
<property name="schemas">haoke</property>
</user>
</mycat:server>

schema.xml:

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!--配置数据表-->
<schema name="haoke" checkSQLschema="false" sqlMaxLimit="100">
<table name="tb_house_resources" dataNode="dn1,dn2" rule="mod-long" />
<table name="tb_ad" dataNode="dn3"/>
</schema>
<!--配置分片关系-->
<dataNode name="dn1" dataHost="cluster1" database="haoke" />
<dataNode name="dn2" dataHost="cluster2" database="haoke" />
<dataNode name="dn3" dataHost="cluster3" database="haoke" />
<!--配置连接信息-->
<dataHost name="cluster1" maxCon="1000" minCon="10" balance="2"
writeType="1" dbType="mysql" dbDriver="native" switchType="1"
slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="W1" url="192.168.1.18:13306" user="root"
password="root">
<readHost host="W1R1" url="192.168.1.18:13307" user="root"
password="root" />
</writeHost>
</dataHost>
<dataHost name="cluster2" maxCon="1000" minCon="10" balance="2"
writeType="1" dbType="mysql" dbDriver="native" switchType="1"
slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="W2" url="192.168.1.18:13308" user="root"
password="root">
<readHost host="W2R1" url="192.168.1.18:13309" user="root"
password="root" />
</writeHost>
</dataHost>
<dataHost name="cluster3" maxCon="1000" minCon="10" balance="3"
writeType="1" dbType="mysql" dbDriver="native" switchType="1"
slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="W3" url="192.168.1.18:23306" user="root"
password="root">
<readHost host="W3R1" url="192.168.1.18:23307" user="root"
password="root" />
</writeHost>
</dataHost>
</mycat:schema>

rule.xml:

<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<property name="count">2</property>
</function>

设置端口以及启动:

vim wrapper.conf
#设置jmx端口
wrapper.java.additional.7=-Dcom.sun.management.jmxremote.port=11985
vim server.xml
#设置服务端口以及管理端口
<property name="serverPort">18067</property>
<property name="managerPort">19067</property>
./startup_nowrap.sh && tail -f ../logs/mycat.log

节点二

cp mycat-node1/ mycat-node2 -R
vim wrapper.conf
#设置jmx端口
wrapper.java.additional.7=-Dcom.sun.management.jmxremote.port=11986
vim server.xml
#设置服务端口以及管理端口
<property name="serverPort">18068</property>
<property name="managerPort">19068</property>
./startup_nowrap.sh && tail -f ../logs/mycat.log

部署HAProxy

修改配置文件:

#修改文件
vim /haoke/haproxy/haproxy.cfg
#输入如下内容
global
log 127.0.0.1 local2
maxconn 4000
daemon
defaults
mode http
log global
option httplog
option dontlognull
option http-server-close
option forwardfor except 127.0.0.0/8
option redispatch
retries 3
timeout http-request 10s
timeout queue 1m
timeout connect 10s
timeout client 1m
timeout server 1m
timeout http-keep-alive 10s
timeout check 10s
maxconn 3000
listen admin_stats
bind 0.0.0.0:4001
mode http
stats uri /dbs
stats realm Global\ statistics
stats auth admin:admin123
listen proxy-mysql
bind 0.0.0.0:4002
mode tcp
balance roundrobin
option tcplog
#代理mycat服务
server mycat_1 192.168.1.18:18067 check port 18067 maxconn 2000
server mycat_2 192.168.1.18:18068 check port 18068 maxconn 2000

启动容器:

#启动容器
docker start haproxy && docker logs -f haproxy