强一致性:主机在写的时候,主机从机枷锁直到主从机的数据一样,解锁

基本原理:
slave会从master读取binlog来进行数据同步
具体步骤:
step1:master将数据改变记录到二进制日志(binary log)中。
step2: slave将master的binary log events拷贝到它的中继日志(relay log)中。
I/O线程去请求主库 的binlog,并将得到的binlog日志写到relay log(中继日志) 文件中log dump 线程,用来给slave的I/O线程线程传输binlogstep3:slave重做中继日志中的事件,将改变反映到自己的数据中。
SQL线程,读取relay log日志,并解析成具体操作,从而实现主从操作一致,最终数据一致。由此可见主从复制过程需要网络传输或大量的IO操作,这些操作会导致数据同步的延时
复制的基本原则:
准备两台服务器 可以克隆一台
主机: 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压缩包上传到/opt下
cd /opt
tar -zxvf Mycat-server-1.6.7.6-release-20220524173810-linux.tar.gz
配置文件打开MyCat目录结构如下

conf:配置文件目录核心的配置有3个conf目录
server配置server.xml中配置了MyCat作为虚拟数据库的基本信息

在这里插入图片描述
注意:分库分表必须是干净的库和表(不能有数据)
分片原则
# 创建数据库 从机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)
);
问题:以上四个表如何分库?
答案:客户表分在一个数据库,另外三张都需要关联查询,分在另外一个数据库。
<?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 stop
<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倍 -->
<!-- weightMapFile 节点的权重,没有指定权重的节点默认是1。以properties文件的格式填写,以从0开始到count-1的整数值也就是节点索引为key,以节点权重值为值。所有权重值必须是正整数,否则以1代替 -->
<!-- /etc/mycat/bucketMapPath
用于测试时观察各物理节点与虚拟节点的分布情况,如果指定了这个属性,会把虚拟节点的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 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>
<!--全局表-->
<!--
-->
</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>
-- 执行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 console
这里指的是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);

<table name="orders" dataNode="dn1,dn2" rule="order_rule" fetchStoreNodeByJdbc="true">
<childTable name="orders_detail" primaryKey="id" joinKey="order_id" parentKey="id" />
</table>
-- 执行orders_detail的建表语句
#订单详细表 rows:600万
CREATE TABLE `orders_detail`(
id INT AUTO_INCREMENT,
detail VARCHAR(2000),
order_id INT,
PRIMARY KEY(id)
);
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);

在分片的情况下,当业务表因为规模而进行分片以后,业务表与这些附属的字典表之间的关联,就成了比较棘手的问题,考虑到字典表具有以下几个特性:
(1)变动不频繁
(2)数据量总体变化不大
(3)数据规模不大,很少有超过数十万条记录
鉴于此,Mycat 定义了一种特殊的表,称之为“全局表”,全局表具有以下特性:
(1)全局表的插入、更新操作会实时在所有节点上执行,保持各个分片的数据一致性
(2)全局表的查询操作,只从一个节点获取
(3)全局表可以跟任何一个表进行 JOIN 操作
<table name="dict_order_type" dataNode="dn1,dn2" type="global" ></table>
CREATE TABLE `dict_order_type`(
id INT AUTO_INCREMENT,
order_type VARCHAR(200),
PRIMARY KEY(id)
);
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备机上线)