MySQL高级-读写分离-分库分表

主从复制

MySQL主从复制原理

强一致性:主机在写的时候,主机从机枷锁直到主从机的数据一样,解锁
在这里插入图片描述
基本原理:

slave会从master读取binlog来进行数据同步

具体步骤:

  • step1:master将数据改变记录到二进制日志(binary log)中。
    • 二进制日志由配置文件log-bin参数指定
    • 这些记录过程叫做二进制日志事件(binary log events)
  • step2: slave将master的binary log events拷贝到它的中继日志(relay log)中。
    • slave的I/O线程去请求主库 的binlog,并将得到的binlog日志写到relay log(中继日志) 文件中
    • master会生成一个 log dump 线程,用来给slave的I/O线程线程传输binlog
  • step3:slave重做中继日志中的事件,将改变反映到自己的数据中。
    • slave的SQL线程,读取relay log日志,并解析成具体操作,从而实现主从操作一致,最终数据一致。

由此可见主从复制过程需要网络传输或大量的IO操作,这些操作会导致数据同步的延时

复制的基本原则:

  • 每个master可以有多个salve
  • 每个slave只有一个master
  • 每个slave只能有一个唯一的服务器ID

一主一从常见配置

准备两台服务器 可以克隆一台

主机: 192.168.200.129
从机: 192.168.200.130
修改ip 地址:vim /etc/sysconfig/network-scripts/ifcfg-ens33
重启网络服务 :service network restart
注意一点:两台服务器中的 server-uuId 可能一致!
vim /var/lib/mysql/auto.cnf

配置/etc/my.cnf

			先配置主机:
			# 服务器唯一id
			server-id=1
			# 启用二进制日志,日志名是mysql-bin
			log-bin=mysql-bin
			# 设置不需要复制的数据库
			binlog-ignore-db=mysql
			binlog-ignore-db=infomation_schema
			# 设置需要复制的数据库
			binlog-do-db=mytestdb
			# 设置logbin格式
			binlog_format=STATEMENT

在主机上创建用户!

			-- 创建slave用户
			CREATE USER 'dudu_slave'@'%';
			-- 设置密码
			ALTER USER 'dudu_slave'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
			-- 授权
			GRANT REPLICATION SLAVE ON *.* TO 'dudu_slave'@'%';
			-- 刷新权限
			FLUSH PRIVILEGES;

在这里插入图片描述

修改从机配置文件:
			vim /etc/my.cnf
			# 服务器唯一id
			server-id=2
			# 启用中继日志
			relay-log=mysql-relay

下面的MASTER_LOG_FILE和MASTER_LOG_POS用到了上图标红的数据

重启mysql-slave 服务器!登录客户端,配置 当前读取台主机
			CHANGE MASTER TO MASTER_HOST='192.168.6.100', 
			MASTER_USER='dudu_slave',MASTER_PASSWORD='123456', MASTER_PORT=3306,
			MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=157; 
启动主从,查看状态:
			start slave;
			show slave status \G;
			
			<!--这两个必须为yes-->
			Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
测试:
			CREATE DATABASE mytestdb;
			USE mytestdb;
			CREATE TABLE mytbl(id INT,NAME VARCHAR(16));
			INSERT INTO mytbl VALUES(1, 'zhang3');
			INSERT INTO mytbl VALUES(2, @@hostname);
注意点:
			如果出现了错误:
			-- 在从机上执行。功能说明:停止I/O 线程和SQL线程的操作。
			stop slave; 

			-- 在从机上执行。功能说明:用于删除SLAVE数据库的relaylog日志文件,并重新启用新的relaylog文件。
			reset slave;

			-- 在主机上执行。功能说明:删除所有的binglog日志文件,并将日志索引文件清空,重新开始所有新的日志文件。
			-- 用于第一次进行搭建主从库时,进行主库binlog初始化工作;
			reset master;

MyCat简介

可以放在主机、从机、也可以单独一个服务器存放.

读写分离和分库分表具体的实现方式一般有两种:中间件封装程序代码封装

MyCat就是一个数据库中间件。

安装MyCat

把mycat压缩包上传到/opt下

cd /opt
tar -zxvf Mycat-server-1.6.7.6-release-20220524173810-linux.tar.gz

配置文件打开MyCat目录结构如下
在这里插入图片描述

  • bin:二进制执行文件
  • conf:配置文件目录
  • lib:依赖
  • logs:日志

配置MyCat

核心的配置有3个conf目录

  • server.xml:定义用户以及系统相关变量,如端口等
  • schemal.xml:定义逻辑库、表、分片节点等内容
  • rule.xml:定义分片规则

server配置server.xml中配置了MyCat作为虚拟数据库的基本信息
在这里插入图片描述
在这里插入图片描述

MyCat实现读写分离

MyCat数据分片

分片分析

注意:分库分表必须是干净的库和表(不能有数据)

分片原则

  1. 能不切分尽量不要切分。数据量不是很大的库或者表,尽量不要分片。单表行数 500W ,或者数据超过2G,才考虑分库分表!
  2. 尽量按照功能模块分库,避免跨库join。

数据准备

#  创建数据库  从机192.168.6.100
 create database orders;

#客户表  rows:20万 
CREATE TABLE `customer`(
    id INT AUTO_INCREMENT,
    NAME VARCHAR(200),
    PRIMARY KEY(id)
);
 
#  创建数据库  主机192.168.6.99
 create database orders;
 
#订单表   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)
);

问题:以上四个表如何分库?

答案:客户表分在一个数据库,另外三张都需要关联查询,分在另外一个数据库。

配置schema.xml实现MyCat分库:

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd"><mycat:schema xmlns:mycat="http://io.mycat/">

	<schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100" randomDataNode="dn1" dataNode="dn1">

			<table name="customer" dataNode="dn2"/>		
	</schema>
	
	<dataNode name="dn1" dataHost="host1" database="orders" />
	<dataNode name="dn2" dataHost="host2" database="orders" />
	
	
	
	<dataHost name="host2" maxCon="1000" minCon="10" balance="0"
			  writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1"  slaveThreshold="100">
		<heartbeat>select user()</heartbeat>
		
		<writeHost host="hostM2" url="jdbc:mysql://192.168.6.100:3306" user="root"
				   password="123456">
				   
				 
		</writeHost>
		
	</dataHost>
	
	
	<dataHost name="host1" maxCon="1000" minCon="10" balance="0"
			  writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1"  slaveThreshold="100">
		<heartbeat>select user()</heartbeat>
		
		<writeHost host="hostM1" url="jdbc:mysql://192.168.6.99:3306" user="root"
				   password="123456">
				   
				 
		</writeHost>
		
	</dataHost>
	
</mycat:schema>

创建表

主节点 :dn1 192.168.99 主机创建 orders 数据库 与 三张表

从节点 :dn2 192.168.6.100 从机创建 orders 数据库 与 一张客户表

查看分库实现

在这里插入图片描述

分表

先停止mycat服务:

./mycat stop

首先在rule.xml中配置分表规则

<tableRule name="order_rule">
    <rule>
        <columns>customer_id</columns>
        <algorithm>mod-long</algorithm>
    </rule>
</tableRule>
-- 修改原有配置文件的数据  3---2
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
    <!-- how many data nodes -->
    <property name="count">2</property>
</function>

完整的代码:

<?xml version="1.0" encoding="UTF-8"?>
<!-- - - Licensed under the Apache License, Version 2.0 (the "License"); 
	- you may not use this file except in compliance with the License. - You 
	may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 
	- - Unless required by applicable law or agreed to in writing, software - 
	distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 
	WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the 
	License for the specific language governing permissions and - limitations 
	under the License. -->
<!DOCTYPE mycat:rule SYSTEM "rule.dtd">
<mycat:rule xmlns:mycat="http://io.mycat/">

	<tableRule name="order_rule">
		<rule>
			<!-- 新建的分表规则 -->
			<columns>customer_id</columns>
			<algorithm>mod-long</algorithm>
		</rule>
	</tableRule>
	
	
	
	
	<tableRule name="rule1">
		<rule>
			<columns>id</columns>
			<algorithm>func1</algorithm>
		</rule>
	</tableRule>

	<tableRule name="sharding-by-date">
		<rule>
			<columns>createTime</columns>
			<algorithm>partbyday</algorithm>
		</rule>
	</tableRule>

	<tableRule name="rule2">
		<rule>
			<columns>user_id</columns>
			<algorithm>func1</algorithm>
		</rule>
	</tableRule>

	<tableRule name="sharding-by-intfile">
		<rule>
			<columns>sharding_id</columns>
			<algorithm>hash-int</algorithm>
		</rule>
	</tableRule>
	<tableRule name="auto-sharding-long">
		<rule>
			<columns>id</columns>
			<algorithm>rang-long</algorithm>
		</rule>
	</tableRule>
	<tableRule name="mod-long">
		<rule>
			<columns>id</columns>
			<algorithm>mod-long</algorithm>
		</rule>
	</tableRule>
	<tableRule name="sharding-by-murmur">
		<rule>
			<columns>id</columns>
			<algorithm>murmur</algorithm>
		</rule>
	</tableRule>
	<tableRule name="crc32slot">
		<rule>
			<columns>id</columns>
			<algorithm>crc32slot</algorithm>
		</rule>
	</tableRule>
	<tableRule name="sharding-by-month">
		<rule>
			<columns>create_time</columns>
			<algorithm>partbymonth</algorithm>
		</rule>
	</tableRule>
	<tableRule name="latest-month-calldate">
		<rule>
			<columns>calldate</columns>
			<algorithm>latestMonth</algorithm>
		</rule>
	</tableRule>

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

	<tableRule name="jch">
		<rule>
			<columns>id</columns>
			<algorithm>jump-consistent-hash</algorithm>
		</rule>
	</tableRule>

	<function name="murmur"
			  class="io.mycat.route.function.PartitionByMurmurHash">
		<property name="seed">0</property><!-- 默认是0 -->
		<property name="count">2</property><!-- 要分片的数据库节点数量,必须指定,否则没法分片 -->
		<property name="virtualBucketTimes">160</property><!-- 一个实际的数据库节点被映射为这么多虚拟节点,默认是160倍,也就是虚拟节点数是物理节点数的160倍 -->
		<!-- <property name="weightMapFile">weightMapFile</property> 节点的权重,没有指定权重的节点默认是1。以properties文件的格式填写,以从0开始到count-1的整数值也就是节点索引为key,以节点权重值为值。所有权重值必须是正整数,否则以1代替 -->
		<!-- <property name="bucketMapPath">/etc/mycat/bucketMapPath</property>
			用于测试时观察各物理节点与虚拟节点的分布情况,如果指定了这个属性,会把虚拟节点的murmur hash值与物理节点的映射按行输出到这个文件,没有默认值,如果不指定,就不会输出任何东西 -->
	</function>

	<function name="crc32slot"
			  class="io.mycat.route.function.PartitionByCRC32PreSlot">
		<property name="count">2</property><!-- 要分片的数据库节点数量,必须指定,否则没法分片 -->
	</function>
	<function name="hash-int"
			  class="io.mycat.route.function.PartitionByFileMap">
		<property name="mapFile">partition-hash-int.txt</property>
	</function>
	<function name="rang-long"
			  class="io.mycat.route.function.AutoPartitionByLong">
		<property name="mapFile">autopartition-long.txt</property>
	</function>
	<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
		<!-- how many data nodes 0 ,1 -->
		<property name="count">2</property>
	</function>

	<function name="func1" class="io.mycat.route.function.PartitionByLong">
		<property name="partitionCount">8</property>
		<property name="partitionLength">128</property>
	</function>
	<function name="latestMonth"
			  class="io.mycat.route.function.LatestMonthPartion">
		<property name="splitOneDay">24</property>
	</function>
	<function name="partbymonth"
			  class="io.mycat.route.function.PartitionByMonth">
		<property name="dateFormat">yyyy-MM-dd</property>
		<property name="sBeginDate">2015-01-01</property>
	</function>


	<function name="partbyday"
			  class="io.mycat.route.function.PartitionByDate">
		<property name="dateFormat">yyyy-MM-dd</property>
		<property name="sNaturalDay">0</property>
		<property name="sBeginDate">2014-01-01</property>
		<property name="sEndDate">2014-01-31</property>
		<property name="sPartionDay">10</property>
	</function>

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

	<function name="jump-consistent-hash" class="io.mycat.route.function.PartitionByJumpConsistentHash">
		<property name="totalBuckets">3</property>
	</function>
</mycat:rule>

在schema.xml中配置使用分表规则

<schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100" randomDataNode="dn1" dataNode="dn1">
		<table name="customer" dataNode="dn2"/>
		<table name="orders" dataNode="dn1,dn2"  rule="order_rule" ></table>
	</schema>

完整代码:

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd"><mycat:schema xmlns:mycat="http://io.mycat/">

	<schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100" randomDataNode="dn1" dataNode="dn1">

			<table name="customer" dataNode="dn2"/>
			
			<!-- 分表 -->
			<table name="orders" dataNode="dn1,dn2"  rule="order_rule" ></table>
			<!--全局表-->
			<!-- <table name="dict_order_type" dataNode="dn1,dn2" type="global" ></table> -->

			

	</schema>
	
	<dataNode name="dn1" dataHost="host1" database="orders" />
	<dataNode name="dn2" dataHost="host2" database="orders" />
	
	
	
	<dataHost name="host2" maxCon="1000" minCon="10" balance="0"
			  writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1"  slaveThreshold="100">
		<heartbeat>select user()</heartbeat>
		
		<writeHost host="hostM2" url="jdbc:mysql://192.168.6.100:3306" user="root"
				   password="123456">	   
				 
		</writeHost>
		
	</dataHost>
	
	
	<dataHost name="host1" maxCon="1000" minCon="10" balance="0"
			  writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1"  slaveThreshold="100">
		<heartbeat>select user()</heartbeat>
		
		<writeHost host="hostM1" url="jdbc:mysql://192.168.6.99:3306" user="root"
				   password="123456">
				   
				 
		</writeHost>
		
	</dataHost>
	
</mycat:schema>

手动在数据节点dn2-从机上建orders表

-- 执行orders的建表语句
#订单表   rows:600万
CREATE TABLE `orders`(
    id INT AUTO_INCREMENT,
    order_type INT,
    customer_id INT,
    amount DECIMAL(10,2),
    PRIMARY KEY(id)  
); 

最后启动mycat

在mycat的安装目录

./mycat console

往MyCat中插入几条数据:

这里指的是windows本地连接的Mycat

-- 在mycat里向orders表插入数据,INSERT时字段不能省略
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

修改schema配置文件

<table name="orders" dataNode="dn1,dn2"  rule="order_rule" fetchStoreNodeByJdbc="true">
    <childTable name="orders_detail" primaryKey="id" joinKey="order_id" parentKey="id" />
</table>

在数据节点dn2从机上建orders_detail表

-- 执行orders_detail的建表语句
 
#订单详细表     rows:600万
CREATE TABLE `orders_detail`(
    id INT AUTO_INCREMENT,
    detail VARCHAR(2000),
    order_id INT,
    PRIMARY KEY(id)
);

访问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);

测试:在mycat、dn1、dn2中运行两个表join语句

在这里插入图片描述

全局表–使用了

在分片的情况下,当业务表因为规模而进行分片以后,业务表与这些附属的字典表之间的关联,就成了比较棘手的问题,考虑到字典表具有以下几个特性:

(1)变动不频繁

(2)数据量总体变化不大

(3)数据规模不大,很少有超过数十万条记录

鉴于此,Mycat 定义了一种特殊的表,称之为“全局表”,全局表具有以下特性:

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

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

(3)全局表可以跟任何一个表进行 JOIN 操作

修改schema配置文件

<table name="dict_order_type" dataNode="dn1,dn2" type="global" ></table>

在dn2从机创建dict_order_type表

CREATE TABLE `dict_order_type`(
    id INT AUTO_INCREMENT,
    order_type VARCHAR(200),
    PRIMARY KEY(id)
);

访问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');

测试

在这里插入图片描述

全局序列

在实现分库分表的情况下,数据库自增主键已无法保证自增主键的全局唯一。

为此,Mycat 提供了全局 sequence,并且提供了包含本地配置和数据库配置等多种实现方式。

本地文件

此方式 Mycat 将 sequence 配置到文件中,当使用到 sequence 中的配置后,Mycat 会更新classpath 中的 sequence_conf.properties 文件中 sequence 当前的值。

优点:本地加载,读取速度较快

缺点:抗风险能力差,Mycat所在主机宕机后,无法读取本地文件。

时间戳方式

全局序列ID = 64 位二进制 (42(毫秒)+5(机器 ID)+5(业务编码)+12(重复累加) 换算成十进制为 18 位数的 long 类型,每毫秒可以并发 12 位二进制的累加。

在这里插入图片描述

第一位:固定为0 二进制里面第一个bit如果是1,表示负数,我们需要生产的数据都是正数,所以第一位要给 0

41bit: 时间戳

数值取值范围 2^41 -1

10 bit:

前5位可以为机房id, 后5位可以代表机器id。 也可以根据公司的实际情况自由定制。

12 bit: 自增序列

同一毫秒内,同一机器可以产生2^12-1 = 4096 个不同的id。

优点:配置简单

缺点:18位ID过长

自主生成全局序列

可在java项目里自己生成全局序列,如下:

根据业务逻辑组合

可以利用 redis的单线程原子性 incr来生成序列

但,自主生成需要单独在工程中用java代码实现,引入了分布式项目的复杂性。

数据库方式

利用数据库的一个表来进行计数累加。

在dn1主机上创建全局序列表

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;
-- 查询当前序列
SELECT * FROM MYCAT_SEQUENCE;
-- 删除全局序列表
TRUNCATE TABLE MYCAT_SEQUENCE;

创建全局序列所需函数
官方提供

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 ;

在dn1主机节点上初始化序列表记录

INSERT INTO MYCAT_SEQUENCE(NAME,current_value,increment) VALUES ('ORDERS', 400000,100);

修改MyCat配置
在这里插入图片描述
验证全局序列
登录MyCat,插入数据

INSERT INTO orders(id,amount,customer_id,order_type) VALUES(NEXT VALUE FOR MYCATSEQ_ORDERS,1000,101,102);

查询数据

SELECT * FROM orders;

重启Mycat后,再次插入数据,再查询(模拟Mycat备机上线)

  • 并不是每次生成序列都读写数据库,这样效率太低。
  • Mycat会预加载一部分号段到Mycat的内存中,这样大部分读写序列都是在内存中完成的。
  • 如果内存中的号段用完了 Mycat会再向数据库要一次。
  • 问:如果Mycat崩溃了 ,内存中的序列岂不是都没了?
  • 是的。如果是这样,那么Mycat启动后会向数据库申请新的号段,原有号段会弃用。
  • 也就是说如果Mycat重启,那么损失是当前的号段没用完的号码,但是不会因此出现主键重复
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值