[09] Mycat
概述#
是什么?#
- Mycat 是数据库中间件,前身是阿里的 Cobar。
- 中间件:是一类连接软件组件和应用的计算机软件,以便于软件各部件之间的沟通。如:Tomcat
- 数据库中间件:连接 Java 应用程序和数据库
- 为什么要用 Mycat?
- Java 与数据库紧耦合
- 高访问量高并发对数据库的压力
- 读写请求数据不一致
能干什么?#
读写分离
数据分片
垂直拆分(分库)、水平拆分(分表)、垂直+水平拆分(分库分表)
多数据源整合
数据库中间件对比#
- Cobar 属于阿里 B2B 事业群,始于 2008 年,在阿里服役 3 年多,接管 3000+ 个 MySQL 数据库的 schema,集群日处理在线 SQL 请求 50 亿次以上。由于 Cobar 发起人的离职,Cobar 停止维护。
- Mycat 是开源社区在阿里 Cobar 基础上进行二次开发,解决了 Cobar 存在的问题,并且加入了许多新的功能在其中,青出于蓝而胜于蓝。
- OneProxy 基于 MySQL 官方的 Proxy 思想利用 C 进行开发的,OneProxy 是一款商业收费的中间件。舍弃了一些功能,专注在性能和稳定性上。
- kingshard 由小团队用 Go 语言开发,还需要发展,需要不断完善。
- Vitess 是 Youtube 生产在使用,架构很复杂。不支持 MySQL 原生协议,使用需要大量改造成本。
- Atlas 是 360 团队基于 MySQL Proxy 改写,功能还需完善,高并发下不稳定。
- MaxScale 是 Mariadb(MySQL 原作者维护的一个版本) 研发的中间件。
- MySQLRoute 是 MySQL 官方 Oracle 公司发布的中间件。
Mycat 原理#
Mycat 的原理中最重要的一个动词是“拦截”,它拦截了用户发送过来的 SQL 语句,首先对 SQL 语句做了一些特定的分析,如:分片分析、路由分析、读写分离分析、缓存分析等,然后将此 SQL 发往后端的真实数据库,并将返回的结果做适当的处理,最终再返回给用户。
这种方式把数据库的分布式从代码中解耦出来,程序员察觉不出来后台使用 Mycat 还是 MySQL。
安装启动#
安装#
1. 解压缩文件拷贝到 Linux 的 /usr/local/ 下,解压后即可使用
2. 三个配置文件
- schema.xml:定义逻辑库,表、分片节点等内容
- rule.xml:定义分片规则
- server.xml:定义用户以及系统相关变量,如端口等
配置#
1. 修改 server.xml
2. 修改 schema.xml
删除 <schema>
标签间的表信息,<dataNode>
标签只留一个,<dataHost>
标签只留一个,<writeHost>
、<readHost>
只留一对。
3. 验证数据库访问情况
Mycat 作为数据库中间件要和数据库部署在不同机器上,所以要验证远程访问情况。
如果没登进去,我的报错是错误次数太多,使用命令:mysqladmin -u root -p flush-hosts
。
启动#
- 控制台启动:在 mycat/bin 目录下
./mycat console
- 后台启动:去 mycat/bin 目录下
./mycat start
登录#
- 后台管理窗口:
mysql -umycat -p123456 -P9066 -h192.168.206.129
- 数据窗口:
mysql -umycat -p123456 -P8066 -h192.168.206.129
搭建读写分离#
通过 Mycat 和 MySQL 的主从复制配合搭建数据库的读写分离,实现 MySQL 的高可用性。
以下将搭建:一主一从、双主双从两种读写分离模式。
一主一从#
一个主机用于处理所有写请求,一台从机负责所有读请求,架构图如下。
主从复制
回顾 MySQL 主从复制原理
- 修改主机配置文件:%MySQL_HOME%/my.ini
# 服务ID:保证整个集群环境中唯一 server-id=1 # 启用二进制日志,配置binlog日志存储路径和文件名 log-bin=U:/mysql/mysqlbin # 错误日志,默认已经开启 # log-err # 是否只读:1代表只读,0代表读写 read-only=0 # 忽略的数据, 指不需要同步(复制)的数据库 binlog-ignore-db=mysql # 指定同步(复制)的数据库 binlog-do-db=mydb # 设置 logbin 格式,就用默认的 MIXED # binlog_format=STATEMENT
- 修改从机配置文件:vim /etc/my.cnf
log_bin=mysqlbin # 开启 binlog 日志,日志的文件前缀 binlog_format=MIXED # 配置二进制日志的格式
- 主从机都重启 MySQL 服务、关闭防火墙
- 主机上创建用户,给从机做主从复制的权限
# 授权给Slave GRANT REPLICATION SLAVE ON *.* TO 'salve1101'@'%' IDENTIFIED BY '1101'; # 刷新权限列表 FLUSH PRIVILEGES; # 查看Master状态,这时候就不要再做操作了,防止接入点变化 SHOW MASTER STATUS;
- 在从机上配置需要复制的主机
# 在从机上配置需要复制的主机:指定当前从库对应的主库的IP地址 # 用户名,密码,从哪个日志文件开始的那个位置开始同步推送日志 CHANGE MASTER TO MASTER_HOST='192.168.206.1', MASTER_USER='salve1101', MASTER_PASSWORD='1101', MASTER_LOG_FILE='mysqlbin.000001', MASTER_LOG_POS=107; START SLAVE; # 启用从服务器复制功能 SHOW SLAVE STATUS\G; # 查看从机状态
- 测试:创建指定要同步的数据库
读写分离
验证读写分离:
- 在写主机插入:
INSERT INTO testMS VALUES (1, @@hostname);
造成主从机数据不一致 - 在 Mycat 里查询:
SELECT * FROM testMS;
schema.xml
修改 <dataHost>
的 balance 属性,通过此属性配置读写分离的类型,目前的取值有 4 种:
- balance="0":不开启读写分离机制,所有读操作都发送到当前可用的 writeHost 上。
- balance="1":全部的 readHost 与 stand by writeHost 参与 SELECT 语句的负载均衡,简单来说,当双主双从模式(M1->S1,M2->S2,并且 M1 与 M2 互为主备),正常情况下,M2,S1,S2 都参与 SELECT 语句的负载均衡。
- balance="2":所有读操作都随机地在 writeHost、readhost 上分发。
- balance="3":所有读请求随机地分发到 readhost 执行,writerHost 不负担读压力。
为了能看到读写分离的效果,把 balance 设置成 2,会在两个主机间切换查询:
双主双从#
一个主机 m1 用于处理所有写请求,它的从机 s1 和另一台主机 m2 还有它的从机 s2 负责所有读请求。当 m1 主机宕机后,m2 主机负责写请求,m1、m2 互为备机。架构图如下。
主从复制
(1) 双主机配置
① Master1 配置
# 主服务器唯一ID
server-id=1
# 启用二进制日志
log-bin=mysql-bin
# 设置不要复制的数据库(可设置多个)
binlog-ignore-db=mysql
binlog-ignore-db=information_schema
# 设置需要复制的数据库
binlog-do-db=需要复制的主数据库名字
# 设置logbin格式
binlog_format=STATEMENT
# 在作为从数据库的时候,有写入操作也要更新二进制日志文件
log-slave-updates
# 表示自增长字段每次递增的量,指自增字段的起始值,其默认值是 1,取值范围是 1 .. 65535
auto-increment-increment=2
# 表示自增长字段从哪个数开始,指字段一次递增多少,他的取值范围是 1 .. 65535
auto-increment-offset=1
② Master2 配置
server-id=3
log-bin=mysql-bin
binlog-ignore-db=mysql
binlog-ignore-db=information_schema
binlog-do-db=mydb
binlog_format=STATEMENT
log-slave-updates
auto-increment-increment=2
auto-increment-offset=2
(2) 双从机配置
① Slave1 配置
# 从服务器唯一ID
server-id=2
# 启用中继日志
relay-log=mysql-relay
② Slave2 配置
server-id=4
relay-log=mysql-relay
(3) 双主机、双从机重启 MySQL 服务
(4) 主机从机都关闭防火墙
(5) 在两台主机上建立帐户并授权 slave
#在主机 MySQL 里执行授权命令
GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%' IDENTIFIED BY '123123';
#查询 Master1、Master2 的状态,分别记录下 File 和 Position 的值
SHOW MASTER STATUS;
(6) 在从机上配置需要复制的主机:Slava1 复制 Master1,Slava2 复制 Master2
CHANGE MASTER TO
MASTER_HOST='主机的IP地址',
MASTER_USER='slave',
MASTER_PASSWORD='123123',
MASTER_LOG_FILE='mysql-bin.具体数字',
MASTER_LOG_POS=具体值;
(7) 启动两台从服务器复制功能
# 启动两台从服务器复制功能
START SLAVE;
#查看从服务器状态
SHOW SLAVE STATUS\G;
# 下面两个参数都是Yes,则说明主从配置成功!
# Slave_IO_Running: Yes
# Slave_SQL_Running: Yes
(8) 两个主机互相复制:Master2 复制 Master1,Master1 复制 Master2
(9) Master1 主机新建库、新建表、insert 记录,Master2 和从机复制
读写分离
修改 Mycat 的配置文件 schema.xml,修改 <dataHost>
的 balance 属性,通过此属性配置读写分离的类型。
- balance="1":全部的 readHost 与 stand by writeHost 参与 SELECT 语句的负载均衡。
- writeType="0":所有写操作发送到配置的第一个 writeHost,第一个挂了切到还活着的第二个;设置为 "1",所有写操作都随机的发送到配置的 writeHost,1.5 以后废弃不推荐。
- writeHost:重新启动后以切换后的为准,切换记录在配置文件 dnindex.properties 中。
- switchType="1" 默认值 1,表示自动切换;-1 表示不自动切换;2 基于 MySQL 主从同步的状态决定是否切换。
1. 验证读写分离:在写主机 Master1 数据库表 mytbl 中插入带系统变量数据,造成主从数据不一致。
2. 停止数据库 Master1,在 Mycat 里插入数据依然成功,Master2 自动切换为写主机。
垂直拆分——分库#
如何划分表#
系统被切分成了用户、订单交易、支付几个模块。
在两台主机上的两个数据库中的表,不可以关联查询!→【分库原则】有紧密关联关系的表应该在一个库里,相互没有关联关系的表可以分到不同的库里。
# 客户表 rows:20万
CREATE TABLE customer(
id INT AUTO_INCREMENT,
NAME VARCHAR(200),
PRIMARY KEY(id)
);
# 订单表 rows:600万
CREATE TABLE orders(
id INT AUTO_INCREMENT,
order_type INT,
customer_id INT,
amount DECIMAL(10,2),
PRIMARY KEY(id)
);
# 订单详细表 rows:600万
CREATE TABLE orders_detail(
id INT AUTO_INCREMENT,
detail VARCHAR(2000),
order_id INT,
PRIMARY KEY(id)
);
# 订单状态字典表 rows:20
CREATE TABLE dict_order_type(
id INT AUTO_INCREMENT,
order_type VARCHAR(200),
PRIMARY KEY(id)
);
以上四个表如何分库?客户表分在一个数据库,另外三张都需要关联查询,分在另外一个数据库。
配置分库#
1. 修改 schema 配置文件
2. 新增两个空白库:分库操作不是在原来的老数据库上进行操作,而是需要准备两台机器分别安装新的数据库。
# 在数据节点 dn1、dn2 上分别创建数据库 orders
CREATE DATABASE orders;
3. 启动 Mycat, 访问 Mycat 创建 4 张表,查看效果
水平拆分——分表#
相对于垂直拆分,水平拆分不是将表做分类,而是按照某个字段的某种规则来分散到多个库之中,每个表中包含一部分数据。简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中的某些行切分到一个数据库,而另外的某些行又切分到其他的数据库中,如图:
实现分表#
1. 选择要拆分的表
MySQL 单表存储数据条数是有瓶颈的,单表达到 1000 万条数据就达到了瓶颈,会影响查询效率,需要进行水平拆分(分表)进行优化。例如:示例中的 orders、orders_detail 都已经达到 600 万行数据,需要进行分表优化。
2. 分表字段
# 订单表 rows:600万
CREATE TABLE orders(
id INT AUTO_INCREMENT,
order_type INT,
customer_id INT,
amount DECIMAL(10,2),
PRIMARY KEY(id)
);
以 orders 表为例,可以根据不同字段进行分表。
3. 修改配置文件 schema.xml
4. 修改配置文件 rule.xml
<!-- 在 rule 配置文件里新增分片规则 mod_rule -->
<tableRule name="mod_rule">
<rule>
<!-- 指定规则适用字段为 customer_id -->
<columns>customer_id</columns>
<!-- 选择分片算法 mod-long (对字段求模运算) -->
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
...
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<!-- how many data nodes -->
<!--
就两个节点,故配置算法 mod-long 参数 count 为 2
customer_id 对两个节点求模,根据结果分片
-->
<property name="count">2</property>
</function>
5. 在数据节点 dn2 上建 orders 表
6. 重启 Mycat,让配置生效
7. 访问 Mycat 实现分片
# 在 Mycat 里向 orders 表插入数据,INSERT 后声明的字段列表不能省略!!!
# ERROR 1064 (HY000): partition table, insert must provide ColumnList
# ···········································································
INSERT INTO orders(id,order_type,customer_id,amount) VALUES(1,101,100,100100);
INSERT INTO orders(id,order_type,customer_id,amount) VALUES(2,101,100,100300);
INSERT INTO orders(id,order_type,customer_id,amount) VALUES(3,101,101,120000);
INSERT INTO orders(id,order_type,customer_id,amount) VALUES(4,101,101,103000);
INSERT INTO orders(id,order_type,customer_id,amount) VALUES(5,102,101,100400);
INSERT INTO orders(id,order_type,customer_id,amount) VALUES(6,102,100,100020);
分片 Join#
Orders 订单表已经进行分表操作了,和它关联的 orders_detail 订单详情表如何进行 Join 查询。
# 订单表 rows:600万
CREATE TABLE orders(
id INT AUTO_INCREMENT,
order_type INT,
customer_id INT,
amount DECIMAL(10,2),
PRIMARY KEY(id)
);
# 订单详细表 rows:600万
CREATE TABLE orders_detail(
id INT AUTO_INCREMENT,
detail VARCHAR(2000),
order_id INT,
PRIMARY KEY(id)
);
我们要对 orders_detail 也要进行分片操作。Join 的原理如下图:
E-R 表
Mycat 借鉴了 NewSQL 领域的新秀 Foundation DB 的设计思路,Foundation DB 创新性的提出了 Table Group 的概念,其将子表的存储位置依赖于主表,并且物理上紧邻存放,因此彻底解决了 JOIN 的效率和性能问题,根据这一思路,提出了基于 E-R 关系的数据分片策略,子表的记录与所关联的父表记录存放在同一个数据分片上。
1. 修改 schema.xml 配置文件
2. 在 dn2 创建 orders_detail 表
3. 重启 Mycat
4. 访问 Mycat 向 orders_detail 表插入数据
INSERT INTO orders_detail(id,detail,order_id) VALUES(1,'detail1',1);
INSERT INTO orders_detail(id,detail,order_id) VALUES(2,'detail1',2);
INSERT INTO orders_detail(id,detail,order_id) VALUES(3,'detail1',3);
INSERT INTO orders_detail(id,detail,order_id) VALUES(4,'detail1',4);
INSERT INTO orders_detail(id,detail,order_id) VALUES(5,'detail1',5);
INSERT INTO orders_detail(id,detail,order_id) VALUES(6,'detail1',6);
5. 在 Mycat、dn1、dn2 中运行两个表 Join 语句
全局表
在分片的情况下,当业务表因为规模而进行分片以后,业务表与这些附属的字典表之间的关联,就成了比较棘手的问题。考虑到字典表具有以下几个特性:
- 变动不频繁
- 数据量总体变化不大
- 数据规模不大,很少有超过数十万条记录
鉴于此,Mycat 定义了一种特殊的表,称之为“全局表”,全局表具有以下特性:
- 全局表的插入、更新操作会实时在所有节点上执行,保持各个分片的数据一致性
- 全局表的查询操作,只从一个节点获取
- 全局表可以跟任何一个表进行 JOIN 操作
将字典表或者符合字典表特性的一些表定义为全局表,则从另外一个方面,很好的解决了数据 JOIN 的难题(实质是通过数据冗余,达到分片的效果)。通过全局表+基于 E-R 关系的分片策略,Mycat 可以满足 80% 以上的企业应用开发。
1. 修改 schema.xml 配置文件
2. 在 dn2 创建 dict_order_type 表
3. 重启 Mycat
4. 访问 Mycat 向 dict_order_type 表插入数据
INSERT INTO dict_order_type(id,order_type) VALUES(101,'type1');
INSERT INTO dict_order_type(id,order_type) VALUES(102,'type2');
5. 在 Mycat、dn1、dn2 中查询表数据
常用分片规则#
取模
此规则为对分片字段取模运算。也是水平分表最常用规则。#1.1 配置分表中,orders 表采用了此规则。
分片枚举
通过在配置文件中配置可能的枚举 id,自己配置分片,本规则适用于特定的场景,比如有些业务需要按照省份或区县来做保存,而全国省份区县是固定的,则这类业务使用本条规则。
1. 修改 schema.xml 配置文件
2. 修改 rule.xml 配置文件
<tableRule name="sharding_by_intfile">
<rule>
<!-- 分片字段 -->
<columns>areacode</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>
<!-- 0 为 int 型、!0 为 String -->
<property name="type">1</property>
<!-- 默认节点:<0 表示不设置默认节点,>=0 表示设置默认节点 -->
<!-- 若设置默认节点,碰到不识别的枚举值,就让它路由到默认节点;若不设置,碰到不识别的就报错 -->
<property name="defaultNode">0</property>
</function>
3. 修改 partition-hash-int.txt 配置文件(与 schema.xml 同目录)
110=0
120=1
0 表示 dn1,1 表示 dn2
4. 重启 Mycat
5. 访问 Mycat 创建表
# 订单归属区域信息表
CREATE TABLE orders_ware_info(
`id` INT AUTO_INCREMENT comment '编号',
`order_id` INT comment '订单编号',
`address` VARCHAR(200) comment '地址',
`areacode` VARCHAR(20) comment '区域编号',
PRIMARY KEY(id)
);
6. 插入数据
INSERT INTO orders_ware_info(id,order_id,address,areacode) VALUES (1,1,'北京','110');
INSERT INTO orders_ware_info(id,order_id,address,areacode) VALUES (2,2,'天津','120');
7. 查询 Mycat、dn1、dn2 可以看到数据分片效果
范围约定
此分片适用于,提前规划好分片字段某个范围属于哪个分片。
1. 修改 schema.xml 配置文件
2. 修改 rule.xml 配置文件
<tableRule name="auto_sharding_long">
<rule>
<!-- 分片字段 -->
<columns>order_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>
3. 修改 autopartition-long.txt 配置文件
0-102=0
103-200=1
4. 重启 Mycat
5. 访问 Mycat 创建表
# 支付信息表
CREATE TABLE payment_info(
`id` INT AUTO_INCREMENT comment '编号',
`order_id` INT comment '订单编号',
`payment_status` INT comment '支付状态',
PRIMARY KEY(id)
);
6. 插入数据
INSERT INTO payment_info (id,order_id,payment_status) VALUES (1,101,0);
INSERT INTO payment_info (id,order_id,payment_status) VALUES (2,102,1);
INSERT INTO payment_info (id,order_id,payment_status) VALUES (3,103,0);
INSERT INTO payment_info (id,order_id,payment_status) VALUES (4,104,1);
7. 查询 Mycat、dn1、dn2 可以看到数据分片效果
按日期分片
此规则为按“天”分片,设定时间格式、范围。
1. 修改 schema.xml 配置文件
2. 修改 rule.xml 配置文件
<tableRule name="sharding_by_date">
<rule>
<columns>login_date</columns>
<algorithm>shardingByDate</algorithm>
</rule>
</tableRule>
...
<function name="shardingByDate" class="io.mycat.route.function.PartitionByDate">
<!-- 日期格式 -->
<property name="dateFormat">yyyy-MM-dd</property>
<!-- 开始日期 -->
<property name="sBeginDate">2019-01-01</property>
<!-- 结束日期,表示数据达到了这个日期的分片后,循环地再从开始分片插入 -->
<property name="sEndDate">2019-01-04</property>
<!-- 分区天数,即默认从开始日期算起,分隔 2 天一个分区 -->
<property name="sPartionDay">2</property>
</function>
11-01、11-02 插入 dn1;11-03、11-04 插入 dn2;如果不设置结束日期,11-05、11-06 就会去找 dn3 插入,我这就俩节点,肯定会报错。所以,要设置结束日期,让他再重新回到开始日期对应的节点进行插入,构成“循环插入”。
3. 重启 Mycat
4. 访问 Mycat 创建表
# 用户信息表
CREATE TABLE login_info(
`id` INT AUTO_INCREMENT comment '编号',
`user_id` INT comment '用户编号',
`login_date` date comment '登录日期',
PRIMARY KEY(id)
);
5. 插入数据
INSERT INTO login_info(id,user_id,login_date) VALUES (1,101,'2019-01-01');
INSERT INTO login_info(id,user_id,login_date) VALUES (2,102,'2019-01-02');
INSERT INTO login_info(id,user_id,login_date) VALUES (3,103,'2019-01-03');
INSERT INTO login_info(id,user_id,login_date) VALUES (4,104,'2019-01-04');
INSERT INTO login_info(id,user_id,login_date) VALUES (5,103,'2019-01-05');
INSERT INTO login_info(id,user_id,login_date) VALUES (6,104,'2019-01-06');
6. 查询 Mycat、dn1、dn2 可以看到数据分片效果
全局序列#
在实现分库分表的情况下,数据库自增主键已无法保证自增主键的全局唯一。为此,Mycat 提供了全局 sequence,并且提供了包含本地配置和数据库配置等多种实现方式。
本地文件
此方式 Mycat 将 sequence 配置到文件中,当使用到 sequence 中的配置后,Mycat 会更下 classpath 中的 sequence_conf.properties 文件中 sequence 当前的值。
优点:本地加载,读取速度较快。
缺点:抗风险能力差,Mycat 所在主机宕机后,无法读取本地文件。
数据库方式
概述:
利用数据库一个表来进行计数累加,但是并不是每次生成序列都读写数据库,这样效率太低。Mycat 会预加载一部分号段到 Mycat 的内存中,这样大部分读写序列都是在内存中完成的。如果内存中的号段用完了 Mycat 会再向数据库要一次。
详述:
在数据库中建立一张表,存放 sequence 名称(name),sequence 当前值(current_value),步长(increment,每次读取多少个 sequence,假设为 K) 等信息。
sequence 获取步骤:
- 第一次使用该 sequence 时,根据传入的 sequence 名称,从数据库这张表中读取 current_value 和 increment 到 MyCat 中,并将数据库中的 current_value 设置为原 current_value 值 + increment 值(实现方式是基于后续的存储函数)。
- MyCat 将读取到 current_value + increment 作为本次要使用的 sequence 值,下次使用时,自动加 1,当使用increment 次后,执行步骤 1 相同的操作。
MyCat 负责维护这张表,用到哪些 sequence,只需要在这张表中插入一条记录即可。若某次读取的 sequence 没有用完,系统就停掉了,则这次读取的 sequence 剩余值不会再使用。当 Mycat 重新启动后会向数据库申请新的号段,原有号段会弃用。也就是说,如果 Mycat 重启,那么损失是当前的号段没用完的号码,因此不会出现主键重复。
1. 数据库配置
# (1) 在 dn1 上创建全局序列表
CREATE TABLE MYCAT_SEQUENCE(
NAME VARCHAR(50) NOT NULL, # sequence名称
current_value INT NOT NULL, # 当前value
increment INT NOT NULL DEFAULT 100, # 增长步长,mycat在数据库中一次读取多少个sequence
PRIMARY KEY(NAME)
) ENGINE=INNODB;
# (2) 创建全局序列所需的存储函数
# a. 获取当前 sequence 的值(返回当前值,增量)
DROP FUNCTION IF EXISTS mycat_seq_currval;
DELIMITER $$
CREATE FUNCTION mycat_seq_currval(seq_name VARCHAR(50))
RETURNS VARCHAR(64) DETERMINISTIC
BEGIN
DECLARE retval VARCHAR(64);
SET retval="-999999999,null";
SELECT CONCAT(CAST(current_value AS CHAR),",",CAST(increment AS CHAR))
INTO retval FROM MYCAT_SEQUENCE WHERE NAME = seq_name;
RETURN retval;
END $$
DELIMITER ;
# b. 设置 sequence 值
DROP FUNCTION IF EXISTS mycat_seq_setval;
DELIMITER $$
CREATE FUNCTION mycat_seq_setval(seq_name VARCHAR(50), VALUE INTEGER)
RETURNS VARCHAR(64) DETERMINISTIC
BEGIN
UPDATE MYCAT_SEQUENCE
SET current_value = VALUE
WHERE NAME = seq_name;
RETURN mycat_seq_currval(seq_name);
END $$
DELIMITER ;
# c. 获取下一个 sequence 值
DROP FUNCTION IF EXISTS mycat_seq_nextval;
DELIMITER $$
CREATE FUNCTION mycat_seq_nextval(seq_name VARCHAR(50))
RETURNS VARCHAR(64) DETERMINISTIC
BEGIN
UPDATE MYCAT_SEQUENCE
SET current_value = current_value + increment
WHERE NAME = seq_name;
RETURN mycat_seq_currval(seq_name);
END $$
DELIMITER ;
# (3) 初始化序列表记录
INSERT INTO MYCAT_SEQUENCE(NAME,current_value,increment) VALUES ('ORDERS', 400000, 100);
2. Mycat 配置
1) 修改 sequence_db_conf.properties
# ORDERS 这个序列在 dn1 节点上,具体 dn1 节点是哪台机子,请参考 schema.xml
ORDERS=dn1
2) 修改 server.xml
3) 重启 Mycat
3. 验证全局序列
1) 插入数据,查询数据
INSERT INTO orders(id,amount,customer_id,order_type)
VALUES (next value for MYCATSEQ_ORDERS, 1000, 101, 102);
2) 重启 Mycat,插入数据,查询数据
时间戳方式
全局序列 ID = 64 位二进制 42(ms) + 5(机器 ID) + 5(业务编码) + 12(重复累加) 换算成十进制为 18 位数的 long 类型,每毫秒可以并发 12 位二进制的累加。
优点:配置简单
缺点:18 位 ID 过长
自主生成全局序列
可在 Java 项目里自己生成全局序列:① 根据业务逻辑组合;② 利用 Redis 的单线程原子性 incr 来生成序列。
但是,自主生成需要单独在工程中用 Java 代码实现(耦合),还是推荐使用 Mycat 自带全局序列。
基于 HA 机制的 Mycat 高可用#
在实际项目中,Mycat 服务也需要考虑高可用性,如果 Mycat 所在服务器出现宕机,或 Mycat 服务故障,需要有备机提供服务,需要考虑 Mycat 集群。
我们可以使用 HAProxy + Keepalived 配合两台 Mycat 搭起 Mycat 集群,实现高可用性。HAProxy 实现了 MyCat 多节点的集群高可用和负载均衡,而 HAProxy 自身的高可用则可以通过 Keepalived 来实现。
Mycat 安全设置#
权限配置#
user 标签
目前 Mycat 对于中间件的连接控制并没有做太复杂的控制,目前只做了中间件逻辑库级别的读写权限控制。是通过 server.xml 的 <user>
标签进行配置。
<user name="mycat">
<property name="password">123456</property>
<property name="schemas">TESTDB</property>
</user>
<user name="user">
<property name="password">user</property>
<property name="schemas">TESTDB</property>
<property name="readOnly">true</property>
</user>
配置说明
测试案例一:使用 user 用户,权限为只读(readOnly:true),验证是否可以查询出数据,验证是否可以写入数据。
- 用 user 用户登录:
mysql -uuser -puser -h 192.168.140.128 -P8066
- 切换到 TESTDB 数据库,查询 orders 表数据
- 执行插入数据 SQL,可看到运行结果:插入失败,只有只读权限
测试案例二:使用mycat用户,权限为可读写(readOnly:false),验证是否可以查询出数据,验证是否可以写入数据。
- 用 mycat 用户登录,运行命令如下:
mysql -umycat -p123456 -h 192.168.140.128 -P8066
- 切换到 TESTDB 数据库,查询 orders 表数据
- 执行插入数据 SQL,可看到运行结果:插入成功
privileges 标签
在<user>
标签下的 <privileges>
标签可以对逻辑库(schema)、表(table)进行精细化的 DML 权限控制。
<privileges>
标签下的 check 属性,如为 true 开启权限检查,为 false 不开启,默认为 false。由于 Mycat 一个用户的 schemas 属性可配置多个逻辑库(schema) ,所以 <privileges>
的下级节点 schema 节点同样可配置多个,对多库多表进行细粒度的 DML 权限控制。
<user name="mycat">
<property name="password">123456</property>
<property name="schemas">TESTDB</property>
<!-- 表级 DML 权限设置 -->
<privileges check="true">
<schema name="TESTDB" dml="1111" >
<table name="orders" dml="0000"></table>
</schema>
</privileges>
</user>
配置说明
测试案例一:使用 mycat 用户,privileges 配置 orders 表权限为禁止增删改查(dml="0000"),验证是否可以查询出数据,验证是否可以写入数据。
- 重启 mycat,用 mycat 用户登录:
mysql -umycat -p123456 -h 192.168.140.128 -P8066
- 切换到 TESTDB 数据库,查询 orders 表数据,运行结果:禁止该用户查询数据
- 执行插入数据sql,同理,禁止该用户插入数据
测试案例二:使用 mycat 用户,privileges 配置 orders 表权限为可以增删改查(dml="1111"),验证是否可以查询出数据,验证是否可以写入数据。
- 重启 mycat,用 mycat 用户登录:
mysql -umycat -p123456 -h 192.168.140.128 -P8066
- 切换到 TESTDB 数据库,查询 orders 表数据,查询成功
- 执行插入数据sql,插入成功
SQL 拦截#
server.xml 中的 <firewall>
标签用来定义防火墙,<firewall>
下 <whitehost>
标签用来定义 IP 白名单 ,blacklist 用来定义 SQL 黑名单。
白名单
可以通过设置白名单,实现某主机某用户可以访问 Mycat,而其他主机用户禁止访问。
# 配置只有 192.168.140.128 主机可以通过 mycat 用户访问
<firewall>
<whitehost>
<host host="192.168.140.128" user="mycat"/>
</whitehost>
</firewall>
黑名单
可以通过设置黑名单,实现 Mycat 对具体 SQL 操作的拦截,如增删改查等操作的拦截。
# 配置禁止 mycat 用户进行删除操作
<firewall>
<whitehost>
<host host="192.168.140.128" user="mycat"/>
</whitehost>
<blacklist check="true">
<property name="deleteAllow">false</property>
</blacklist>
</firewall>
测试删除操作:
可以设置的黑名单 SQL 拦截功能列表:
Mycat 监控平台#
Mycat-web 简介#
Mycat-web 是 Mycat 可视化运维的管理和监控平台,弥补了 Mycat 在监控上的空白。帮 Mycat 分担统计任务和配置管理任务。Mycat-web 引入了 ZooKeeper 作为配置中心,可以管理多个节点。Mycat-web 主要管理和监控 Mycat 的流量、连接、活动线程和内存等,具备 IP 白名单、邮件告警等模块,还可以统计 SQL 并分析慢 SQL 和高频 SQL 等,为优化 SQL 提供依据。
Mycat-web 配置使用#
ZooKeeper 安装
- 下载安装包 http://zookeeper.apache.org/
- 安装包拷贝到 Linux 系统 /opt 目录下,并解压:
tar -zxvf zookeeper-3.4.11.tar.gz
- 进入 ZooKeeper 解压后的配置目录(conf),复制配置文件并改名:
cp zoo_sample.cfg zoo.cfg
- 进入 ZooKeeper 的命令目录(bin),运行启动命令:
./zkServer.sh start
- ZooKeeper 服务端口为 2181,查看服务:
netstat -ant | grep 2181
Mycat-web 安装
- 下载安装包 http://www.mycat.io/
- 安装包拷贝到 Linux 系统 /opt 目录下,并解压:
tar -zxvf Mycat-web-1.0-SNAPSHOT-20170102153329-linux.tar.gz
- 拷贝 mycat-web 文件夹到 /usr/local 目录下:
cp -r mycat-web /usr/local
- 进入 mycat-web 的目录下运行启动命令:
./start.sh &
- Mycat-web 服务端口为 8082,查看服务:
netstat -ant | grep 8082
- 通过地址访问服务:http://192.168.140.127:8082/mycat/
Mycat-web 配置
Mycat 性能监控指标#
在 Mycat-web 上可以进行 Mycat 性能监控,例如:内存分享、流量分析、连接分析、活动线程分析等等。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?