MyCat使用指北
本文最后更新于:2021年6月23日 晚上
一、MyCat概述
(1)为什么要用MyCat
- 大表水平拆分,提高系统系统:将存放在同一个数据库的数据,分散存储在多个数据库中,以达到分散单台设备负载的效果。当数据库量超过800万行且需要做分片时,就可以考虑使用MyCat实现数据切分。
- 轻松实现读写分离:通过MySQL数据库的binlog日志完成主从复制,并可以通过MyCat轻松实现读写分离,实现insert、update、delete走主库,而在select时走从库,从而缓解单台服务器的访问压力。
- 高可用性:利用MyCat可以轻松实现热备份,当一台服务器停机时,可以由集群中的另一台服务器自动接管业务,无需人工干预,从而保证高可用
- 整合多种数据源:当一个项目中使用了多个数据库(Oracle,MySQL,SQL Server,PostgreSQL),并配置了多个数据源,操作起来就比较烦锁,这时就可以使用MyCat进行整合,最终我们的应用程序只需要访问一个数据源即可。
(2)安装MyCat
先安装Java环境
A. 上传JDK的安装包到Linux的root目录下 alt + p -----------> put D:/jdk-8u181-linux-x64.tar.gz B. 解压压缩包 , 到 /usr/share 目录下 tar -zxvf jdk-8u181-linux-x64.tar.gz -C /usr/share/ C. 配置PATH环境变量 , 在该配置文件(/etc/profile)的最后加入如下配置 export JAVA_HOME=/usr/share/jdk1.8.0_181 export PATH=$PATH:$JAVA_HOME/bin
上传MyCat的压缩包,并解压
alt + p --------> put D:/Mycat-server-1.6.7.3-release-20190927161129-linux.tar.gz tar -zxvf Mycat-server-1.6.7.3-release-20190927161129-linux.tar.gz -C /usr/local
MyCat的目录结构介绍
(3)MyCat 原理介绍
MyCat最关键的一点在于,拦截用户发送过来的SQL语句
,然后对这些SQL语句进行特定的分析,如分片分析、路由分析、读写分离分析等,然后将此SQL语句发往后端的真实数据库,并将返回的结果做适当处理,最终再返回给用户。
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对应的主机上执行, 然后将结果集合并后输出给用户。
(4)MyCat核心概念
⚡ 分片
简单来说,就是指通过某种特定的条件,将我们存放在同一个数据库中的数据分散存放到多个数据库(主机)上面,以达到分散单台设备负载的效果。
垂直拆分
:将不同的表切分到不同的数据库(主机)上。tableA和tableB分散在不同的服务器中水平拆分
:将同一个表中的数据拆分到多个数据库(主机)上。tableA的部分数据在DataNode1,部分数据在DataNode2上
⚡ 逻辑库(schema)
数据库中间件MyCat,可以被看做是一个或多个数据库集群构成的逻辑库。
⚡ 逻辑表(table)
读写数据的表就是逻辑表。
分片表:需要切分到多个数据库的表
非分片表:一些比较小、不需要进行数据切分的表。
ER表:ER指实体关系模型(Entity Relationship Model), MyCat中有一种基于ER关系的数据分片策略 , 子表的记录与其所关联的父表的记录存放在同一个数据分片中,通过表分组(Table Group)保证数据关联查询不会跨库操作。
全局表:在一个大型的项目中,会存在一部分字典表(码表) , 在其中存储的是项目中的一些基础的数据 , 而这些基础的数据 , 数据量都不大 , 在各个业务表中可能都存在关联 。当业务表由于数据量大而分片后 , 业务表与附属的数据字典表之间的关联查询就变成了比较棘手的问题 , 在MyCat中可以通过数据冗余来解决这类表的关联查询 , 即所有分片都复制这一份数据(数据字典表),因此可以把这些冗余数据的表定义为全局表。
⚡ 分片节点(dataNode)
数据被切分后,一个大表被分到不同的分片数据库上面,每个表分片所在的数据库就是分片节点(dataNode)。
⚡ 节点主机(dataHost)
数据切分后,每个分片节点不一定都会独占一台机器,同一机器上面可以有多个分片数据库。
分片节点所在的真实机器就是节点主机(dataHost),为了规避单节点主机并发数限制,尽量将读写压力高的分片节点均衡的放在不同的节点主机。
(4) 分片配置测试
2.3.1 需求
由于 TB_TEST 表中数据量很大, 现在需要对 TB_TEST 表进行数据分片, 分为三个数据节点 , 每一个节点主机位于不同的服务器上, 具体的结构 ,参考下图 :
2.3.2 环境准备
准备三台虚拟机 , 且安装好MySQL , 并配置好 :
IP 地址列表 :
192.168.192.157
192.168.192.158
192.168.192.159
2.3.3 配置 schema.xml
schema.xml 作为MyCat中重要的配置文件之一,管理着MyCat的逻辑库、逻辑表以及对应的分片规则、DataNode以及DataSource。弄懂这些配置,是正确使用MyCat的前提。这里就一层层对该文件进行解析。
属性 | 含义 |
---|---|
schema | 标签用于定义MyCat实例中的逻辑库 |
table | 标签定义了MyCat中的逻辑表, rule用于指定分片规则,auto-sharding-long的分片规则是按ID值的范围进行分片 1-5000000 为第1片 5000001-10000000 为第2片…. 具体设置我们会在第四节中讲解。 |
dataNode | 标签定义了MyCat中的数据节点,也就是我们通常说所的数据分片。 |
dataHost | 标签在mycat逻辑库中也是作为最底层的标签存在,直接定义了具体的数据库实例、读写分离配置和心跳语句。 |
在服务器上创建3个数据库,命名为 db1
修改schema.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_TEST" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
</schema>
<!-- 数据节点配置 -->
<dataNode name="dn1" dataHost="host1" database="db1" />
<dataNode name="dn2" dataHost="host2" database="db1" />
<dataNode name="dn3" dataHost="host3" database="db1" />
<!-- 节点主机配置 -->
<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="hostM1" 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="hostM1" url="192.168.192.159:3306" user="root" password="itcast"></writeHost>
</dataHost>
</mycat:schema>
2.3.4 配置 server.xml
server.xml几乎保存了所有mycat需要的系统配置信息。最常用的是在此配置用户名、密码及权限。在system中添加UTF-8字符集设置,否则存储中文会出现问号
<property name="charset">utf8</property>
修改user的设置 , 我们这里为 ITCAST 设置了两个用户 :
<user name="root">
<property name="password">123456</property>
<property name="schemas">ITCAST</property>
</user>
<user name="test">
<property name="password">123456</property>
<property name="schemas">ITCAST</property>
</user>
并且需要将原来的逻辑库的配置 , 替换为 ITCAST逻辑库 ;
2.3.5 启动MyCat
启动:
bin/mycat start
bin/mycat stop
bin/mycat status
查看MyCat:
连接端口号 8066
1). 通过命令行
mysql -h 127.0.0.1 -P 8066 -u root -p
2). 通过sqlyog连接
2.3.6 MyCat分片测试
进入mycat ,执行下列语句创建一个表
CREATE TABLE TB_TEST (
id BIGINT(20) NOT NULL,
title VARCHAR(100) NOT NULL ,
PRIMARY KEY (id)
) ENGINE=INNODB DEFAULT CHARSET=utf8 ;
我们再查看MySQL的3个库,发现表都自动创建好啦。好神奇。
接下来是插入表数据,注意,在写 INSERT 语句时一定要写把字段列表写出来,否则会出现下列错误提示:
错误代码: 1064
partition table, insert must provide ColumnList
我们试着插入一些数据:
INSERT INTO TB_TEST(ID,TITLE) VALUES(1,'goods1');
INSERT INTO TB_TEST(ID,TITLE) VALUES(2,'goods2');
INSERT INTO TB_TEST(ID,TITLE) VALUES(3,'goods3');
我们会发现这些数据被写入到第一个节点中了,那什么时候数据会写到第二个节点中呢?
我们插入下面的数据就可以插入第二个节点了
INSERT INTO TB_TEST(ID,TITLE) VALUES(5000001,'goods5000001');
因为我们采用的分片规则是每节点存储500万条数据,所以当ID大于5000000则会存储到第二个节点上。
目前只设置了两个节点,如果数据大于1000万条,会怎么样呢?执行下列语句测试一下
INSERT INTO TB_TEST(ID,TITLE) VALUES(10000001,'goods10000001');
二、MyCat配置文件
(1)server.xml
⚡ system 标签
属性 | 取值 | 含义 |
---|---|---|
charset | utf8 | 设置Mycat的字符集, 字符集需要与MySQL的字符集保持一致 |
nonePasswordLogin | 0,1 | 0为需要密码登陆、1为不需要密码登陆 ,默认为0,设置为1则需要指定默认账户 |
useHandshakeV10 | 0,1 | 使用该选项主要的目的是为了能够兼容高版本的jdbc驱动, 是否采用HandshakeV10Packet来与client进行通信, 1:是, 0:否 |
useSqlStat | 0,1 | 开启SQL实时统计, 1 为开启 , 0 为关闭 ; 开启之后, MyCat会自动统计SQL语句的执行情况 ; 查看MyCat执行的SQL, 执行效率比较低的SQL , SQL的整体执行情况、读写比例等 ; show @@sql ; show @@sql.slow ; show @@sql.sum ; |
useGlobleTableCheck | 0,1 | 是否开启全局表的一致性检测。1为开启 ,0为关闭 。 |
sqlExecuteTimeout | 1000 | SQL语句执行的超时时间 , 单位为 s ; |
sequnceHandlerType | 0,1,2 | 用来指定Mycat全局序列类型,0 为本地文件,1 为数据库方式,2 为时间戳列方式,默认使用本地文件方式,文件方式主要用于测试 |
sequnceHandlerPattern | 正则表达式 | 必须带有MYCATSEQ或者 mycatseq进入序列匹配流程 注意MYCATSEQ_有空格的情况 |
subqueryRelationshipCheck | true,false | 子查询中存在关联查询的情况下,检查关联字段中是否有分片字段 .默认 false |
useCompression | 0,1 | 开启mysql压缩协议 , 0 : 关闭, 1 : 开启 |
fakeMySQLVersion | 5.5,5.6 | 设置模拟的MySQL版本号 |
defaultSqlParser | 由于MyCat的最初版本使用了FoundationDB的SQL解析器, 在MyCat1.3后增加了Druid解析器, 所以要设置defaultSqlParser属性来指定默认的解析器; 解析器有两个 : druidparser 和 fdbparser, 在MyCat1.4之后,默认是druidparser, fdbparser已经废除了 | |
processors | 1,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 | |
idleTimeout | 30 | 指定连接的空闲时间的超时长度;如果超时,将关闭资源并回收, 默认30分钟 |
txIsolation | 1,2,3,4 | 初始化前端连接的事务隔离级别,默认为 REPEATED_READ , 对应数字为3 READ_UNCOMMITED=1; READ_COMMITTED=2; REPEATED_READ=3; SERIALIZABLE=4; |
sqlExecuteTimeout | 300 | 执行SQL的超时时间, 如果SQL语句执行超时,将关闭连接; 默认300秒; |
serverPort | 8066 | 定义MyCat的使用端口, 默认8066 |
managerPort | 9066 | 定义MyCat的管理端口, 默认9066 |
⚡ user 标签
<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). \
2). \
3). \
4). \
5). \
6). \
7). \
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黑名单。
<firewall>
<!-- 白名单配置 -->
<whitehost>
<host user="root" host="127.0.0.1"></host>
</whitehost>
<!-- 黑名单配置 -->
<blacklist check="true">
<property name="selelctAllow">false</property>
</blacklist>
</firewall>
黑名单拦截明细配置:
配置项 | 缺省值 | 描述 | |
---|---|---|---|
selelctAllow | true | 是否允许执行 SELECT 语句 | |
selectAllColumnAllow | true | 是否允许执行 SELECT FROM T 这样的语句。如果设置为 false,不允许执行 select from t,但可以select * from (select id, name from t) a。 | |
selectIntoAllow | true | SELECT 查询中是否允许 INTO 字句 | |
deleteAllow | true | 是否允许执行 DELETE 语句 | |
updateAllow | true | 是否允许执行 UPDATE 语句 | |
insertAllow | true | 是否允许执行 INSERT 语句 | |
replaceAllow | true | 是否允许执行 REPLACE 语句 | |
mergeAllow | true | 是否允许执行 MERGE 语句,这个只在 Oracle 中有用 | |
callAllow | true | 是否允许通过 jdbc 的 call 语法调用存储过程 | |
setAllow | true | 是否允许使用 SET 语法 | |
truncateAllow | true | truncate 语句是危险,缺省打开,若需要自行关闭 | |
createTableAllow | true | 是否允许创建表 | |
alterTableAllow | true | 是否允许执行 Alter Table 语句 | |
dropTableAllow | true | 是否允许修改表 | |
commentAllow | false | 是否允许语句中存在注释,Oracle 的用户不用担心,Wall 能够识别 hints和注释的区别 | |
noneBaseStatementAllow | false | 是否允许非以上基本语句的其他语句,缺省关闭,通过这个选项就能够屏蔽 DDL。 | |
multiStatementAllow | false | 是否允许一次执行多条语句,缺省关闭 | |
useAllow | true | 是否允许执行 mysql 的 use 语句,缺省打开 | |
describeAllow | true | 是否允许执行 mysql 的 describe 语句,缺省打开 | |
showAllow | true | 是否允许执行 mysql 的 show 语句,缺省打开 | |
commitAllow | true | 是否允许执行 commit 操作 | |
rollbackAllow | true | 是否允许执行 roll back 操作 | |
拦截配置-永真条件 | |||
selectWhereAlwayTrueCheck | true | 检查 SELECT 语句的 WHERE 子句是否是一个永真条件 | |
selectHavingAlwayTrueCheck | true | 检查 SELECT 语句的 HAVING 子句是否是一个永真条件 | |
deleteWhereAlwayTrueCheck | true | 检查 DELETE 语句的 WHERE 子句是否是一个永真条件 | |
deleteWhereNoneCheck | false | 检查 DELETE 语句是否无 where 条件,这是有风险的,但不是 SQL 注入类型的风险 | |
updateWhereAlayTrueCheck | true | 检查 UPDATE 语句的 WHERE 子句是否是一个永真条件 | |
updateWhereNoneCheck | false | 检查 UPDATE 语句是否无 where 条件,这是有风险的,但不是SQL 注入类型的风险 | |
conditionAndAlwayTrueAllow | false | 检查查询条件(WHERE/HAVING 子句)中是否包含 AND 永真条件 | |
conditionAndAlwayFalseAllow | false | 检查查询条件(WHERE/HAVING 子句)中是否包含 AND 永假条件 | |
conditionLikeTrueAllow | true | 检查查询条件(WHERE/HAVING 子句)中是否包含 LIKE 永真条件 | |
其他拦截配置 | |||
selectIntoOutfileAllow | false | SELECT … INTO OUTFILE 是否允许,这个是 mysql 注入攻击的常见手段,缺省是禁止的 | |
selectUnionCheck | true | 检测 SELECT UNION | |
selectMinusCheck | true | 检测 SELECT MINUS | |
selectExceptCheck | true | 检测 SELECT EXCEPT | |
selectIntersectCheck | true | 检测 SELECT INTERSECT | |
mustParameterized | false | 是否必须参数化,如果为 True,则不允许类似 WHERE ID = 1 这种不参数化的 SQL | |
strictSyntaxCheck | true | 是否进行严格的语法检测,Druid SQL Parser 在某些场景不能覆盖所有的SQL 语法,出现解析 SQL 出错,可以临时把这个选项设置为 false,同时把 SQL 反馈给 Druid 的开发者。 | |
conditionOpXorAllow | false | 查询条件中是否允许有 XOR 条件。XOR 不常用,很难判断永真或者永假,缺省不允许。 | |
conditionOpBitwseAllow | true | 查询条件中是否允许有”&”、”~”、”\ | “、”^”运算符。 |
conditionDoubleConstAllow | false | 查询条件中是否允许连续两个常量运算表达式 | |
minusAllow | true | 是否允许 SELECT FROM A MINUS SELECT FROM B 这样的语句 | |
intersectAllow | true | 是否允许 SELECT FROM A INTERSECT SELECT FROM B 这样的语句 | |
constArithmeticAllow | true | 拦截常量运算的条件,比如说 WHERE FID = 3 - 1,其中”3 - 1”是常量运算表达式。 | |
limitZeroAllow | false | 是否允许 limit 0 这样的语句 | |
禁用对象检测配置 | |||
tableCheck | true | 检测是否使用了禁用的表 | |
schemaCheck | true | 检测是否使用了禁用的 Schema | |
functionCheck | true | 检测是否使用了禁用的函数 | |
objectCheck | true | 检测是否使用了“禁用对对象” | |
variantCheck | true | 检测是否使用了“禁用的变量” | |
readOnlyTables | 空 | 指定的表只读,不能够在 SELECT INTO、DELETE、UPDATE、INSERT、MERGE 中作为”被修改表”出现 |
(2)schema.xml
schema.xml 作为MyCat中最重要的配置文件之一 , 涵盖了MyCat的逻辑库 、 表 、 分片规则、分片节点及数据源的配置。
⚡ schema 标签
<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标签中定义 。
<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 标签
<dataNode name="dn1" dataHost="host1" database="db1" />
dataNode标签中定义了MyCat中的数据节点, 也就是我们通常说的数据分片。一个dataNode标签就是一个独立的数据分片。
具体的属性 :
属性 | 含义 | 描述 |
---|---|---|
name | 数据节点的名称 | 需要唯一 ; 在table标签中会引用这个名字, 标识表与分片的对应关系 |
dataHost | 数据库实例主机名称 | 引用自 dataHost 标签中name属性 |
database | 定义分片所属的数据库 |
⚡ dataHost 标签
<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。MySQL和MariaDB使用native。其他数据库需要使用JDBC |
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 是 |
(3)rule.xml
rule.xml中定义所有拆分表的规则, 在使用过程中可以灵活的使用分片算法, 或者对同一个分片算法使用不同的参数, 它让分片过程可配置化。
⚡ tableRule标签
<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标签
<function name="rang-long" class="io.mycat.route.function.AutoPartitionByLong">
<property name="mapFile">autopartition-long.txt</property>
</function>
A. name : 指定算法名称, 该文件中唯一
B. class : 指定算法的具体类
C. property : 根据算法的要求执行
(4)全局序列
在分库分表的情况下 , 原有的自增主键已无法满足在集群中全局唯一的主键 ,因此, MyCat中提供了全局sequence来实现主键 , 并保证全局唯一。那么在MyCat的配置文件 sequence_conf.properties 中就配置的是序列的相关配置。
主要包含以下几种形式:
1). 本地文件方式:sequence_conf.properties 文件中 sequence 当前的值
2). 数据库方式:利用数据库一个表 来进行计数累加
- Mycat 会预加载一部分号段到Mycat 的内存中,这样大部分读写序列都是在内存中完成的
3). 本地时间戳方式
4). 自主生成全局序列
- 根据业务逻辑组合
- 利用 redis 的单线程原子性 incr 来生成序列
三、 MyCat分片
(1)垂直拆分:分库
⚡ 案例场景
在业务系统中, 有以下表结构 ,但是由于用户与订单每天都会产生大量的数据, 单台服务器的数据存储及处理能力是有限的, 可以对数据库表进行拆分, 原有的数据库表:
分库的原则:有紧密关联关系的表应该在一个库里,相互没有关联关系的表可以分到不同的库里。
⚡ 构建场景
1). 准备三台数据库实例
192.168.192.157
192.168.192.158
192.168.192.159
2). 在三台数据库实例中建库建表
将准备好的三个SQL脚本, 分别导入到三台MySQL实例中 ;
登录MySQL数据库之后, 使用source命令导入 ;
⚡ 配置schema.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
<!-- 定义Mycat的登陆用户及其权限 -->
<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>
⚡ 测试
1). 查询数据
select * from tb_goods_base;
select * from tb_user;
select * from tb_order_master;
2). 插入数据
insert into tb_user_address(id,user_id,province_id,city_id,town_id,mobile,address,contact,is_default,notes,create_date,alias) values (null,'java00001',NULL,NULL,NULL,'13900112222','钟楼','张三','0',NULL,NULL,NULL)
insert into tb_order_item(id,item_id,goods_id,order_id,title,price,num,total_fee,pic_path,seller_id) values (null,19,149187842867954,3,'3G 6','1.00',5,'5.00',NULL,'qiandu')
3). 测试跨分片的查询
SELECT order_id , payment ,receiver, province , city , area FROM tb_order_master o , tb_areas_provinces p , tb_areas_city c , tb_areas_region r
WHERE o.receiver_province = p.provinceid AND o.receiver_city = c.cityid AND o.receiver_region = r.areaid ;
当运行上述的SQL语句时, MyCat会报错, 原因是因为当前SQL语句涉及到跨域的join操作 ;
⚡ 全局表配置
1). 将数据节点user_db中的关联的字典表 tb_areas_provinces , tb_areas_city , tb_areas_region中的数据备份 ;
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). 将备份的表结构及数据信息, 远程同步到其他两个数据节点的数据库中;
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逻辑表中的配置
<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
6). 测试
再次执行相同的连接查询 , 是可以正常查询出对应的数据的 ;
SELECT order_id , payment ,receiver, province , city , area FROM tb_order_master o , tb_areas_provinces p , tb_areas_city c , tb_areas_region r
WHERE o.receiver_province = p.provinceid AND o.receiver_city = c.cityid AND o.receiver_region = r.areaid ;
当我们对Mycat全局表进行增删改的操作时, 其他节点主机上的后端MySQL数据库中的数据时会同步变化的;
update tb_areas_city set city = '石家庄' where id = 5;
(2)水平拆分:分表
⚡ 案例场景
在业务系统中, 有一张表(日志表), 业务系统每天都会产生大量的日志数据 , 单台服务器的数据存储及处理能力是有限的, 可以对数据库表进行拆分, 原有的数据库表拆分成以下表 :
⚡ 准备工作
1). 准备三台数据库实例
192.168.192.157
192.168.192.158
192.168.192.159
2). 在三台数据库实例中创建数据库
create database log_db DEFAULT CHARACTER SET utf8mb4;
⚡ 配置schema.xml
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!-- 将tb_log中的数据,分散到dn1~dn3这三个数据节点上;分片规则为mod-long -->
<schema name="LOG_DB" checkSQLschema="false" sqlMaxLimit="100">
<table name="tb_log" dataNode="dn1,dn2,dn3" primaryKey="id" rule="mod-long" />
</schema>
<dataNode name="dn1" dataHost="host1" database="log_db" />
<dataNode name="dn2" dataHost="host2" database="log_db" />
<dataNode name="dn3" dataHost="host3" database="log_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
<user name="root" defaultAccount="true">
<property name="password">123456</property>
<property name="schemas">LOG_DB</property>
</user>
<user name="test">
<property name="password">123456</property>
<property name="schemas">LOG_DB</property>
</user>
<user name="user">
<property name="password">123456</property>
<property name="schemas">LOG_DB</property>
<property name="readOnly">true</property>
</user>
⚡ 测试
1). 在MyCat中执行建表语句,此时三个数据节点中均会创建该表
CREATE TABLE `tb_log` (
`id` bigint(20) NOT NULL COMMENT 'ID',
`model_name` varchar(200) DEFAULT NULL COMMENT '模块名',
`model_value` varchar(200) DEFAULT NULL COMMENT '模块值',
`return_value` varchar(200) DEFAULT NULL COMMENT '返回值',
`return_class` varchar(200) DEFAULT NULL COMMENT '返回值类型',
`operate_user` varchar(20) DEFAULT NULL COMMENT '操作用户',
`operate_time` varchar(20) DEFAULT NULL COMMENT '操作时间',
`param_and_value` varchar(500) DEFAULT NULL COMMENT '请求参数名及参数值',
`operate_class` varchar(200) DEFAULT NULL COMMENT '操作类',
`operate_method` varchar(200) DEFAULT NULL COMMENT '操作方法',
`cost_time` bigint(20) DEFAULT NULL COMMENT '执行方法耗时, 单位 ms',
`source` int(1) DEFAULT NULL COMMENT '来源 : 1 PC , 2 Android , 3 IOS',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2). 插入数据
INSERT INTO `tb_log` (`id`, `model_name`, `model_value`, `return_value`, `return_class`, `operate_user`, `operate_time`, `param_and_value`, `operate_class`, `operate_method`, `cost_time`,`source`) VALUES('1','user','insert','success','java.lang.String','10001','2020-02-26 18:12:28','{\"age\":\"20\",\"name\":\"Tom\",\"gender\":\"1\"}','cn.itcast.controller.UserController','insert','10',1);
INSERT INTO `tb_log` (`id`, `model_name`, `model_value`, `return_value`, `return_class`, `operate_user`, `operate_time`, `param_and_value`, `operate_class`, `operate_method`, `cost_time`,`source`) VALUES('2','user','insert','success','java.lang.String','10001','2020-02-26 18:12:27','{\"age\":\"20\",\"name\":\"Tom\",\"gender\":\"1\"}','cn.itcast.controller.UserController','insert','23',1);
INSERT INTO `tb_log` (`id`, `model_name`, `model_value`, `return_value`, `return_class`, `operate_user`, `operate_time`, `param_and_value`, `operate_class`, `operate_method`, `cost_time`,`source`) VALUES('3','user','update','success','java.lang.String','10001','2020-02-26 18:16:45','{\"age\":\"20\",\"name\":\"Tom\",\"gender\":\"1\"}','cn.itcast.controller.UserController','update','34',1);
INSERT INTO `tb_log` (`id`, `model_name`, `model_value`, `return_value`, `return_class`, `operate_user`, `operate_time`, `param_and_value`, `operate_class`, `operate_method`, `cost_time`,`source`) VALUES('4','user','update','success','java.lang.String','10001','2020-02-26 18:16:45','{\"age\":\"20\",\"name\":\"Tom\",\"gender\":\"1\"}','cn.itcast.controller.UserController','update','13',2);
INSERT INTO `tb_log` (`id`, `model_name`, `model_value`, `return_value`, `return_class`, `operate_user`, `operate_time`, `param_and_value`, `operate_class`, `operate_method`, `cost_time`,`source`) VALUES('5','user','insert','success','java.lang.String','10001','2020-02-26 18:30:31','{\"age\":\"200\",\"name\":\"TomCat\",\"gender\":\"0\"}','cn.itcast.controller.UserController','insert','29',3);
INSERT INTO `tb_log` (`id`, `model_name`, `model_value`, `return_value`, `return_class`, `operate_user`, `operate_time`, `param_and_value`, `operate_class`, `operate_method`, `cost_time`,`source`) VALUES('6','user','find','success','java.lang.String','10001','2020-02-26 18:30:31','{\"age\":\"200\",\"name\":\"TomCat\",\"gender\":\"0\"}','cn.itcast.controller.UserController','find','29',2);
(3)常见分片规则
MyCat的分片规则配置在conf目录下的rule.xml文件中定义 ;
环境准备 :
1). schema.xml中的内容做好备份 , 并配置逻辑库;
<schema name="PARTITION_DB" checkSQLschema="false" sqlMaxLimit="100">
<table name="" dataNode="dn1,dn2,dn3" rule=""/>
</schema>
<dataNode name="dn1" dataHost="host1" database="partition_db" />
<dataNode name="dn2" dataHost="host2" database="partition_db" />
<dataNode name="dn3" dataHost="host3" database="partition_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>
2). 在MySQL的三个节点的数据库中 , 创建数据库partition_db
create database partition_db DEFAULT CHARACTER SET utf8mb4;
⚡ mod-long 取模分片
<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 | 数据节点的数量 |
⚡ rang-long 范围分片
根据指定的字段及其配置的范围与数据节点的对应情况, 来决定该数据属于哪一个分片 , 配置如下:
<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 配置如下:
# range start-end ,data node index
# 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 | 默认节点 默认节点的所用:枚举分片时,如果碰到不识别的枚举值, 就让它路由到默认节点 ; 如果没有默认值,碰到不识别的则报错 。 |
测试:
<table name="tb_log" dataNode="dn1,dn2,dn3" rule="auto-sharding-long"/>
1). 创建表
CREATE TABLE `tb_log` (
id bigint(20) NOT NULL COMMENT 'ID',
operateuser varchar(200) DEFAULT NULL COMMENT '姓名',
operation int(2) DEFAULT NULL COMMENT '1: insert, 2: delete, 3: update , 4: select',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2). 插入数据
insert into tb_log (id,operateuser ,operation) values(1,'Tom',1);
insert into tb_log (id,operateuser ,operation) values(2,'Cat',2);
insert into tb_log (id,operateuser ,operation) values(3,'Rose',3);
insert into tb_log (id,operateuser ,operation) values(4,'Coco',2);
insert into tb_log (id,operateuser ,operation) values(5,'Lily',1);
⚡ hash-int 枚举分片
通过在配置文件中配置可能的枚举值, 指定数据分布到不同数据节点上, 本规则适用于按照省份或状态等字段拆分数据等业务 , 配置如下:
<tableRule name="sharding-by-intfile">
<rule>
<columns>aeracode</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代表设置默认节点 ; 默认节点的所用:枚举分片时,如果碰到不识别的枚举值, 就让它路由到默认节点 ; 如果没有默认值,碰到不识别的则报错 。 |
测试:
<table name="tb_user" dataNode="dn1,dn2,dn3" rule="sharding-by-enum-status"/>
# 创建表
CREATE TABLE `tb_user` (
id bigint(20) NOT NULL COMMENT 'ID',
username varchar(200) DEFAULT NULL COMMENT '姓名',
status int(2) DEFAULT '1' COMMENT '1: 未启用, 2: 已启用, 3: 已关闭',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# 插入数据
insert into tb_user (id,username ,status) values(1,'Tom',1);
insert into tb_user (id,username ,status) values(2,'Cat',2);
insert into tb_user (id,username ,status) values(3,'Rose',3);
insert into tb_user (id,username ,status) values(4,'Coco',2);
insert into tb_user (id,username ,status) values(5,'Lily',1);
⚡ rang-mod 范围求模算法
该算法为先进行范围分片, 计算出分片组 , 再进行组内求模。
优点: 综合了范围分片和求模分片的优点。 分片组内使用求模可以保证组内的数据分布比较均匀, 分片组之间采用范围分片可以兼顾范围分片的特点。
缺点: 在数据范围时固定值(非递增值)时,存在不方便扩展的情况,例如将 dataNode Group size 从 2 扩展为 4 时,需要进行数据迁移才能完成 ; 如图所示:
配置如下:
<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开始 |
测试:
配置
<table name="tb_stu" dataNode="dn1,dn2,dn3" rule="auto-sharding-rang-mod"/>
数据
1). 创建表
CREATE TABLE `tb_stu` (
id bigint(20) NOT NULL COMMENT 'ID',
username varchar(200) DEFAULT NULL COMMENT '姓名',
status int(2) DEFAULT '1' COMMENT '1: 未启用, 2: 已启用, 3: 已关闭',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2). 插入数据
insert into tb_stu (id,username ,status) values(1,'Tom',1);
insert into tb_stu (id,username ,status) values(2,'Cat',2);
insert into tb_stu (id,username ,status) values(3,'Rose',3);
insert into tb_stu (id,username ,status) values(4,'Coco',2);
insert into tb_stu (id,username ,status) values(5,'Lily',1);
insert into tb_stu (id,username ,status) values(5000001,'Roce',1);
insert into tb_stu (id,username ,status) values(5000002,'Jexi',2);
insert into tb_stu (id,username ,status) values(5000003,'Mini',1);
⚡ sharding-by-pattern 取模范围算法
该算法先进行取模,然后根据取模值所属范围进行分片。
优点:可以自主决定取模后数据的节点分布
缺点:dataNode 划分节点是事先建好的,需要扩展时比较麻烦。
配置如下:
<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 | 求模基数 |
测试:
配置
<table name="tb_mod_range" dataNode="dn1,dn2,dn3" rule="sharding-by-pattern"/>
数据
1). 创建表
CREATE TABLE `tb_mod_range` (
id int(11) NOT NULL COMMENT 'ID',
name varchar(200) DEFAULT NULL COMMENT '名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2). 插入数据
insert into tb_mod_range (id,name) values(1,'Test1');
insert into tb_mod_range (id,name) values(2,'Test2');
insert into tb_mod_range (id,name) values(3,'Test3');
insert into tb_mod_range (id,name) values(4,'Test4');
insert into tb_mod_range (id,name) values(5,'Test5');
注意 : 取模范围算法只能针对于数字类型进行取模运算 ; 如果是字符串则无法进行取模分片 ;
⚡ 固定分片hash算法
该算法类似于十进制的求模运算,但是为二进制的操作,例如,取 id 的二进制低 10 位 与 1111111111 进行位 & 运算。
最小值:
最大值:
优点: 这种策略比较灵活,可以均匀分配也可以非均匀分配,各节点的分配比例和容量大小由partitionCount和partitionLength两个参数决定
缺点:和取模分片类似。
配置如下 :
<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
测试:
<table name="tb_brand" dataNode="dn1,dn2,dn3" rule="sharding-by-long-hash"/>
1). 创建表
CREATE TABLE `tb_brand` (
id int(11) NOT NULL COMMENT 'ID',
name varchar(200) DEFAULT NULL COMMENT '名称',
firstChar char(1) COMMENT '首字母',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2). 插入数据
insert into tb_brand (id,name ,firstChar) values(1,'七匹狼','Q');
insert into tb_brand (id,name ,firstChar) values(529,'八匹狼','B');
insert into tb_brand (id,name ,firstChar) values(1203,'九匹狼','J');
insert into tb_brand (id,name ,firstChar) values(1205,'十匹狼','S');
insert into tb_brand (id,name ,firstChar) values(1719,'六匹狼','L');
4.3.7 字符串hash求模范围算法
与取模范围算法类似, 该算法支持数值、符号、字母取模,首先截取长度为 prefixLength 的子串,在对子串中每一个字符的 ASCII 码求和,然后对求和值进行取模运算(sum%patternValue),就可以计算出子串的分片数。
优点:可以自主决定取模后数据的节点分布
缺点:dataNode 划分节点是事先建好的,需要扩展时比较麻烦。
配置如下:
<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 | 求模基数 |
如 :
字符串 :
gf89f9a
截取字符串的前5位进行ASCII的累加运算 :
g - 103
f - 102
8 - 56
9 - 57
f - 102
sum求和 : 103 + 102 + + 56 + 57 + 102 = 420
求模 : 420 % 96 = 36
附录 ASCII码表 :
测试:
配置
<table name="tb_u" dataNode="dn1,dn2,dn3" rule="sharding-by-prefixpattern"/>
数据
1). 创建表
CREATE TABLE `tb_u` (
username varchar(50) NOT NULL COMMENT '用户名',
age int(11) default 0 COMMENT '年龄',
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2). 插入数据
insert into tb_u (username,age) values('Test100001',18);
insert into tb_u (username,age) values('Test200001',20);
insert into tb_u (username,age) values('Test300001',19);
insert into tb_u (username,age) values('Test400001',25);
insert into tb_u (username,age) values('Test500001',22);
4.3.8 应用指定算法
由运行阶段由应用自主决定路由到那个分片 , 直接根据字符子串(必须是数字)计算分片号 , 配置如下 :
<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 。
测试:
配置
<table name="tb_app" dataNode="dn1,dn2,dn3" rule="sharding-by-substring"/>
数据
1). 创建表
CREATE TABLE `tb_app` (
id varchar(10) NOT NULL COMMENT 'ID',
name varchar(200) DEFAULT NULL COMMENT '名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2). 插入数据
insert into tb_app (id,name) values('00-00001','Testx00001');
insert into tb_app (id,name) values('01-00001','Test100001');
insert into tb_app (id,name) values('01-00002','Test200001');
insert into tb_app (id,name) values('02-00001','Test300001');
insert into tb_app (id,name) values('02-00002','TesT400001');
4.3.9 字符串hash解析算法
截取字符串中的指定位置的子字符串, 进行hash算法, 算出分片 , 配置如下:
<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 | 指定该分片算法对应的类 |
partitionLength | hash求模基数 ; length*count=1024 (出于性能考虑) |
partitionCount | 分区数 |
hashSlice | hash运算位 , 根据子字符串的hash运算 ; 0 代表 str.length() , -1 代表 str.length()-1 , 大于0只代表数字自身 ; 可以理解为substring(start,end),start为0则只表示0 |
测试:
配置
<table name="tb_strhash" dataNode="dn1,dn2,dn3" rule="sharding-by-stringhash"/>
数据
1). 创建表
create table tb_strhash(
name varchar(20) primary key,
content varchar(100)
)engine=InnoDB DEFAULT CHARSET=utf8mb4;
2). 插入数据
INSERT INTO tb_strhash (name,content) VALUES('T1001', UUID());
INSERT INTO tb_strhash (name,content) VALUES('ROSE', UUID());
INSERT INTO tb_strhash (name,content) VALUES('JERRY', UUID());
INSERT INTO tb_strhash (name,content) VALUES('CRISTINA', UUID());
INSERT INTO tb_strhash (name,content) VALUES('TOMCAT', UUID());
原理:
⚡ 一致性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>
<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值与物理节点的映射按行输出到这个文件,没有默认值,如果不指定,就不会输出任何东西 |
测试:
<table name="tb_order" dataNode="dn1,dn2,dn3" rule="sharding-by-murmur"/>
1). 创建表
create table tb_order(
id int(11) primary key,
money int(11),
content varchar(200)
)engine=InnoDB ;
2). 插入数据
INSERT INTO tb_order (id,money,content) VALUES(1, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(212, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(312, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(412, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(534, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(621, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(754563, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(8123, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(91213, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(23232, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(112321, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(21221, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(112132, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(12132, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(124321, 100 , UUID());
INSERT INTO tb_order (id,money,content) VALUES(212132, 100 , UUID());
⚡ 日期分片算法
按照日期来分片
<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个分片, 下个月从头开始循环, 每个月末需要手动清理数据。
配置如下 :
<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 | 一天切分的分片数 |
4.3.13 自然月分片算法
使用场景为按照月份列分区, 每个自然月为一个分片, 配置如下:
<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 | 结束日期,如果配置了结束日期,则代码数据到达了这个日期的分片后,会重复从开始分片插入 |
4.3.14 日期范围hash算法
其思想和范围取模分片一样,先根据日期进行范围分片求出分片组,再根据时间hash使得短期内数据分布的更均匀 ;
优点 : 可以避免扩容时的数据迁移,又可以一定程度上避免范围分片的热点问题
注意 : 要求日期格式尽量精确些,不然达不到局部均匀的目的
<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 | 代表多少天为一组 |
四、MyCat读写分离
(1) MySQL主从复制原理
复制是指将主数据库的DDL 和 DML 操作通过二进制日志binlog传到从库服务器中,然后在从库上对这些日志重新执行,从而使得从库和主库的数据保持同步。
MySQL支持一台主库同时向多台从库进行复制, 从库同时也可以作为其他从服务器的主库,实现链状复制。
从上层来看,主从复制分成三步:
- Master 主库在事务提交时,会把数据变更作为时间 Events 记录在二进制日志文件 Binlog 中。
主库推送二进制日志文件 Binlog 中的日志事件到从库的中继日志 Relay Log 。
slave重做中继日志中的事件,将改变反映它自己的数据。
MySQL 主从复制的优点主要包含以下三个方面:
主库出现问题,可以快速切换到从库提供服务。
可以在从库上执行查询操作,从主库中更新,实现读写分离,降低主库的访问压力。
可以在从库中执行备份,以避免备份期间影响主库的服务。
(2)搭建MySQL 一主一从
MySQL角色 | IP | 端口号 |
---|---|---|
Master | 192.168.192.157 | 3306 |
Slave | 192.168.192.158 | 3306 |
⚡ master 主库
1) 在master 的配置文件(/etc/mysql/my.cnf)中,配置如下内容:
#mysql 服务ID,保证整个集群环境中唯一
server-id=1
#mysql binlog 日志的存储路径和文件名,需先chmod 777 /var/log/mysql赋予权限
#创建容器时,/mydata/mysql/log已和/var/log/mysql进行了关联
log-bin=/var/log/mysql/mysqlbin
#是否只读,1 代表只读, 0 代表读写
read-only=0
#忽略的数据, 指不需要同步的数据库
binlog-ignore-db=mysql
#指定同步的数据库
#binlog-do-db=db01
2) 执行完毕之后,需要重启Mysql:
docker restart mysql
3) 创建同步数据的账户,并且进行授权操作:
grant replication slave on *.* to 'root'@'192.168.192.158' identified by 'root';
flush privileges;
4) 查看master状态:
show master status;
字段含义:
File : 从哪个日志文件开始推送日志文件
Position : 从哪个位置开始推送日志
Binlog_Ignore_DB : 指定不需要同步的数据库
⚡ slave 从库
1) 在 slave 端配置文件中,配置如下内容:
#mysql服务端ID,唯一
server-id=2
#指定binlog日志
log-bin=/var/log/mysql/mysqlbin
#启用中继日志
relay-log=mysql-relay
2) 执行完毕之后,需要重启Mysql:
docker restart mysql
3) 执行如下指令 :
change master to master_host= '192.168.56.10', master_user='root', master_password='root', master_log_file='mysqlbin.000001', master_log_pos=154;
指定当前从库对应的主库的IP地址,用户名,密码,从哪个日志文件开始的那个位置开始同步推送日志。
4) 开启同步操作
start slave;
show slave status;
5) 停止同步操作
stop slave;
⚡ 验证主从同步
1) 在主库中创建数据库,创建表,并插入数据 :
create database db01;
user db01;
create table user(
id int(11) not null auto_increment,
name varchar(50) not null,
sex varchar(1),
primary key (id)
)engine=innodb default charset=utf8;
insert into user(id,name,sex) values(null,'Tom','1');
insert into user(id,name,sex) values(null,'Trigger','0');
insert into user(id,name,sex) values(null,'Dawn','1');
2) 在从库中查询数据,进行验证 :
在从库中,可以查看到刚才创建的数据库:
在该数据库中,查询user表中的数据:
(3)MyCat一主一从读写分离
⚡ 读写分离原理
主数据库提供写操作,从数据库提供读操作,这样能有效地减轻单台数据库的压力。
通过MyCat即可轻易实现上述功能,不仅可以支持MySQL,也可以支持Oracle和SQL Server。
⚡ 读写分离配置
检查MySQL的主从复制是否运行正常 .
修改MyCat 的conf/schema.xml 配置如下:
<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <schema name="ITCAST" checkSQLschema="true" sqlMaxLimit="100"> <table name="user" dataNode="dn1" primaryKey="id"/> </schema> <dataNode name="dn1" dataHost="localhost1" database="db01" /> <dataHost name="localhost1" maxCon="1000" minCon="10" balance="1" 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"> <readHost host="hostS1" url="192.168.192.158:3306" user="root" password="itcast" /> </writeHost> </dataHost> </mycat:schema>
- checkSQLschema:当该值设置为true时, 如果我们执行语句”select * from test01.user ;” 语句时, MyCat则会把schema字符去掉 , 可以避免后端数据库执行时报错 ;
- balance:负载均衡的类型。
- balance=”0” : 不开启读写分离机制 , 所有读操作都发送到当前可用的writeHost上.
- balance=”1” : 全部的readHost 与 stand by writeHost (备用的writeHost) 都参与select 语句的负载均衡。双主双从模式(M1 —> S1 , M2 —> S2, 正常情况下,M2,S1,S2 都参与 select 语句的负载均衡。);
- balance=”2” : 所有的读写操作都随机在writeHost , readHost上分发
- balance=”3” : 所有的读请求随机分发到writeHost对应的readHost上执行, writeHost不负担读压力
修改conf/server.xml
<user name="root" defaultAccount="true"> <property name="password">123456</property> <property name="schemas">ITCAST</property> </user> <user name="test"> <property name="password">123456</property> <property name="schemas">ITCAST</property> </user> <user name="user"> <property name="password">123456</property> <property name="schemas">ITCAST</property> <property name="readOnly">true</property> </user>
配置完成,重启MyCat服务
⚡ 验证读写分离
修改balance的值, 查询MyCat中的逻辑表中的数据变化;
(4) 搭建MySQL双主双从
⚡ 架构概述
一个主机 Master1 用于处理所有写请求,它的从机 Slave1 和另一台主机 Master2 还有它的从机 Slave2 负责所有读请求。当 Master1 主机宕机后,Master2 主机负责写请求,Master1 、Master2 互为备机。架构图如下:
编号 | 角色 | IP地址 | 端口号 |
---|---|---|---|
1 | Master1 | 192.168.192.157 | 3306 |
2 | Slave1 | 192.168.192.158 | 3306 |
3 | Master2 | 192.168.192.159 | 3306 |
4 | Slave2 | 192.168.192.160 | 3306 |
⚡ 配置双主机
1) Master1配置:
#主服务器唯一ID
server-id=1
#启用二进制日志
log-bin=mysql-bin
# 设置不要复制的数据库(可设置多个)
# binlog-ignore-db=mysql
# binlog-ignore-db=information_schema
#设置需要复制的数据库
binlog-do-db=db02
binlog-do-db=db03
binlog-do-db=db04
#设置logbin格式
binlog_format=STATEMENT
# 在作为从数据库的时候,有写入操作也要更新二进制日志文件
log-slave-updates
2) Master2配置:
#主服务器唯一ID
server-id=3
#启用二进制日志
log-bin=mysql-bin
# 设置不要复制的数据库(可设置多个)
#binlog-ignore-db=mysql
#binlog-ignore-db=information_schema
#设置需要复制的数据库
binlog-do-db=db02
binlog-do-db=db03
binlog-do-db=db04
#设置logbin格式
binlog_format=STATEMENT
# 在作为从数据库的时候,有写入操作也要更新二进制日志文件!!
log-slave-updates
3) 在两台从机上均执行以下命令:
- 重启MySQL服务:
docker restart mysql
- MySQL中为从机授权:
GRANT REPLICATION SLAVE ON *.* TO 'itcast'@'%' IDENTIFIED BY 'itcast';
- 刷新权限:
flush privileges;
4) 查询两台主机Master1、Master2的状态 :
5) 设置两个主机互相复制:
- Master2 复制 Master1
CHANGE MASTER TO MASTER_HOST='192.168.192.159', MASTER_USER='itcast', MASTER_PASSWORD='itcast',
MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=409;
- Master1 复制 Master2
CHANGE MASTER TO MASTER_HOST='192.168.192.157', MASTER_USER='itcast', MASTER_PASSWORD='itcast',
MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=409;
6) 启动两台主服务器复制功能 ,并查看主从复制的运行状态
start slave;
show slave status\G;
⚡ 配置双从机
1) Slave1配置:
#从服务器唯一ID
server-id=2
#启用中继日志
relay-log=mysql-relay
2) Slave2配置:
#从服务器唯一ID
server-id=4
#启用中继日志
relay-log=mysql-relay
3) 在从机上配置需要复制的主机:
- Slave1 复制 Master1
CHANGE MASTER TO MASTER_HOST='192.168.192.157', MASTER_USER='itcast', MASTER_PASSWORD='itcast',
MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=409;
- Slave2 复制 Master2
CHANGE MASTER TO MASTER_HOST='192.168.192.159', MASTER_USER='itcast', MASTER_PASSWORD='itcast',
MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=409;
4) 启动两台从服务器复制功能 , 查看主从复制的运行状态**
start slave;
show slave status\G;
⚡ 验证主从同步
create database db03;
use db03;
create table user(
id int(11) not null auto_increment,
name varchar(50) not null,
sex varchar(1),
primary key (id)
)engine=innodb default charset=utf8;
insert into user(id,name,sex) values(null,'Tom','1');
insert into user(id,name,sex) values(null,'Trigger','0');
insert into user(id,name,sex) values(null,'Dawn','1');
insert into user(id,name,sex) values(null,'Jack Ma','1');
insert into user(id,name,sex) values(null,'Coco','0');
insert into user(id,name,sex) values(null,'Jerry','1');
1) 在Master1上创建数据库:
2) 在Master1上创建表 :
3) 停止从服务复制功能
stop slave;
4) 重新配置主从关系
stop slave;
reset master;
(5)MyCat双主双从读写分离
⚡ 配置MyCat
修改\
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="ITCAST" checkSQLschema="true" sqlMaxLimit="100">
<table name="user" dataNode="dn1" primaryKey="id"/>
</schema>
<dataNode name="dn1" dataHost="localhost1" database="db03" />
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1" 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">
<readHost host="hostS1" url="192.168.192.149:3306" user="root" password="itcast" />
</writeHost>
<writeHost host="hostM2" url="192.168.192.150:3306" user="root" password="itcast">
<readHost host="hostS2" url="192.168.192.151:3306" user="root" password="itcast" />
</writeHost>
</dataHost>
</mycat:schema>
balance
:balance=”1” 代表 全部的 readHost 与 stand by writeHost 参与 select 语句的负载均衡。双主双从模式下(M1->S1,M2->S2,并且 M1 与 M2 互为主备),M2,S1,S2 都参与 select 语句的负载均衡 ;writeType
:0表示 写操作都转发到第1台writeHost,writeHost1挂了, 会自动切换到writeHost2上;1 表示所有的写操作会随机地发送到配置的writeHost上 ;switchType
:-1 —> 不自动切换;1 —> 默认值, 自动切换;2 —> 基于MySQL的主从同步状态决定是否切换, 心跳语句 : show slave status
⚡ 验证读写分离
查询数据 : select * from user;
插入数据 : insert into user(id,name,sex) values(null,’Dawn’,’1’);
⚡ 验证可用性
关闭Master1 , 然后再执行写入的SQL语句 , 通过日志查询当前写入操作, 操作的是那台服务器 ;
五、MyCat性能监控
(1)MyCat-web简介
Mycat-web 是 Mycat 可视化运维的管理和监控平台,弥补了 Mycat 在监控上的空白。帮 Mycat 分担统计任务和配置管理任务。Mycat-web 引入了 ZooKeeper 作为配置中心,可以管理多个节点。Mycat-web 主要管理和监控 Mycat 的流量、连接、活动线程和内存等,具备 IP 白名单、邮件告警等模块,还可以统计 SQL 并分析慢 SQL 和高频 SQL 等。为优化 SQL 提供依据。
(2)MyCat-web 安装
下载地址 : http://dl.mycat.io/
1). 安装Zookeeper
A. 上传安装包
alt + p -----> put D:\tmp\zookeeper-3.4.11.tar.gz
B. 解压
tar -zxvf zookeeper-3.4.11.tar.gz -C /usr/local/
C. 创建数据存放目录
mkdir data
D. 修改配置文件名称并配置
mv zoo_sample.cfg zoo.cfg
E. 配置数据存放目录
dataDir=/usr/local/zookeeper-3.4.11/data
F. 启动Zookeeper
bin/zkServer.sh start
2). 安装MyCat-web
A. 上传安装包
alt + p --------> put D:\tmp\Mycat-web-1.0-SNAPSHOT-20170102153329-linux.tar.gz
B. 解压
tar -zxvf Mycat-web-1.0-SNAPSHOT-20170102153329-linux.tar.gz -C /usr/local/
C. MycatWeb目录介绍
drwxr-xr-x. 2 root root 4096 Oct 19 2015 etc ----> jetty配置文件
drwxr-xr-x. 3 root root 4096 Oct 19 2015 lib ----> 依赖jar包
drwxr-xr-x. 7 root root 4096 Jan 1 2017 mycat-web ----> mycat-web项目
-rwxr-xr-x. 1 root root 116 Oct 19 2015 readme.txt
-rwxr-xr-x. 1 root root 17125 Oct 19 2015 start.jar ----> 启动jar
-rwxr-xr-x. 1 root root 381 Oct 19 2015 start.sh ----> linux启动脚本
D. 启动
sh start.sh
E. 访问
http://192.168.192.147:8082/mycat
如果Zookeeper与Mycat-web不在同一台服务器上 , 需要设置Zookeeper的地址 ; 在/usr/local/mycat-web/mycat-web/WEB-INF/classes/mycat.properties文件中配置 :
(3)配置MyCat-Web
(4)MyCat性能监控
在 Mycat-web 上可以进行 Mycat 性能监控,例如:内存分享、流量分析、连接分析、活动线程分析等等。 如下图:
A. MyCat内存分析:
MyCat的内存分析 , 反映了当前的内存使用情况与历史时间段的峰值、平均值。
B. MyCat流量分析:
MyCat流量分析统计了历史时间段的流量峰值、当前值、平均值,是MyCat数据传输的重要指标, In代表输入, Out代表输出。
C. MyCat连接分析
MyCat连接分析, 反映了MyCat的连接数
D. MyCat TPS分析
MyCat TPS 是并发性能的重要参数指标, 指系统在每秒内能够处理的请求数量。 MyCat TPS的值越高 , 代表MyCat单位时间内能够处理的请求就越多, 并发能力也就越高。
E. MyCat活动线程分析反映了MyCat线程的活动情况。
F. MyCat缓存队列分析, 反映了当前在缓存队列中的任务数量。
(5)MySQL性能监控
1). MySQL配置
2). MySQL监控指标
可以通过MySQL服务监控, 检测每一个MySQL节点的运行状态, 包含缓存命中率 、增删改查比例、流量统计、慢查询比例、线程、临时表等相关性能数据。
(6)SQL监控
1). SQL 统计
2). SQL表分析
3). SQL监控
4). 高频SQL
5). 慢SQL统计
6). SQL解析
六、MyCat高可用集群
(1)集群架构
图解说明:
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来实现后端MySQL的负载均衡 ,再通过HAProxy实现MyCat集群的高可用和负载均衡,而 HAProxy 的高可用,则由 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 选举状态。
(2) 高可用集群搭建
名称 | IP | 端口 | 用户名/密码 |
---|---|---|---|
MySQL Master | 192.168.192.157 | 3306 | root/itcast |
MySQL Slave | 192.168.192.158 | 3306 | root/itcast |
MyCat节点1 | 192.168.192.157 | 8066 | root/123456 |
MyCat节点2 | 192.168.192.158 | 8066 | root/123456 |
HAProxy节点1/keepalived主 | 192.168.192.159 | ||
HAProxy节点2/keepalived备 | 192.168.192.160 |
⚡ MySQL主从复制搭建
🔎 master
1) 在master 的配置文件(/usr/my.cnf)中,配置如下内容:
#mysql 服务ID,保证整个集群环境中唯一
server-id=1
#mysql binlog 日志的存储路径和文件名
log-bin=/var/lib/mysql/mysqlbin
#设置logbin格式
binlog_format=STATEMENT
#是否只读,1 代表只读, 0 代表读写
read-only=0
#指定同步的数据库
binlog-do-db=db01
binlog-do-db=db02
binlog-do-db=db03
2) 执行完毕之后,需要重启Mysql:
service mysql restart ;
3) 创建同步数据的账户,并且进行授权操作:
grant replication slave on *.* to 'itcast'@'%' identified by 'itcast';
flush privileges;
4) 查看master状态:
show master status;
字段含义:
File : 从哪个日志文件开始推送日志文件
Position : 从哪个位置开始推送日志
Binlog_Do_DB : 指定需要同步的数据库
🔎 slave
1)在 slave 端配置文件中,配置如下内容:
#mysql服务端ID,唯一
server-id=2
#指定binlog日志
log-bin=/var/lib/mysql/mysqlbin
#启用中继日志
relay-log=mysql-relay
2)执行完毕之后,需要重启Mysql:
service mysql restart;
3)执行如下指令 :
change master to master_host= '192.168.192.157', master_user='itcast', master_password='itcast', master_log_file='mysqlbin.000002', master_log_pos=120;
指定当前从库对应的主库的IP地址,用户名,密码,从哪个日志文件开始的那个位置开始同步推送日志。
4)开启同步操作
start slave;
show slave status;
5) 停止同步操作
stop slave;
🔎 测试验证
create database db01;
user db01;
create table user(
id int(11) not null auto_increment,
name varchar(50) not null,
sex varchar(1),
primary key (id)
)engine=innodb default charset=utf8;
insert into user(id,name,sex) values(null,'Tom','1');
insert into user(id,name,sex) values(null,'Trigger','0');
insert into user(id,name,sex) values(null,'Dawn','1');
⚡ MyCat安装配置
🔎 配置schema.xml
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="ITCAST" checkSQLschema="true" sqlMaxLimit="100">
<table name="user" dataNode="dn1" primaryKey="id"/>
</schema>
<dataNode name="dn1" dataHost="localhost1" database="db01" />
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1" 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">
<readHost host="hostS1" url="192.168.192.158:3306" user="root" password="itcast" />
</writeHost>
</dataHost>
</mycat:schema>
🔎 配置 server.xml
<user name="root" defaultAccount="true">
<property name="password">123456</property>
<property name="schemas">ITCAST</property>
</user>
<user name="test">
<property name="password">123456</property>
<property name="schemas">ITCAST</property>
</user>
两台MyCat服务, 做相同的配置 ;
⚡ HAProxy安装配置
🔎 安装
1). 准备好HAProxy安装包haproxy-1.5.16.tar.gz
,传到/root
目录下
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
5). 安装完成后,创建目录
mkdir -p /usr/data/haproxy/
6). 创建HAProxy配置文件,vim /usr/local/haproxy/haproxy.conf
#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请求 |
🔎 启动访问
1). 启动HAProxy
/usr/local/haproxy/sbin/haproxy -f /usr/local/haproxy/haproxy.conf
2). 查看HAProxy进程
ps -ef|grep haproxy
3). 访问http://192.168.192.162:8888/admin
⚡ 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:
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
#!/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
MyCat综合案例
(1) 案例概述
模拟电商项目中的商品管理、订单管理、基础信息管理、日志管理模块,对整个系统中的数据表进行分片操作,将根据不同的业务需求,采用不同的分片方式 。
(2) 案例需求
1). 商品管理: 添加商品、查询商品
2). 订单管理:下订单、 查询订单
3). 日志管理:日志记录、日志查询
(3) 案例环境搭建
⚡ 数据库
1). 省份表 tb_provinces
Field | Type | Comment |
---|---|---|
provinceid | varchar(20) | 省份ID |
province | varchar(50) | 省份名称 |
2). 市表 tb_cities
Field | Type | Comment |
---|---|---|
cityid | varchar(20) | 城市ID |
city | varchar(50) | 城市名称 |
provinceid | varchar(20) | 省份ID |
3). 区县表 tb_areas
Field | Type | Comment |
---|---|---|
areaid | varchar(20) | 区域ID |
area | varchar(50) | 区域名称 |
cityid | varchar(20) | 城市ID |
4). 商品分类表 tb_category
Field | Type | Comment |
---|---|---|
id | int(20) | 分类ID |
name | varchar(50) | 分类名称 |
goods_num | int(11) | 商品数量 |
is_show | char(1) | 是否显示 |
is_menu | char(1) | 是否导航 |
seq | int(11) | 排序 |
parent_id | int(20) | 上级ID |
5). 品牌表 tb_brand
Field | Type | Comment |
---|---|---|
id | int(11) | 品牌id |
name | varchar(100) | 品牌名称 |
image | varchar(1000) | 品牌图片地址 |
letter | char(1) | 品牌的首字母 |
seq | int(11) | 排序 |
6). 商品SPU表 tb_spu
Field | Type | Comment |
---|---|---|
id | varchar(20) | 主键 |
sn | varchar(60) | 货号 |
name | varchar(100) | SPU名 |
caption | varchar(100) | 副标题 |
brand_id | int(11) | 品牌ID |
category1_id | int(20) | 一级分类 |
category2_id | int(10) | 二级分类 |
category3_id | int(10) | 三级分类 |
template_id | int(20) | 模板ID |
freight_id | int(11) | 运费模板id |
image | varchar(200) | 图片 |
images | varchar(2000) | 图片列表 |
sale_service | varchar(50) | 售后服务 |
introduction | text | 介绍 |
spec_items | varchar(3000) | 规格列表 |
para_items | varchar(3000) | 参数列表 |
sale_num | int(11) | 销量 |
comment_num | int(11) | 评论数 |
is_marketable | char(1) | 是否上架 |
is_enable_spec | char(1) | 是否启用规格 |
is_delete | char(1) | 是否删除 |
status | char(1) | 审核状态 |
7). 商品SKU表 tb_sku
Field | Type | Comment |
---|---|---|
id | varchar(20) | 商品id |
sn | varchar(100) | 商品条码 |
name | varchar(200) | SKU名称 |
price | int(20) | 价格(分) |
num | int(10) | 库存数量 |
alert_num | int(11) | 库存预警数量 |
image | varchar(200) | 商品图片 |
images | varchar(2000) | 商品图片列表 |
weight | int(11) | 重量(克) |
create_time | datetime | 创建时间 |
update_time | datetime | 更新时间 |
spu_id | varchar(20) | SPUID |
category_id | int(10) | 类目ID |
category_name | varchar(200) | 类目名称 |
brand_name | varchar(100) | 品牌名称 |
spec | varchar(200) | 规格 |
sale_num | int(11) | 销量 |
comment_num | int(11) | 评论数 |
status | char(1) | 商品状态 1-正常,2-下架,3-删除 |
version | int(255) |
8). 订单表 tb_order
Field | Type | Comment |
---|---|---|
id | varchar(200) | 订单id |
total_num | int(11) | 数量合计 |
total_money | int(11) | 金额合计 |
pre_money | int(11) | 优惠金额 |
post_fee | int(11) | 邮费 |
pay_money | int(11) | 实付金额 |
pay_type | varchar(1) | 支付类型,1、在线支付、0 货到付款 |
create_time | datetime | 订单创建时间 |
update_time | datetime | 订单更新时间 |
pay_time | datetime | 付款时间 |
consign_time | datetime | 发货时间 |
end_time | datetime | 交易完成时间 |
close_time | datetime | 交易关闭时间 |
shipping_name | varchar(20) | 物流名称 |
shipping_code | varchar(20) | 物流单号 |
username | varchar(50) | 用户名称 |
buyer_message | varchar(1000) | 买家留言 |
buyer_rate | char(1) | 是否评价 |
receiver_contact | varchar(50) | 收货人 |
receiver_mobile | varchar(12) | 收货人手机 |
receiver_province | varchar(200) | 收货人省份 |
receiver_city | varchar(200) | 收货人市 |
receiver_area | varchar(200) | 收货人区/县 |
receiver_address | varchar(200) | 收货人具体街道地址 |
source_type | char(1) | 订单来源:1:web,2:app,3:微信公众号,4:微信小程序 5 H5手机页面 |
transaction_id | varchar(30) | 交易流水号 |
order_status | char(1) | 订单状态 |
pay_status | char(1) | 支付状态 0:未支付 1:已支付 |
consign_status | char(1) | 发货状态 0:未发货 1:已发货 2:已送达 |
is_delete | char(1) | 是否删除 |
9). 订单明细表 tb_order_item
Field | Type | Comment |
---|---|---|
id | varchar(200) | ID |
category_id1 | int(11) | 1级分类 |
category_id2 | int(11) | 2级分类 |
category_id3 | int(11) | 3级分类 |
spu_id | varchar(200) | SPU_ID |
sku_id | varchar(200) | SKU_ID |
order_id | varchar(200) | 订单ID |
name | varchar(200) | 商品名称 |
price | int(20) | 单价 |
num | int(10) | 数量 |
money | int(20) | 总金额 |
pay_money | int(11) | 实付金额 |
image | varchar(200) | 图片地址 |
weight | int(11) | 重量 |
post_fee | int(11) | 运费 |
is_return | char(1) | 是否退货 |
10). 订单日志表 tb_order_log
Field | Type | Comment |
---|---|---|
id | varchar(20) | ID |
operater | varchar(50) | 操作员 |
operate_time | datetime | 操作时间 |
order_id | bigint(20) | 订单ID |
order_status | char(1) | 订单状态 |
pay_status | char(1) | 付款状态 |
consign_status | char(1) | 发货状态 |
remarks | varchar(100) | 备注 |
11). 操作日志表 tb_operatelog
Field | Type | Comment |
---|---|---|
id | bigint(20) | ID |
model_name | varchar(200) | 模块名 |
model_value | varchar(200) | 模块值 |
return_value | varchar(200) | 返回值 |
return_class | varchar(200) | 返回值类型 |
operate_user | varchar(20) | 操作用户 |
operate_time | varchar(20) | 操作时间 |
param_and_value | varchar(500) | 请求参数名及参数值 |
operate_class | varchar(200) | 操作类 |
operate_method | varchar(200) | 操作方法 |
cost_time | bigint(20) | 执行方法耗时, 单位 ms |
12). 字典表 tb_dictionary
Field | Type | Comment |
---|---|---|
id | int(11) | 主键ID , 自增 |
codeid | int(11) | 码表ID |
codetype | varchar(2) | 码值类型 |
codename | varchar(50) | 名称 |
codevalue | varchar(50) | 码值 |
description | varchar(100) | 描述 |
createtime | datetime | 创建时间 |
updatetime | datetime | 修改时间 |
createuser | int(11) | 创建人 |
updateuser | int(11) | 修改人 |
⚡工程预览
spring-boot-starter-parent
|- v_parent --------------------> 父工程, 统一管理依赖版本
|- v_common ----------------> 通用工程, 存放通用的工具类及组件
|- v_model -----------------> 实体类
|- v_eureka ----------------> 注册中心
|- v_feign_api -------------> feign远程调用的客户端接口
|- v_gateway ---------------> 网关工程
|- v_manage_web ------------> 模拟前端工程
|- v_service_goods ---------> 商品微服务
|- v_service_log -----------> 日志微服务
|- v_service_order ---------> 订单微服务
⚡ 工程层级关系
⚡ 父工程搭建
工程名: v_parent
pom.xml
<!-- springBoot项目需要集成自父工程 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
</parent>
<properties>
<skipTests>true</skipTests>
</properties>
<!--依赖包-->
<dependencies>
<!--测试包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--MySQL数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--mybatis分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
</dependency>
</dependencies>
</dependencyManagement>
⚡ 基础工程搭建
1). v_model
该基础工程中存放的是与数据库对应的实体类 ;
A. pom.xml
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
B. 导入实体类
2). v_common
该基础工程中存放的是通用的组件及工具类 , 比如 分页实体类, 结果实体类, 状态码 等
直接导入资料中提供的基础组件和工具类 ;
3). v_feign_api
该工程中, 主要存放的是Feign远程调用的客户端接口;
pom.xml
<dependencies>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Feign起步依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>cn.itcast</groupId>
<artifactId>v_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.itcast</groupId>
<artifactId>v_model</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
⚡ Eureka Server搭建
1). pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
2). 引导类
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}
3). application.yml
spring:
application:
name: eureka
server:
port: 8161
eureka:
client:
register-with-eureka: false #是否将自己注册到eureka中
fetch-registry: false #是否从eureka中获取信息
service-url:
defaultZone: http://127.0.0.1:${server.port}/eureka/
server:
enable-self-preservation: true
⚡ GateWay 网关搭建
1). pom.xml
<!--网关依赖-->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
2). 引导类
@SpringBootApplication
@EnableEurekaClient
public class GateWayApplication {
public static void main(String[] args) {
SpringApplication.run(GateWayApplication.class,args);
}
}
3). application.yml
server:
port: 8001
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8161/eureka
instance:
prefer-ip-address: true
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: v_goods_route
uri: lb://goods
predicates:
- Path=/goods/**
filters:
- StripPrefix=1
- id: v_order_route
uri: lb://order
predicates:
- Path=/order/**
filters:
- StripPrefix=1
4). 跨域处理:Cors配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", buildConfig());
return new CorsWebFilter(source);
}
private CorsConfiguration buildConfig(){
CorsConfiguration corsConfiguration = new CorsConfiguration();
//在生产环境上最好指定域名,以免产生跨域安全问题
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
return corsConfiguration;
}
}
(4) 功能开发
⚡ 商品管理模块
需求 :
1). 根据ID查询商品SPU信息;
2). 根据条件查询商品SPU列表;
3). 根据ID查询商品SKU信息;
概念:
1). SPU = Standard Product Unit (标准产品单位)
概念 : SPU 是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。
通俗点讲,属性值、特性相同的货品就可以称为一个 SPU
例如:华为P30 就是一个 SPU
2). SKU=stock keeping unit( 库存量单位)
SKU 即库存进出计量的单位, 可以是以件、盒、托盘等为单位。
SKU 是物理上不可分割的最小存货单元。在使用时要根据不同业态,不同管理模式来处理。在服装、鞋类商品中使用最多最普遍。
例如:红色 64G 全网通 的华为P30 就是一个 SKU
8.4.1.1 创建工程
pom.xml
<dependencies>
<!-- Eureka客户端依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--MySQL数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- redis 使用-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- fastJson依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<!-- Feign依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>cn.itcast</groupId>
<artifactId>v_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.itcast</groupId>
<artifactId>v_model</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.itcast</groupId>
<artifactId>v_feign_api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
application.yml
server:
port: 9001
spring:
application:
name: goods
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/v_shop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: 2143
main:
allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://127.0.0.1:8161/eureka
instance:
prefer-ip-address: true
引导类
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = "cn.itcast.feign")
@MapperScan("cn.itcast.goods.mapper")
public class GoodsApplication {
public static void main(String[] args) {
SpringApplication.run(GoodsApplication.class,args);
}
}
8.4.1.2 Mapper
1). mapper接口定义
public interface SpuMapper {
public TbSpu findById(String spuId);
public List<TbSpu> search(Map<String,Object> searchMap);
}
public interface SkuMapper {
//根据ID查询SKU
public TbSku findById(String skuId);
}
2). mapper映射配置文件
SpuMapper.xml
<mapper namespace="cn.itcast.goods.mapper.SpuMapper" >
<resultMap id="spuResultMap" type="cn.itcast.model.TbSpu">
<id column="id" jdbcType="VARCHAR" property="id" />
<result column="sn" jdbcType="VARCHAR" property="sn" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="caption" jdbcType="VARCHAR" property="caption" />
<result column="brand_id" jdbcType="INTEGER" property="brandId" />
<result column="category1_id" jdbcType="INTEGER" property="category1Id" />
<result column="category2_id" jdbcType="INTEGER" property="category2Id" />
<result column="category3_id" jdbcType="INTEGER" property="category3Id" />
<result column="template_id" jdbcType="INTEGER" property="templateId" />
<result column="freight_id" jdbcType="INTEGER" property="freightId" />
<result column="image" jdbcType="VARCHAR" property="image" />
<result column="images" jdbcType="VARCHAR" property="images" />
<result column="sale_service" jdbcType="VARCHAR" property="saleService" />
<result column="spec_items" jdbcType="VARCHAR" property="specItems" />
<result column="para_items" jdbcType="VARCHAR" property="paraItems" />
<result column="sale_num" jdbcType="INTEGER" property="saleNum" />
<result column="comment_num" jdbcType="INTEGER" property="commentNum" />
<result column="is_marketable" jdbcType="CHAR" property="isMarketable" />
<result column="is_enable_spec" jdbcType="CHAR" property="isEnableSpec" />
<result column="is_delete" jdbcType="CHAR" property="isDelete" />
<result column="status" jdbcType="CHAR" property="status" />
</resultMap>
<select id="findById" parameterType="java.lang.String" resultMap="spuResultMap">
select
*
from
tb_spu
where
id = #{spuId}
</select>
<select id="search" resultMap="spuResultMap">
select * from tb_spu
<where>
<if test="name != null and name != ''">
and name like '%${name}%'
</if>
<if test="caption != null and caption != ''" >
and caption like '%${caption}%'
</if>
<if test="brandId != null">
and brand_id = #{brandId}
</if>
<if test="status != null and status != ''">
and status = #{status}
</if>
</where>
</select>
</mapper>
SkuMapper.xml
<mapper namespace="cn.itcast.goods.mapper.SkuMapper" >
<resultMap id="skuResultMap" type="cn.itcast.model.TbSku">
<id column="id" jdbcType="VARCHAR" property="id" />
<result column="sn" jdbcType="VARCHAR" property="sn" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="price" jdbcType="INTEGER" property="price" />
<result column="num" jdbcType="INTEGER" property="num" />
<result column="alert_num" jdbcType="INTEGER" property="alertNum" />
<result column="image" jdbcType="VARCHAR" property="image" />
<result column="images" jdbcType="VARCHAR" property="images" />
<result column="weight" jdbcType="INTEGER" property="weight" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
<result column="spu_id" jdbcType="VARCHAR" property="spuId" />
<result column="category_id" jdbcType="INTEGER" property="categoryId" />
<result column="category_name" jdbcType="VARCHAR" property="categoryName" />
<result column="brand_name" jdbcType="VARCHAR" property="brandName" />
<result column="spec" jdbcType="VARCHAR" property="spec" />
<result column="sale_num" jdbcType="INTEGER" property="saleNum" />
<result column="comment_num" jdbcType="INTEGER" property="commentNum" />
<result column="status" jdbcType="CHAR" property="status" />
<result column="version" jdbcType="INTEGER" property="version" />
</resultMap>
<select id="findById" parameterType="java.lang.String" resultMap="skuResultMap">
select * from tb_sku where id = #{skuId}
</select>
</mapper>
8.4.1.3 Service
1). 接口定义
public interface SkuService {
/**
* 根据ID查询SKU
* @param skuId
* @return
*/
public TbSku findById(String skuId);
}
public interface SpuService {
/**
* 根据ID查询
* @param id
* @return
*/
TbSpu findById(String id);
/***
* 多条件分页查询
* @param searchMap
* @param page
* @param size
* @return
*/
Page<TbSpu> findPage(Map<String, Object> searchMap, int page, int size);
}
2).接口实现
@Service
public class SkuServiceImpl implements SkuService {
@Autowired
private SkuMapper skuMapper;
@Autowired
private RedisTemplate redisTemplate;
@Override
public TbSku findById(String skuId) {
return skuMapper.findById(skuId);
}
}
@Service
public class SpuServiceImpl implements SpuService {
@Autowired
private SpuMapper spuMapper;
/**
* 根据ID查询
* @param id
* @return
*/
@Override
public TbSpu findById(String id){
return spuMapper.findById(id);
}
/**
* 条件+分页查询
* @param searchMap 查询条件
* @param page 页码
* @param size 页大小
* @return 分页结果
*/
@Override
public Page<TbSpu> findPage(Map<String,Object> searchMap, int page, int size){
PageHelper.startPage(page,size);
return (Page<TbSpu>) spuMapper.search(searchMap);
}
}
8.4.1.4 Controller
@RestController
@CrossOrigin(origins = "*")
@RequestMapping("/sku")
public class SkuController {
@Autowired
private SkuService skuService;
/***
* 根据ID查询数据
* @param id
* @return
*/
@GetMapping("/{id}")
public Result<TbSku> findById(@PathVariable("id") String id){
TbSku sku = skuService.findById(id);
return new Result(true,StatusCode.OK,"查询成功",sku);
}
}
@RestController
@CrossOrigin(origins = "*")
@RequestMapping("/spu")
public class SpuController {
@Autowired
private SpuService spuService;
/***
* 根据ID查询数据
* @param id
* @return
*/
@GetMapping("/{id}")
public Result<TbSpu> findById(@PathVariable("id") String id){
TbSpu spu = spuService.findById(id);
return new Result(true,StatusCode.OK,"查询成功",spu);
}
/***
* 分页搜索实现
* @param searchMap
* @param page
* @param size
* @return
*/
@PostMapping(value = "/search/{page}/{size}" )
public Result<TbSpu> findPage(@RequestBody Map searchMap, @PathVariable Integer page, @PathVariable Integer size){
com.github.pagehelper.Page<TbSpu> pageList = spuService.findPage(searchMap, page, size);
PageResult pageResult=new PageResult<TbSpu>(pageList.getTotal(),pageList.getResult());
return new Result<TbSpu>(true,StatusCode.OK,"查询成功",pageResult);
}
}
⚡ 订单模块
需求: 1). 下单业务分析;2). 根据条件分页查询订单
表结构: tb_order , tb_order_item , tb_order_log
8.4.2.1 创建工程
1).pom.xml
<dependencies
<dependency>
<groupId>cn.itcast</groupId>
<artifactId>v_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.itcast</groupId>
<artifactId>v_model</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.itcast</groupId>
<artifactId>v_feign_api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- Eureka客户端依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- springboot - Mybatis 起步依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<!--MySQL数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- redis 使用-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- fastJson依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<!-- Feign依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
2). application.yml
server:
port: 9002
spring:
application:
name: order
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/v_shop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: 2143
main:
allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://127.0.0.1:8161/eureka
instance:
prefer-ip-address: true
feign:
client:
config:
default: #配置全局的feign的调用超时时间 如果 有指定的服务配置 默认的配置不会生效
connectTimeout: 60000 # 指定的是 消费者 连接服务提供者的连接超时时间 是否能连接 单位是毫秒
readTimeout: 20000 # 指定的是调用服务提供者的 服务 的超时时间() 单位是毫秒
3). 引导类
@SpringBootApplication
@EnableEurekaClient
@MapperScan(basePackages = "cn.itcast.order.mapper")
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class,args);
}
}
8.4.2.2 下单业务分析
8.4.2.3 查询订单
8.4.2.3.1 Mapper
1). mapper接口
public interface OrderMapper {
public List<TbOrder> search(Map<String,Object> searchMap);
}
2). mapper映射配置文件
OrderMapper.xml
<mapper namespace="cn.itcast.order.mapper.OrderMapper" >
<resultMap id="orderResultMap" type="cn.itcast.model.TbOrder">
<id column="id" jdbcType="VARCHAR" property="id" />
<result column="total_num" jdbcType="INTEGER" property="totalNum" />
<result column="total_money" jdbcType="INTEGER" property="totalMoney" />
<result column="pre_money" jdbcType="INTEGER" property="preMoney" />
<result column="post_fee" jdbcType="INTEGER" property="postFee" />
<result column="pay_money" jdbcType="INTEGER" property="payMoney" />
<result column="pay_type" jdbcType="VARCHAR" property="payType" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
<result column="pay_time" jdbcType="TIMESTAMP" property="payTime" />
<result column="consign_time" jdbcType="TIMESTAMP" property="consignTime" />
<result column="end_time" jdbcType="TIMESTAMP" property="endTime" />
<result column="close_time" jdbcType="TIMESTAMP" property="closeTime" />
<result column="shipping_name" jdbcType="VARCHAR" property="shippingName" />
<result column="shipping_code" jdbcType="VARCHAR" property="shippingCode" />
<result column="username" jdbcType="VARCHAR" property="username" />
<result column="buyer_message" jdbcType="VARCHAR" property="buyerMessage" />
<result column="buyer_rate" jdbcType="CHAR" property="buyerRate" />
<result column="receiver_contact" jdbcType="VARCHAR" property="receiverContact" />
<result column="receiver_mobile" jdbcType="VARCHAR" property="receiverMobile" />
<result column="receiver_province" jdbcType="VARCHAR" property="receiverProvince" />
<result column="receiver_city" jdbcType="VARCHAR" property="receiverCity" />
<result column="receiver_area" jdbcType="VARCHAR" property="receiverArea" />
<result column="receiver_address" jdbcType="VARCHAR" property="receiverAddress" />
<result column="source_type" jdbcType="CHAR" property="sourceType" />
<result column="transaction_id" jdbcType="VARCHAR" property="transactionId" />
<result column="order_status" jdbcType="CHAR" property="orderStatus" />
<result column="pay_status" jdbcType="CHAR" property="payStatus" />
<result column="consign_status" jdbcType="CHAR" property="consignStatus" />
<result column="is_delete" jdbcType="CHAR" property="isDelete" />
</resultMap>
<select id="search" resultType="cn.itcast.model.TbOrder">
SELECT
o.id ,
o.`create_time` createTime,
o.username ,
o.`total_money` totalMoney,
o.`total_num` totalNum,
o.`pay_type` payType,
o.`pay_status` payStatus,
p.`province` receiverProvince
FROM
tb_order o , tb_provinces p
WHERE
o.receiver_province = p.provinceid
<if test="orderId != null and orderId != ''">
and o.id = #{orderId}
</if>
<if test="payType != null and payType != ''">
and o.pay_type = #{payType}
</if>
<if test="username != null and username != ''">
and o.username = #{username}
</if>
<if test="payStatus != null and payStatus != ''">
and o.order_status = #{payStatus}
</if>
</select>
</mapper>
8.4.2.3.2 Service
service接口
public interface OrderService {
/***
* 新增
* @param order
*/
void add(TbOrder order);
/***
* 多条件分页查询
* @param searchMap
* @param page
* @param size
* @return
*/
Page<TbOrder> findPage(Map<String, Object> searchMap, int page, int size);
}
service实现
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderItemMapper orderItemMapper;
@Autowired
private IdWorker idWorker;
/**
* 增加
* @param order
*/
@Override
public void add(TbOrder order){
//1.获取购物车的相关数据(redis)
//2.统计计算:总金额,总数量
//3.填充订单数据并保存到tb_order
//4.填充订单项数据并保存到tb_order_item
//5.记录订单日志
//6.扣减库存并增加销量
//7.删除购物车数据(redis)
}
/**
* 条件+分页查询
* @param searchMap 查询条件
* @param page 页码
* @param size 页大小
* @return 分页结果
*/
@Override
public Page<TbOrder> findPage(Map<String,Object> searchMap, int page, int size){
PageHelper.startPage(page,size);
return (Page<TbOrder>)orderMapper.search(searchMap);
}
}
8.4.2.3.3 Controller
@RestController
@CrossOrigin(value = {"*"})
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping
@OperateLog
public Result add(@RequestBody TbOrder order){
//获取登录人名称
orderService.add(order);
return new Result(true,StatusCode.OK,"提交成功");
}
/***
* 分页搜索实现
* @param searchMap
* @param page
* @param size
* @return
*/
@PostMapping(value = "/search/{page}/{size}" )
@OperateLog
public Result findPage(@RequestBody Map searchMap, @PathVariable Integer page, @PathVariable Integer size){
Page<TbOrder> pageList = orderService.findPage(searchMap, page, size);
PageResult pageResult=new PageResult(pageList.getTotal(),pageList.getResult());
return new Result(true,StatusCode.OK,"查询成功",pageResult);
}
}
⚡ 日志模块
表结构: tb_operatelog
需求: 1). 记录日志;2). 查询日志
8.4.3.1 创建工程
1). pom.xml
<dependencies>
<dependency>
<groupId>cn.itcast</groupId>
<artifactId>v_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.itcast</groupId>
<artifactId>v_model</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.itcast</groupId>
<artifactId>v_feign_api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- Eureka客户端依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<!--MySQL数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- fastJson依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<!-- Feign依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
2). application.yml
server:
port: 9003
spring:
application:
name: log
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/v_shop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: 2143
main:
allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://127.0.0.1:8161/eureka
instance:
prefer-ip-address: true
3). 引导类
@SpringBootApplication
@EnableEurekaClient
@MapperScan(basePackages = "cn.itcast.log.mapper")
public class LogApplication {
public static void main(String[] args) {
SpringApplication.run(LogApplication.class,args);
}
@Bean
public IdWorker idworker(){
return new IdWorker(0,0);
}
}
分布式ID生成
snowflake是 Twitter 开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0 ;
使用方式:
IdWorker idWorker=new IdWorker(1,1);//0-31 , 0-31
for(int i=0;i<10000;i++){
long id = idWorker.nextId();
System.out.println(id);
}
8.4.3.2 Mapper
mapper接口
public interface OperateLogMapper {
public void insert(TbOperatelog operationLog);
public List<TbOperatelog> search(Map searchMap);
}
OperateLogMapper.xml
<mapper namespace="cn.itcast.log.mapper.OperateLogMapper" >
<resultMap id="operateLogResultMap" type="cn.itcast.model.TbOperatelog">
<id column="id" jdbcType="BIGINT" property="id" />
<result column="model_name" jdbcType="VARCHAR" property="modelName" />
<result column="model_value" jdbcType="VARCHAR" property="modelValue" />
<result column="return_value" jdbcType="VARCHAR" property="returnValue" />
<result column="return_class" jdbcType="VARCHAR" property="returnClass" />
<result column="operate_user" jdbcType="VARCHAR" property="operateUser" />
<result column="operate_time" jdbcType="VARCHAR" property="operateTime" />
<result column="param_and_value" jdbcType="VARCHAR" property="paramAndValue" />
<result column="operate_class" jdbcType="VARCHAR" property="operateClass" />
<result column="operate_method" jdbcType="VARCHAR" property="operateMethod" />
<result column="cost_time" jdbcType="BIGINT" property="costTime" />
</resultMap>
<insert id="insert" parameterType="cn.itcast.model.TbOperatelog">
insert into tb_operatelog (id, model_name, model_value,
return_value, return_class, operate_user,
operate_time, param_and_value, operate_class,
operate_method, cost_time)
values (#{id}, #{modelName}, #{modelValue},
#{returnValue}, #{returnClass}, #{operateUser},
#{operateTime}, #{paramAndValue}, #{operateClass},
#{operateMethod}, #{costTime})
</insert>
<select id="search" resultMap="operateLogResultMap">
select * from tb_operatelog
<where>
<if test="operateUser != null and operateUser != ''">
and operate_user = #{operateUser}
</if>
<if test="operateMethod != null and operateMethod != ''">
and operate_method = #{operateMethod}
</if>
<if test="returnClass != null and returnClass != ''">
and return_class = #{returnClass}
</if>
<if test="costTime != null and costTime != '' ">
and cost_time = #{costTime}
</if>
</where>
</select>
</mapper>
8.4.3.3 Service
接口
public interface OperateLogService {
public void insert(TbOperatelog operationLog);
public Page<TbOperatelog> findPage(Map searchMap, Integer pageNum , Integer pageSize);
}
实现
@Service
@Transactional
public class OperateLogServiceImpl implements OperateLogService {
@Autowired
private OperateLogMapper operateLogMapper;
public void insert(TbOperatelog operationLog){
long id = idworker.nextId();
operationLog.setId(id);
operateLogMapper.insert(operationLog);
}
public Page<TbOperatelog> findPage(Map searchMap, Integer pageNum , Integer pageSize){
System.out.println(searchMap);
PageHelper.startPage(pageNum,pageSize);
List<TbOperatelog> list = operateLogMapper.search(searchMap);
return (Page<TbOperatelog>) list;
}
}
8.4.3.4 Controller
@RestController
@RequestMapping("/operateLog")
public class OperateLogController {
@Autowired
private OperateLogService operateLogService;
@RequestMapping("/search/{page}/{size}")
public Result findList(@RequestBody Map dataMap, @PathVariable Integer page, @PathVariable Integer size){
Page<TbOperatelog> pageList = operateLogService.findPage(dataMap, page, size);
PageResult pageResult=new PageResult(pageList.getTotal(),pageList.getResult());
return new Result(true, StatusCode.OK,"查询成功",pageResult);
}
@RequestMapping("/add")
public Result add(@RequestBody TbOperatelog operatelog){
operateLogService.insert(operatelog);
return new Result(true,StatusCode.OK,"添加成功");
}
}
8.4.3.5 AOP记录日志
在需要记录操作日志的微服务中, 引入AOP记录日志的类 :
1). 自定义注解
@Inherited
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperateLog {
}
2). AOP通知类
@Component
@Aspect
public class OperateAdvice {
@Autowired
private OperateLogFeign operateLogFeign;
@Around("execution(* cn.itcast.goods.controller.*.*(..)) && @annotation(operateLog)")
public Object insertLogAround(ProceedingJoinPoint pjp , OperateLog operateLog) throws Throwable{
System.out.println(" *********************************** 记录日志 [start] ****************************** ");
TbOperatelog op = new TbOperatelog();
DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
op.setOperateTime(sdf.format(new Date()));
op.setOperateUser("10000");
op.setOperateClass(pjp.getTarget().getClass().getName());
op.setOperateMethod(pjp.getSignature().getName());
String paramAndValue = "";
Object[] args = pjp.getArgs();
if(args != null){
for (Object arg : args) {
if(arg instanceof String || arg instanceof Integer || arg instanceof Long){
paramAndValue += arg +",";
}else{
paramAndValue += JSON.toJSONString(arg)+",";
}
}
op.setParamAndValue(paramAndValue);
}
long start_time = System.currentTimeMillis();
//放行
Object object = pjp.proceed();
long end_time = System.currentTimeMillis();
op.setCostTime(end_time - start_time);
if(object != null){
op.setReturnClass(object.getClass().getName());
op.setReturnValue(object.toString());
}else{
op.setReturnClass("java.lang.Object");
op.setParamAndValue("void");
}
operateLogFeign.add(op);
System.out.println(" *********************************** 记录日志 [end] ****************************** ");
return object;
}
}
(5) MyCat分片
当前数据库的情况 :
由于当前项目是一个电商项目,项目上线后,随着项目的运营,业务系统的数据库中的数据与日俱增,特别是订单、日志等数据,如果数据量过大,这个时候就需要考虑通过MyCat分库分表。
⚡ 分片分析
1). 垂直拆分
数据量过大,需要考虑扩容,可以通过MyCat来实现数据库表的垂直拆分,将同一块业务的数据库表拆分到同一个数据库服务中。拆分方式如下:
2). 全局表
按照上述的方式进行表结构的拆分,可以解决扩容的问题,但是存在另一个问题:由于省、市、区县、数据字典表,在订单及商品等模块中都需要用到,还会涉及到多表连接查询,那么这个时候涉及到跨库的join操作,可以使用全局表来解决。结构图如下:
3). 水平拆分
即使我们在上述的方案中使用垂直拆分,将系统中的表结构拆分到了三个数据库服务器中,但是对于当前这个比较繁忙的业务系统来说,每天都会产生大量的用户操作日志,长年累月,这张表的数据在单台服务器中已经存储不下了,这个时候,我们就可以使用MyCat的水平拆分来解决这个问题。
⚡ 各服务器信息
名称 | IP | 端口 | 用户名/密码 |
---|---|---|---|
MyCat-Server | 192.168.192.157 | 8066 | root/123456 |
MySQL-1 | 192.168.192.158 | 3306 | root/itcast |
MySQL-2 | 192.168.192.159 | 3306 | root/itcast |
MySQL-3 | 192.168.192.160 | 3306 | root/itcast |
MySQL-4 | 192.168.192.161 | 3306 | root/itcast |
⚡ 分片配置
1). 配置MyCat的schema.xml
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="V_SHOP" checkSQLschema="false" sqlMaxLimit="100">
<table name="tb_areas" dataNode="dn1,dn2,dn3,dn4" primaryKey="areaid" type="global"/>
<table name="tb_provinces" dataNode="dn1,dn2,dn3,dn4" primaryKey="provinceid" type="global"/>
<table name="tb_cities" dataNode="dn1,dn2,dn3,dn4" primaryKey="cityid" type="global"/>
<table name="tb_dictionary" dataNode="dn1,dn2,dn3,dn4" primaryKey="id" type="global"/>
<table name="tb_brand" dataNode="dn1" primaryKey="id" />
<table name="tb_category" dataNode="dn1" primaryKey="id" />
<table name="tb_sku" dataNode="dn1" primaryKey="id" />
<table name="tb_spu" dataNode="dn1" primaryKey="id" />
<table name="tb_order" dataNode="dn2" primaryKey="id" />
<table name="tb_order_item" dataNode="dn2" primaryKey="id" />
<table name="tb_order_log" dataNode="dn2" primaryKey="id" />
<table name="tb_operatelog" dataNode="dn3,dn4" primaryKey="id" rule="log-sharding-by-murmur"/>
</schema>
<dataNode name="dn1" dataHost="host1" database="v_goods" />
<dataNode name="dn2" dataHost="host2" database="v_order" />
<dataNode name="dn3" dataHost="host3" database="v_log" />
<dataNode name="dn4" dataHost="host4" database="v_log" />
<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.158: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.159: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.160:3306" user="root" password="itcast"></writeHost>
</dataHost>
<dataHost name="host4" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM4" url="192.168.192.161:3306" user="root" password="itcast"></writeHost>
</dataHost>
</mycat:schema>
2). 配置rule.xml
<tableRule name="log-sharding-by-murmur">
<rule>
<columns>id</columns>
<algorithm>log-murmur</algorithm>
</rule>
</tableRule>
<function name="log-murmur" class="io.mycat.route.function.PartitionByMurmurHash">
<property name="seed">0</property>
<property name="count">2</property>
<property name="virtualBucketTimes">160</property>
</function>
3). 配置server.xml
<user name="root" defaultAccount="true">
<property name="password">GO0bnFVWrAuFgr1JMuMZkvfDNyTpoiGU7n/Wlsa151CirHQnANVk3NzE3FErx8v6pAcO0ctX3xFecmSr+976QA==</property>
<property name="schemas">V_SHOP</property>
<property name="readOnly">false</property>
<property name="benchmark">1000</property>
<property name="usingDecrypt">1</property>
<!-- 表级 DML 权限设置
<privileges check="true">
<schema name="ITCAST" dml="1111" >
<table name="TB_TEST" dml="1110"></table>
</schema>
</privileges>
-->
</user>
4). 在各个MySQL数据库实例中创建数据库
MySQL-1 : v_goods
MySQL-2 : v_order
MySQL-3 : v_log
MySQL-4 : v_log
5). 导出本地的SQL脚本 , 在MyCat中执行SQL脚本 , 创建数据表 ,并导入数据
6). 连接测试
⚡ 微服务连接MyCat
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.192.157:8066/V_SHOP?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: 123456
⚡ 配置MyCat-Web监控
1). 启动Zookeeper
2). 启动MyCat-Web
3). 访问 http://192.168.192.157:8082/mycat
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!