赞
踩
读写分离方案
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!-- 数据库配置 与server.xml中的数据库对应 即逻辑库 -->
<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="db_node">
</schema>
<!-- 分片配置 即分片节点 -->
<dataNode name="db_node" dataHost="db_host" database="db_test" />
<!-- 物理数据库配置 即节点主机 -->
<dataHost name="db_host" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<!-- can have multi write hosts -->
<writeHost host="hostM1" url="192.168.126.17:3305" user="root"
password="123456">
<readHost host="hostS1" url="192.168.126.18:3304" user="root" password="123456" />
</writeHost>
</mycat:schema>
参数:
schema参数:
dataNode参数:
dataHost参数:
dataHost:数据主机(节点主机)
dbType:数据库驱动native:MySQL JDBC: oracle SQLServer
Balance参数设置:
WriteType参数设置:
switchType参数设置:
在此新窗口mycat,重启mycat:mycat restart
进入mycat数据库管理命令行:mysql -u root -p123456 -h 192.168.126.17 -P 8066
这-p和密码不能有空格
如果连接不上,可能是配置文件schema.xml配错了,或者mysql没启动等原因
在数据端口8066命令行使用TESTDB数据库:use TESTDB
从MyCat数据端口命令行插入一条数据:insert into dog values(2,@@hostname);
@@hostname是docker的容器id,因为是插入操作,所以是master的id。
双主双从,即将对方作为自己的master,自己作为对方的slave来进行复制,但对外来讲,还是一主一从。
一主一从,读写压力比较大会出现性能性问题;使用一主多从来解决,防止master单点故障;又使用双主双从来解决。
准备4台虚拟机或者在,搭建双主双从
不小心区分了端口号,但是没有影响,问题不大
第一台虚拟机的MySQL服务master1:docker run -d -p 3360:3306 -e MYSQL_ROOT_PASSWORD=123456 --name=master1 mysql:latest
第二台虚拟机的MySQL服务slave1:docker run -d -p 3370:3306 -e MYSQL_ROOT_PASSWORD=123456 --name=slave1 mysql:latest
第三台虚拟机的MySQL服务master2:docker run -d -p 3380:3306 -e MYSQL_ROOT_PASSWORD=123456 --name=master2 mysql:latest
第四台虚拟机的MySQL服务slave2:docker run -d -p 3390:3306 -e MYSQL_ROOT_PASSWORD=123456 --name=slave2 mysql:latest
修改master1配置文件
将mysql.cnf文件拷贝出来:docker cp master1:/etc/my.cnf .
修改配置文件my.cnf:vim my.cnf
,添加如下配置:
[mysqld]
# 配置唯一ID
server-id=17
# 开启二进制日志
log-bin=mysql-bin
# 设置不要复制的数据库(可设置多个)
binlog-ignore-db=mysql
binlog-ignore-db=information_schema
# 设置logbin格式
binlog_format=STATEMENT
# 在作为从数据库的时候,有写入操作也要更新二进制文件
log-slave-updates
# 指自增字段的起始值,其默认值是1,取值范围是1~65535
auto_increment_increment=2
# 指字段一次递增多少,取值范围是1~65535
auto_increment_offset=1
将my.cnf文件拷贝回master1容器:docker cp my.cnf master1:/etc/
修改master2配置文件
将mysql.cnf文件拷贝出来:docker cp master2:/etc/my.cnf .
修改配置文件my.cnf:vim my.cnf
,添加如下配置:
[mysqld]
# 配置唯一ID
server-id=19
# 开启二进制日志
log-bin=mysql-bin
# 设置不要复制的数据库(可设置多个)
binlog-ignore-db=mysql
binlog-ignore-db=information_schema
# 设置需要复制的数据库
#binlog-do-db=需要复制的master数据库
# 设置logbin格式
binlog_format=STATEMENT
# 在作为从数据库的时候,有写入操作也要更新二进制文件
log-slave-updates
# 指自增字段的起始值,其默认值是1,取值范围是1~65535
auto_increment_increment=2
# 指字段一次递增多少,取值范围是1~65535
auto_increment_offset=1
将my.cnf文件拷贝回master2容器:docker cp my.cnf master2:/etc/
修改slave1配置文件
将mysql.cnf文件拷贝出来:docker cp slave1:/etc/my.cnf .
修改配置文件my.cnf:vim my.cnf
,添加如下配置:
[mysqld]
# 配置唯一ID
server-id=18
# 开启中继日志
relay-log=mysql-relay
将my.cnf文件拷贝回slave1容器:docker cp my.cnf slave1:/etc/
修改slave2配置文件
将mysql.cnf文件拷贝出来:docker cp slave2:/etc/my.cnf .
修改配置文件my.cnf:vim my.cnf
,添加如下配置:
[mysqld]
# 配置唯一ID
server-id=20
# 开启中继日志
relay-log=mysql-relay
将my.cnf文件拷贝回slave2容器:docker cp my.cnf slave2:/etc/
重启所有MySQL服务:
docker restart master1
docker restart master2
docker restart slave1
docker restart slave2
进入容器:docker exec -it 容器名或id bash
进入docker容器后只显示bash-4.4,则输入:cp /etc/skel/.bash* /root/
exit退出容器,再进容器就可以了。
如果此时若容器没有启动,可能是配置文件有问题。
在两个master中,进入到Mysql命令行,创建数据同步用户slave:
CREATE USER slave IDENTIFIED BY '123456';
GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%';
GRANT ALL PRIVILEGES ON *.* TO 'slave'@'%';
FLUSH PRIVILEGES;
因为版本是大于8.0.4的,需要在两个master上更换密码机制并修改密码:ALTER USER 'slave'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
查看用户的密码机制:select user,host,plugin from user;
查看master1的状态:show master status;
从库slave1连接主库master1,在从库slave1中的mysql命令行执行:change master to master_host='192.168.126.17',master_user='slave',master_port=3360,master_password='123456',master_log_file='mysql-bin.000007',master_log_pos=157;
启动复制功能:start slave;
查看master2的状态:show master status;
从库slave2连接主库master2,在从库slave2中的mysql命令行执行:change master to master_host='192.168.126.19',master_user='slave',master_port=3380,master_password='123456',master_log_file='mysql-bin.000006',master_log_pos=157;
启动复制功能:start slave;
查看连接状态:show slave status \G;
当两个线程都显示yes时,配置主从复制成功。
配置master1成为master2的slave
在master1的mysql命令行,配置为master2的slave:change master to master_host='192.168.126.19',master_user='slave',master_port=3380,master_password='123456',master_log_file='mysql-bin.000007',master_log_pos=157;
配置master2成为master1的slave
在master2的mysql命令行,配置为master1的slave:change master to master_host='192.168.126.17',master_user='slave',master_port=3360,master_password='123456',master_log_file='mysql-bin.000008',master_log_pos=157;
启动复制功能:start slave;
查看连接状态:show slave status \G;
当显示Slave_IO_Running: Yes,Slave_SQL_Running: Yes时,配置成功。
进入到/usr/local/mycat/conf目录下,编辑schema.xml文件:vim schema.xml
,配置如下:
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!-- 数据库配置 与server.xml中的数据库对应 即逻辑库 -->
<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="db_node">
</schema>
<!-- 分片配置 即分片节点 -->
<dataNode name="db_node" dataHost="db_host" database="db_test" />
<!-- 物理数据库配置 即节点主机 -->
<dataHost name="db_host" 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.126.17:3360" user="root" password="123456">
<readHost host="hostS1" url="192.168.126.18:3370" user="root" password="123456" />
</writeHost>
<writeHost host="hostM2" url="192.168.126.19:3380" user="root" password="123456">
<readHost host="hostS2" url="192.168.126.20:3390" user="root" password="123456" />
</writeHost>
</dataHost>
</mycat:schema>
即在一主一从的配置基础上,再配置另一个主从。
启动mycat:mycat start
目前很多互联网系统都存在单表数据量过大的问题,这就降低了查询数据,影响了客户体验。为了提高查询效率,可以优化SQL语句,优化表结构和索引,不过对那些百万级千万级的数据库表,即便是优化过后,查询速度还是满足不了要求。
垂直分割是指数据表列的拆分,把一张列比较多的表拆分为多张表。表的记录并不多,但是字段却很长,表占用空间很大,检索表的时候需要执行大量的IO,严重降低了性能。这时需要把大的字段拆分到另一个表,并且该表与原表是一对一的关系。
拆分原则:
修改mycat目录下的schema.xml配置文件,配置如下:
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!-- 数据库配置 与server.xml中的数据库对应 即逻辑库 -->
<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
<!-- 垂直拆分 -->
<table name="customer" dataNode="dn2"/>
</schema>
<!-- 分片配置 即分片节点 实现垂直分库 -->
<dataNode name="dn1" dataHost="host1" database="orders" />
<dataNode name="dn2" dataHost="host2" database="orders" />
<!-- 物理数据库配置 即节点主机 -->
<dataHost name="host1" 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.126.17:3360" user="root" password="123456">
</writeHost>
</dataHost>
<dataHost name="host2" 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.126.19:3380" user="root" password="123456">
</writeHost>
</dataHost>
</mycat:schema>
停止之前两个master的相互复制,都在mysql命令行中执行:stop slave;
两个master同时创建数据库orders:create database orders;
将两个master的密码机制都改为mysql_native_password:ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
这样MyCat才能访问到,否则密码不正确,因为Mysql是最新版的
重启mycat:mycat restart
进入到master1的Mysql命令行:mysql -u root -p123456 -h 192.168.126.17 -P 8066
在MyCat中,使用数据库TESTDB:use TESTDB;
创建表customer:create table customer(id int auto_increment,name varchar(200),primary key(id));
创建表orders:create table orders(id int auto_increment,order_type int,customer_id int,amount decimal(10,2),primary key(id));
创建表orders_detail:create table orders_detail(id int auto_increment,order_id int,detail varchar(200),primary key(id));
创建表dict_orders_type:create table dict_orders_type(id int auto_increment,order_type varchar(200),primary key(id));
此时在mycat的TESEDB逻辑库中,创建4个表,但是customer表被创建到master2的orders,其他都在master1的orders中,实现了分库。
即指定customer表都在master2的orders中执行,其余都在master1的orders中执行。
实现分表:
选择要拆分的表
MySQL单表存储条数是有瓶颈的,单表达到1000W条就达到了瓶颈,会影响查询效率,需要进行水平拆分(分表)来优化。
分表字段
orders表为例,可以根据不同字段进行分表。
字段id:查询订单注重时效,历史订单被查询的次数少,如此分片会造成一个节点访问多,一个节点访问少,不平均。
字段customer_id:根据客户id去分,两个节点访问平均。
修改配置文件schema.xml
<!-- 水平拆分 -->
<table name="orders" dataNode="dn1,dn2" rule="mod-rule" />
即为orders表设置数据节点为dn1、dn2,并指定分片规则为mod-rule(自定义)
修改配置文件rule.xml
<tableRule name="mod-rule">
<rule>
<columns>customer_id</columns>
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<!-- how many data nodes -->
<property name="count">2</property>
</function>
在master2中的orders数据库中创建orders表
create table orders(
id int auto_increment,
order_type int,
customer_id int,
amount decimal(10,2),
primary key(id)
);
重启mycat,生效配置:mycat restart
在mycat的8066端口插入数据,测试是否实现水平拆分
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);
在master1和master通过select查看分片是否正确即可。
问题,orders表分片了,那和它相关的orders_detail表未分片,join联查时,master1正常查询出结果,master2由于没有orders_detail表,则报错,最后聚合结果也是错的。
ER表,将子表的存储位置依赖于主表,并且物理上紧邻存放,因此彻底解决了JOIN的效率和性能问题,根据这一思路,提出了基于E-R关系的数据分片策略,子表的记录与所关联的父表记录存放在同一个数据分片上。
修改配置文件schema.xml,代码如下:
<table name="orders" dataNode="dn1,dn2" rule="mod-rule" >
<!-- ER表 -->
<childTable name="orders_detail" primaryKey="id" joinKey="order_id" parentKey="id" />
</table>
即在原先的table下添加一个子表childTable。
在master2中的orders数据库中创建orders_detail表:
create table orders_detail(
id int auto_increment,
order_id int,
detail varchar(200),
primary key(id)
);
重启Mycat:mycat restart
进入到8066端口,在mycat中插入orders_detail数据:
insert into orders_detail(detail, order_id) values('detail1',1);
insert into orders_detail(detail, order_id) values('detail1',2);
insert into orders_detail(detail, order_id) values('detail1',3);
insert into orders_detail(detail, order_id) values('detail1',4);
insert into orders_detail(detail, order_id) values('detail1',5);
在Mycat中使用join来测试ER表配置是否成功:select a.*,b.detail from orders a join orders_detail b on a.id=b.order_id;
create table dict_order_type(
id int auto_increment,
order_type varchar(200),
primary key(id)
);
在master1修改一下表名: alter table dict_orders_type rename to dict_order_type;
之前创建table错了好像,没有错就不用改名字
在schema.xml配置全局表,如下
<!-- 全局表 -->
<table name="dict_order_type" dataNode="dn1,dn2" type="global"></table>
重启MyCat:mycat restart
重新进入8066端口,插入数据:
insert into dict_order_type(id,order_type) values(101,'type1');
insert into dict_order_type(id,order_type) values(102,'type2');
此时在master1和master2都查询得到插入的两条记录:select * from dict_order_type;
实现原理,有些业务需要按照省份或区县来做保存,这类业务使用本条规则。
实现过程,需要定义三个值,规则均是在rule.xml中定义。
在master1和master2的orders数据库中,创建示例表orders_ware_info
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)
);
修改schema.xml文件
<!-- 枚举分片 -->
<table name="orders_ware_info" dataNode="dn1,dn2" rule="sharding-by-intfile" />
即表orders_ware_info,按照规则sharding-by-intfile进行分片
修改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>
<property name="type">1</property>
<property name="defaultNode">0</property>
</function>
即按照字段areacode进行枚举分片。
参数:
修改partition-hash-int.txt文件
10086=0
10010=1
0则代表第一个节点,1则代表第二个节点
重启mycat:mycat restart
进入8066端口,使用TESTDB数据库,插入2条数据:
INSERT INTO orders_ware_info(id, order_id,address,areacode) VALUES (1,1,'北京','10086');
INSERT INTO orders_ware_info(id, order_id,address,areacode) VALUES (2,2,'天津','10010');
实现原理,此分片适用于提前规划好分片字段的某个范围属于哪个分片。
在master1和master2的orders数据库中,创建示例表payment_info
CREATE TABLE payment_info
(
`id` INT AUTO_INCREMENT comment '编号',
`order_id` INT comment '订单编号',
`payment_status` INT comment '支付状态',
PRIMARY KEY(id)
);
修改配置文件schema.xml
<!-- 范围分片 -->
<table name="payment_info" dataNode="dn1,dn2" rule="auto-sharding-long" />
修改配置文件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>
</function>
参数
修改文件autopartition-long.txt
0-102=0
103-200=1
重启mycat:mycat restart
在8066端口插入数据用来测试:
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);
实现原理,此规则按天分片,设定时间格式、范围
在master1和master2中创建示例表login_info
create table login_info(
id int auto_increment comment '编号',
user_id int comment '用户编号',
login_date date comment '登录时间',
primary key(id)
);
修改配置文件schema.xml
<!-- 日期(天)分片 -->
<table name="login_info" dataNode="dn1,dn2" rule="sharding-by-date" />
修改配置文件rule.xml
<!-- 日期(天)分片 -->
<tableRule name="sharding-by-date">
<rule>
<columns>login_date</columns>
<algorithm>partbyday</algorithm>
</rule>
</tableRule>
<!-- 日期(天)分片 -->
<function name="partbyday" class="io.mycat.route.function.PartitionByDate">
<property name="dateFormat">yyyy-MM-dd</property>
<property name="sNaturalDay">0</property>
<property name="sBeginDate">2022-01-01</property>
<property name="sEndDate">2022-01-04</property>
<property name="sPartionDay">2</property>
</function>
参数
重启mycat:mycat restart
在8066端口插入数据用来测试:
insert into login_info(id,user_id,login_date) values(1,101,'2022-01-01');
insert into login_info(id,user_id,login_date) values(2,102,'2022-01-02');
insert into login_info(id,user_id,login_date) values(3,103,'2022-01-03');
insert into login_info(id,user_id,login_date) values(4,104,'2022-01-04');
insert into login_info(id,user_id,login_date) values(5,105,'2022-01-05');
insert into login_info(id,user_id,login_date) values(6,106,'2022-01-06');
解决方案
本地文件方式
此方式MyCat将sequence配置到文件中,当使用到sequence中的配置后,MyCat会更新classpath中的sequence_conf.properties文件中sequence当前的值。
本地时间戳方式
全局序列ID=64位二进制(42(毫秒)+ 5(机器ID)+ 5(业务编码)+ 12(重复累加))换算成十进制,为18位数的long类型,每毫秒可以并发12位二进制的累加
数据库方式
利用数据库的一个表来进行计数累加。但是并不是每次生成序列都读写数据库,这样效率太低。MyCat会预加载一部分号段到MyCat内存中,这样大部分读写序列都是在内存中完成的。如果内存中的号段用完了,MyCat会再向数据库要一次。
原理:在数据库中建立一张表,存放sequence名称(name),sequence当前值(current_value),步长(increment int类型每次读取多少个sequence,假设为K)等信息。
数据库解决全局序列
修改MyCat配置文件server.xml
<!-- 全局序列类型:0-本地文件,1-数据库方式,2-时间戳方式 -->
<property name="sequenceHandlerType">1</property>
修改配置文件schema.xml
<!-- 全局序列-数据库方式 -->
<table name="test" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2" rule="mod-long" />
<table name="mycat_sequence" primaryKey="name" dataNode="dn2" />
即前面一个表用来做测试,后面一个表就是存放sequence的表。
修改文件sequence_db_conf.properties
GLOBAL=dn1
COMPANY=dn1
CUSTOMER=dn1
ORDERS=dn1
MYCAT=dn2
在master2的orders数据库中添加MYCAT_SEQUENCE表
DROP TABLE IF EXISTS MYCAT_SEQUENCE;
CREATE TABLE MYCAT_SEQUENCE (name VARCHAR(50) NOT NULL,current_value INT NOT NULL,increment INT NOT NULL DEFAULT 100, PRIMARY KEY(name)) ENGINE=InnoDB;
MYCAT_SEQUENCE表插入初始记录
INSERT INTO MYCAT_SEQUENCE(name,current_value,increment) VALUES ('mycat', -99, 100);
代表插入了一个名为mycat的sequence,当前值为-99,步长为100。
在master2的orders数据库中创建全局序列所需存储过程
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 ;
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 ;
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 ;
重启mycat:mycat restart
在master1和master2中创建test表:
create table test(id int,name varchar(10));
在MyCat中向test表中添加测试数据
insert into test(id,name) values(next value for MYCATSEQ_MYCAT,(select database()));
查询数据进行验证,看是否自增:SELECT * FROM test order by id asc;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。