MySql第四章,MyCat读写分离、分区分片

MySql第四章,MyCat读写分离、分区分片

一、读写分离

Redis是从 头 开始复制
MySql是从 接入点  开始复制

1、一主一从

1.1、MySql主从复制搭建

1>、主从配置

Master

修改配置文件: vim /etc/my.cnf
#主服务器唯-ID
server-id=l
#启用二进制日志。
log-bin=mysql-bin
#设置logbin格式
binlog_format=STATEMENT
#设置不要复制的数据库(可设置多个)。
binlog-ignore-db=mysql
binlog-ignore-db=information_schema
#设置需要复制的数据库
binlog-do-db=mycatdb    #该数据库一定要 master 还未创建,等开启主从复制后  再在主机上创建

解析logbin三种格式

statement:(默认)
	记录SQL语句,但是遇到update xxx set xxx time = now() where xxx;时   主从的time就会出现不一致
	
	[用statement效率高,但是不可以在 代码 中使用 函数]

row:
	记录行变化, 1 time->1.5   2 time->2  3 time->1;  如果全表更新的话,效率会很慢

MIXED
	mixed是statement和row的混合,解决了一部分数据不一致或效率慢的问题。
	但是遇到 @@host name 获取当前主机名称时,会识别不了

Slave

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

2>、关闭主从防火墙

systemctl stop firewalld
##查看防火墙状态  systemctl status firewalld

3>、Master上创建账户并赋权Slave

##授权
GRANT REPLICATION SLAVE ON *.* TO 'ren'@'192.168.0.107' IDENTIFIED BY 'admin123';
或
GRANT REPLICATION SLAVE ON *.* TO 'ren'@'%' IDENTIFIED BY'admin123";

##刷新权限
flush privileges;

4>、Slave上配置需要复制的主机

CHANGE MASTER TO MASTER_HOST='192.168.0.111',
MASTER_USER='ren',MASTER_PASSWORD='admin123',
MASTER_LOG_FILE='mysql-bin.000006',
MASTER_LOG_POS=154;

##启动从服务器复制功能
start slave;


##检测--下面两个参数都是YES,则说明主从配置成功  show slave status \G
show slave status \G

1.2、MyCat读写分离配置

1>、server.xml

##mycat对外暴露的逻辑MySQL,name为用户名、password为用户密码、schemas为逻辑数据库名称

<user name="mycat">
    <property name="password">mycat</property>
    <property name="schemas">TESTDB</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>

2>、schema.xml

##schema:mycat对外暴露的逻辑库
##dataNode:MySQL对应的数据库
##dataHost:MySql对应的主从名称

##############balance设置为3#############

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

        <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
        </schema>

    	<!--指定库名-->
        <dataNode name="dn1" dataHost="host1" database="mycatdb" />
        <dataHost name="host1" maxCon="1000" minCon="10" balance="3"
           writeType="0" dbType="mysql" dbDriver="native" switchType="1" 
           slaveThreshold="100">
                <heartbeat>select user()</heartbeat>
            	<!--写节点-->
                <writeHost host="hostM1" url="192.168.0.111:3306" user="root"
                                   password="admin123">
                    <!--读节点-->    
                    <readHost host="hostS1" url="192.168.0.107:3306" user="root" password="admin123" />
                </writeHost>
        </dataHost>

</mycat:schema>

balance参数解析说明:

修改dataHost的balance属性,通过此属性配置  读写分离  的类型

负载均衡类型,目前的取值有4种:。
1、balance="0",不开启读写分离机制,所有读操作都发送到当前可用的writeHost上。。

2、balance="1", 全部的readHost与stand by writeHost参与select 语句的负载均衡,简单的说,
  当双主双从模式(M1->S1,M2->S2, 并且M1与M2互为主备),正常情况下,M2,S1,S2 都参与select 
  语句的负载均衡。。

2、balance="2", 所有读操作都随机的在writeHost. readhost. 上分发。

4、balance="3", 所有读请求随机的分发到readhost 执行,writerHost 不负担读压力。

switchType参数解析说明:

switchType指的是切换的模式,目前的取值也有4种:

1. switchType='-1' 表示不自动切换

2. switchType='1' 默认值,表示自动切换

3. switchType='2' 基于MySQL主从同步的状态决定是否切换,心跳语句为 show slave status

4. switchType='3'基于MySQL galary cluster的切换机制(适合集群)(1.4.1),心跳语句为 show status like 'wsrep%'。

WriteType参数解析说明:

writeType负载均衡类型,目前的取值有3种:

1. writeType="0", 所有写操作发送到配置的第一个writeHost,第一个挂了切到还生存的第二个writeHost,重新启
动后已切换后的为准,切换记录在配置文件中:dnindex.properties

2. writeType="1",所有写操作都随机的发送到配置的writeHost

3. writeType="2",没实现。

1.3、测试

1、在master111上插入数据
create database mycatdb;
use mycatdb;
create table user(id int,name varchar(100));
insert into user values(1,@@hostname);

##查询Master111上数据和Slave107上的数据
##111
mysql> select * from user;
+------+---------+
| id   | name    |
+------+---------+
|    1 | master1 |
+------+---------+


##107
mysql> select * from user;
+------+--------+
| id   | name   |
+------+--------+
|    1 | slave1 |
+------+--------+


2、启动mycat
./mycat console
mysql -umycat -pmycat -h 192.168.0.111 -P 8066

mysql> show databases;
+----------+
| DATABASE |
+----------+
| TESTDB   |
+----------+
1 row in set (0.01 sec)
mysql> use TESTDB;
mysql> select * from user;
+------+--------+
| id   | name   |
+------+--------+
|    1 | slave1 |
+------+--------+
1 row in set (0.00 sec)

2、双主双从

一个主机m1用于处理所有写请求,它的从机s1和另一台主机m2还有它的从机s2负贵所有读请求。

当ml主机宕机后,m2主机负责写请求,ml、m2互为备机。

[master1 master2 互备主从,master1宕机后master2作为写主机,master1重启后作为master2的备机]

在这里插入图片描述

编号 角色 IP host_name
1 Master1 192.168.0.111 Master1
2 Slave1 192.168.0.107 Slave1
3 Master2 192.168.0.112 Master2
4 Slave2 192.168.0.108 Slave2

2.1、MySql双主双从搭建

1>、Master1-111机

配置 vi /etc/my.cnf

#主服务器唯一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.. 65535
auto-increment-offset=1
#表示自增长字段每次递增的量,取值范围是1 .. 65535
auto-increment-increment=2

创建账户并授权slave1

##授权
GRANT REPLICATION SLAVE ON *.* TO 'ren'@'%' IDENTIFIED BY'admin123";

##刷新权限
flush privileges;

##查看master1状态
show master status;

两台主机互相复制,master1复制master2

CHANGE MASTER TO MASTER_HOST='192.168.0.112',
MASTER_USER='ren',MASTER_PASSWORD='admin123',
MASTER_LOG_FILE='mysql-bin.000006',
MASTER_LOG_POS=154;

##启动从服务器复制功能
start slave;


##检测--下面两个参数都是YES,则说明主从配置成功  show slave status \G
show slave status \G

2>、Master2-112机

配置 vi /etc/my.cnf

#主服务器唯一ID
server-id=3
#启用二进制日志
log-bin-mysql-bin
#设置不要复制的数据库(可设置多个)
binlog-ignore-db=mysql
binlog-ignore-db=information_schema
#设置需要复制的数据库
binlog-do-db=需要复制的主数据库名字
#设置logbin格式
binlog_format=STATEMENT
#在作为从数据库的时候,有写入操作也要更新二进制日志文件
log-slave-updates
#表示自增长字段从哪个数开始,取值范围是1.. 65535
auto-increment-offset=2
#表示自增长字段每次递增的量,取值范围是1 .. 65535
auto-increment-increment=2

创建账户并授权slave2

##授权
GRANT REPLICATION SLAVE ON *.* TO 'ren'@'%' IDENTIFIED BY'admin123";

##刷新权限
flush privileges;

##查看master1状态
show master status;

两台主机互相复制,master2复制master1

CHANGE MASTER TO MASTER_HOST='192.168.0.111',
MASTER_USER='ren',MASTER_PASSWORD='admin123',
MASTER_LOG_FILE='mysql-bin.000006',
MASTER_LOG_POS=154;

##启动从服务器复制功能
start slave;


##检测--下面两个参数都是YES,则说明主从配置成功  show slave status \G
show slave status \G

3>、Slave1-107机

配置 vi /etc/my.cnf

#从服务器唯- ID
server-id=2
#启用中继日志。
relay-log=mysql-relay

连接需要复制的主机master1

CHANGE MASTER TO MASTER_HOST='192.168.0.111',
MASTER_USER='ren',MASTER_PASSWORD='admin123',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=154;

##启动从服务器复制功能
start slave;


##检测--下面两个参数都是YES,则说明主从配置成功  show slave status \G
show slave status \G

4>、Slave2-108机

配置 vi /etc/my.cnf

#从服务器唯- ID
server-id=4
#启用中继日志。
relay-log=mysql-relay

连接需要复制的主机master2

CHANGE MASTER TO MASTER_HOST='192.168.0.112',
MASTER_USER='ren',MASTER_PASSWORD='admin123',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=154;

##启动从服务器复制功能
start slave;


##检测--下面两个参数都是YES,则说明主从配置成功  show slave status \G
show slave status \G

2.2、MyCat读写分离配置

配置:schema.xml

##balance设置为1

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

        <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
        </schema>

    	<!--指定库名-->
        <dataNode name="dn1" dataHost="host1" database="mycatdb" />
        <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.0.111:3306" user="root"
                                   password="admin123">
                    <!--读节点-->    
                    <readHost host="hostS1" url="192.168.0.107:3306" user="root" password="admin123" />
                </writeHost>
            
                <!--写节点-->
                <writeHost host="hostM1" url="192.168.0.112:3306" user="root"
                                   password="admin123">
                    <!--读节点-->    
                    <readHost host="hostS1" url="192.168.0.108:3306" user="root" password="admin123" />
                </writeHost>
            
        </dataHost>

</mycat:schema>

2.3、测试

##这里就不再写测试方法了,参考1.3一主一从的测试步骤即可

二、垂直拆分–分库

一个数据库由很多表的构成,每个表对应着不同的业务,垂直切分是指按照业务将表进行分类,
分布到不同的数据库上面,这样也就将数据或者说压力分担到不同的库上面。

注:不同库中的表无法做关联查询(left join、right join)

[垂直分库的主要作用是减轻磁盘的压力,单表的数据量并未减少]

在这里插入图片描述

修改schema.xml

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

        <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
            <table name="users" dataNode="dn2"></table>
            <table name="comments" dataNode="dn2"></table>
        </schema>

    	<!--指定库名-->
        <dataNode name="dn1" dataHost="host1" database="shopping" />
        <dataNode name="dn2" dataHost="host2" database="shopping" />
    
    
        <dataHost name="host1" maxCon="1000" minCon="10" balance="3"
           writeType="0" dbType="mysql" dbDriver="native" switchType="1" 
           slaveThreshold="100">
                <heartbeat>select user()</heartbeat>
            	<!--写节点-->
                <writeHost host="hostM1" url="192.168.0.111:3306" user="root"
                                   password="admin123">
                    <!--读节点-->    
                    <readHost host="hostS1" url="192.168.0.107:3306" user="root" password="admin123" />
                </writeHost>
        </dataHost>
    
    
        <dataHost name="host2" maxCon="1000" minCon="10" balance="3"
           writeType="0" dbType="mysql" dbDriver="native" switchType="1" 
           slaveThreshold="100">
                <heartbeat>select user()</heartbeat>
            	<!--写节点-->
                <writeHost host="hostM1" url="192.168.0.112:3306" user="root"
                                   password="admin123">
                    <!--读节点-->    
                    <readHost host="hostS1" url="192.168.0.108:3306" user="root" password="admin123" />
                </writeHost>
        </dataHost>

</mycat:schema>

测试:

1、分别启动四个MySQL服务和mycat服务

2、在mycat上创建库和四张表

##111上创建库
create database shopping;

##112上创建库
create database shopping;

##mycat上创建表
create table orders(......);

##mycat上创建表
create table goods(......);

##mycat上创建表
create table users(.......);

##mycat上创建表
create table comments(.......);

3、结果:
orders和goods表被分配到了 dn1即 111和107数据库上了
users和comments表被分配到了 dn2即 112和108数据库上了

三、水平拆分–分表

相对于垂直拆分,水平拆分不是将表做分类,而是按照某个字段的某种规则来分散到多个库之中,每个表中包含一部分数据。

简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中的某些行切分到一个数据库,而另外的某些行又切分到其他的数据库中。

在这里插入图片描述

1、选择要拆分的表

MySQL单表存储数据条数是有瓶颈的,单表达到1000万条数据就达到了瓶颈,会影响查询效率,
需要进行水平拆分(分表)进行优化。

2、确定分表的字段

根据字段进行拆分,如所属区域  所属年份  所属类型  或  客户ID
如:600W数据量的orders订单表,我们可以根据客户ID进行划分,同一个客户的订单划分到同一张表里

1、单表的拆分

拿order表做例子:

schema.xml

##为orders表设置数据节点为dn1、dn2,并指定分片规则为mod_rule(自定义的名称)

<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
    <table name="users" dataNode="dn2"></table>
    <table name="orders" dataNode="dn1,dn2" rule="mod_rule"></table>
</schema>

rule.xml

#在rule配置文件里新增分片规则mod_ _rule, 并指定规则适用字段为customer_ jid,。
#还有选择分片算法mod-long (对字段求模运算),customer_id对两个节点求模,根据结果分片。
#配置算法mod-long参数count 为2,两个节点。


<tableRule name="mod_rule">
    <rule>
        <columns>customer_id</columns>  <!--根据customer_id字段划分-->
        <algorithm>mod-long</algorithm> <!--算法-->
    </rule>
</tableRule>


<!--这个本身mycat自带了,只需要设置count就好-->
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
    <!-- how many data nodes -->
    <property name="count">2</property>
</function>


测试:

1、分别在111和112上创建orders表
CREATE TABLE orders(
    id INT AUTO INCREMEN,
    order_type INT,
    customer_ id INT,
    amount DECIMAL(10,2),
    PRIMARY KEY(id)
}

2、mycat向orders表插入数据
[注意:customer_id必须指定,不能写成insert into order values (xxxxxxxx)]
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);


3、结果如图:3、4、5在一张表中  1、2、6在另一张表中

在这里插入图片描述

上图的结果,其实是对两个库中的两张表进行了union,即345和126合并了,如果想按顺序可以order by
select * from orders order by id;

2、分片后的 join

Orders订单表已经进行分表操作了,和它关联的orders_ detail 订单详情表如何进行join查询。

我们要对orders_detail 也要进行分片操作。

CREATE TABLE orders_ detail(
    id INT AUTO INCREMENT,
    detail VARCHAR(2000),
    order_id INT,
    PRIMARY KEY(id)
);

由于orders表进行的拆分,所以如果orders表在和没有拆分的orders_detail表进行 关联查询的时候,就会报错。

即一个库里边有orders_detail,另一个库里没有orders_detail。orders关联不到,所以orders_detail表也要进行拆分。

如何拆分orders_detail表呢,应该根据order_id进行拆分,即将和orders表有关联的orders_detail行拆分到一个库中。

2.1、ER表分表

Mycat借鉴了NewSQL 领域的新秀Foundation DB的设计思路,Foundation DB创新性的提出了Table Group的概念,
其将子表的存储位置依赖于主表,并且物理上紧邻存放,因此彻底解决了JION的效率和性能问题,根据这一思路, 提出了基
于E-R关系的数据分片策略,子表的记录与所关联的父表记录存放在同一个数据分片上。

修改schema.xml配置文件

<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
    <table name="users" dataNode="dn2"></table>
    <table name="orders" dataNode="dn1,dn2" rule="mod_rule">
        <!--ER表-->
    	<childTable name="orders_detail" primaryKey="id" joinKey="order_id" parentKey="id" />
    </table>
</schema>

##joinKey为orders_detail表的外键
##parentKey为joinKey外键所关联的orders表的主键
##适用于1:1或1:N的表关系中

在这里插入图片描述

2.2、全局表

##订单状态字典表
CREATE TABLE dict_order_type(
    id INT AUTO INCREMENT,
    order_type VARCHAR(200),
    PRIMARY KEY(id)
);

dict_order_type表作为一个字典表,order表无论是在dn1库还是在dn2库中都会需要用到。

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

①、变动不频繁。
②、数据量总体变化不大。
③、数据规模不大,很少有超过数十万条记录。

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

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

②全局表的查询操作,只从一个节点获取。

③全局表可以跟任何一个表进行JOIN操作。

将字典表或者符合字典表特性的一些表定 义为全局表,则从另外一个方面,很好的解决了数据JOIN的难题。通过全局表+基于E-R 关系的分片策略,MyCAT可以满足80%以上的企业应用开发。

修改schema.xml:

<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
    <table name="users" dataNode="dn2"></table>
    <table name="orders" dataNode="dn1,dn2" rule="mod_rule">
        <!--ER表-->
    	<childTable name="orders_detail" primaryKey="id" joinKey="order_id" parentKey="id" />
    </table>
    
    <!--全局表-->
    <table name="dict_order_type" dataNode- ="dn1,dn2" type="global" > </table>
</schema>

3、常用的分片规则

mycat的rule.xml中定义了以下常用分片规则:

<function name="murmur" class="io.mycat.route.function.PartitionByMurmurHash">
    <property name="seed">0</property>
    <property name="count">2</property>
    <property name="virtualBucketTimes">160</property>
</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>
    <property name="type">1</property>
    <property name="defaultNode">0</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">
    <property name="count">3</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="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>

3.1、取模:mod-long

此规则为对分片字段求摸运算。也是水平分表最常用规则。上面orders 表就采用了此规则。

【如:一般根据库的个数进行取模】

<tableRule name="mod_rule">
    <rule>
        <columns>customer_id</columns>  <!--根据customer_id字段划分-->
        <algorithm>mod-long</algorithm> <!--算法-->
    </rule>
</tableRule>

3.2、分片枚举:hash-int

通过在配置文件中配置可能的枚举id,自己配置分片,本规则适用于特定的场景,比如有些业务需要按照省份或区县来做保存,而全国省份区县固定的,这类业务使用本条规则。

【如:根据省份prov划分】

修改schema.xml

...
<table name="orders_are_info" dataNode="dn1,dn2" rule="sharding_by_intfile"></table>
...

修改rule.xml

<tableRule name-"sharding_by_intfile">
    <rule>
        <columns>areacode</columns>  <!--划分字段-->
        <algorithm>hash-int</algorithm>  <!--划分算法-->
    </rule>
</tableRule>

<function name="hash-int" class="io.mycatroute.function.PartitionByFileMap">
    <property name="mapFile">partition-hash-int.txt</property>
    <property name="type">1</property>
    <property name="defaultNode">0</property>
</function>

##mapFile标识配置文件名称
##type 0为int型、非0为String型
##defaultNode: 默认节点:小于0表示不设置默认节点,大于等于0表示设置默认节点,设置默认节点如果碰到不识别的
枚举值,就让它路由到默认节点,如不设置不识别就报错

修改partition-hash-int.txt

##areacode为110放在node0节点,120放在node1节点
110=0
120=1

创建数据表 且 插入数据:

CREATE TABLE orders_are_info(
    'id' InT AUTO_INCREMENT comment'编号',
    'order_id' INT comment '订单编号',
    'address' VARCHAR(200) comment '地址',
    'areacode' VARCHAR(20) comment '区域编号',
    PRIMARY KEY(id)
);INSERT INTO orders_are_info(id, order_id,address,areacode) VALUES (1,1,北京,110);
INSERT INTO orders_are_info(id, order_id,address,areacode) VALUES (2,2,天津,120);

3.3、范围约定:rang-long

此分片适用于,提前规划好分片字段某个范围属于哪个分片。

修改schemax.xml

<table name="payment_info" dataNode="dn1,dn2" rule="auto_sharding_long" > </table>

修改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>
##defaultNode: 默认节点:小于0表示不设置默认节点,大于等于0表示设置默认节点,设置默认节点如果碰到不识别的
枚举值,就让它路由到默认节点,如不设置不识别就报错

修改autopartition-long.txt:

##order_id在0-102写入node0  103-200写入node1
0-102=0
103-200=1

创建数据表 并 插入数据:

CREATE TABLE payment_info(
    'id' INT AUTO_INCREMENT comment '编号',
    'orde_id' INT comment '订单编号',
    'payment_status'INT comment '支付状态',
    PRIMARY KEY(id)
);

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

3.4、按日期(天)分片

修改schema.xml

...
<table name="login_info" dataNode="dn1,dn2" rule="sharding_by_date"></table>
...

修改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>
    <prpperty name="sBeginDate">2019-01-01</property>
    <property name="sEndDate">2019-01-04</property>
    <property name="sPartionDay">2</property>
</function>

#columns:分片字段,algorithm: 分片函数。
#dateFormat :日期格式。
#sBeginDate :开始日期。
#sEndDate:结束日期,则代表数据达到了这个日期的分片后循环从开始分片插入。
#sPartionDay :分区天数,即默认从开始日期算起,分隔2天一一个分区。

##如果循环完成则返回第一个节点,以上01、02为node1,03、04为node2.当05、06分配时,则会循环到第一个分片即分
配到node1上

创建数据表 并 插入数据:

CREATE TABLE login_info(
    'id' INT AUTO_INCREMENT comment '编号''user_id' INT comment '用户编号',
    'login_date' DATE comment '登录日期'PRIMARY KEY(id)
);
    
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');

4、全局序列号

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

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

4.1、本地文件–不推荐

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

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

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

#default global sequence
GLOBAL.HISIDS=
GLOBAL.MINID=10001
GLOBAL.MAXID=20000
GLOBAL.CURID=10000

其中HISIDS表示使用过的历史分段(一般无特殊需要可不配置),MINID表示最小ID值,MAXID表示最大ID值,CURID表示当前ID值。

使用方式:

在server.xml中配置如下:

###全局序列类型: 0-本地文件,1-数据库方式,2-时间戳方式。此处应该修改成1。
<property name="sequnceHandlerType">0</property>

测试:

mysql> create table test(id int,name varchar(20));
Query OK, 0 rows affected (0.13 sec)

mysql> insert into test(id,name) values(next value for MYCATSEQ_GLOBAL,@@hostname);
Query OK, 1 row affected, 1 warning (0.03 sec)

mysql> select * from test;
+-------+--------------+
| id    | name         |
+-------+--------------+
| 10001 | mysql-slave2 |
+-------+--------------+
1 row in set (0.02 sec)

此时,sequence_conf.properties中GLOBAL.CURID值为10001。当然,可以使用sequence_conf.properties中定义的任何规则,譬如:

# self define sequence
COMPANY.HISIDS=
COMPANY.MINID=1001
COMPANY.MAXID=2000
COMPANY.CURID=1000


mysql> insert into test(id,name) values(next value for MYCATSEQ_COMPANY,@@hostname);
Query OK, 1 row affected, 1 warning (0.02 sec)

mysql> select * from test;
+-------+--------------+
| id    | name         |
+-------+--------------+
| 10001 | mysql-slave1 |
|  1001 | mysql-slave1 |
+-------+--------------+
2 rows in set (0.10 sec)

4.2、时间戳方式–雪花算法

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

①优点:配置简单

②缺点: 18位ID过长

1>、server.xml指定时间戳方式生成序列方式

###全局序列类型: 0-本地文件,1-数据库方式,2-时间戳方式。此处应该修改成1。
<property name="sequnceHandlerType">2</property>

2>、schema.xml中配置分片表

注意分片表的主键为自增长,分片表的主键类型不能为int类型

<schema name="TESTTIME" checkSQLschema="false" sqlMaxLimit="100">
    <table name="test" dataNode="dn1,dn2" primaryKey="id" autoIncrement="true" rule="mod-long" />
</schema>
 
<dataNode name="dn1" dataHost="localhost1" database="testtimeseq1" />
<dataNode name="dn2" dataHost="localhost1" database="testtimeseq2" />

3>、配置sequence_time_conf.properties

##WORKID=0-31 任意整数
##DATAACENTERID=0-31 任意整数
##多个 mycat 节点下每个 mycat 配置的 WORKID,DATAACENTERID 不同,
##组成唯一标识,总共支持 32*32=1024 种组合。

#sequence depend on TIME
WORKID=05
DATAACENTERID=05

测试:

insert into test(id,xm) values(next value for MYCATSEQ_GLOBAL,'lisi') ; 
insert into test(id,xm) values(next value for MYCATSEQ_GLOBAL,'lisi') ;

4.3、数据库方式

原理

在数据库中建立一张表,存放sequence名称(name),sequence当前值(current_value),
步长(increment int类型每次读取多少个sequence,假设为K)等信息

Sequence获取步骤:

1)、第一次使用该sequence时,根据传入的sequence名称,从数据库这张表中读取current_value,和increment到MyCat中,并将数据库中的current_value设置为原current_value值+increment值(实现方式是基于后续的存储函数)

2)、MyCat将读取到current_value+increment作为本次要使用的sequence值,下次使用时,自动加1,当使用increment次后,执行步骤1)相同的操作.

MyCat负责维护这张表,用到哪些sequence,只需要在这张表中插入一条记录即可。若某次读取的sequence没有用完,系统就停掉了,则这次读取的sequence剩余值不会再使用。

使用步骤:

1>、配置server.xml文件

###全局序列类型: 0-本地文件,1-数据库方式,2-时间戳方式。此处应该修改成1。
<property name="sequnceHandlerType">1</property>

2>、配置数据库

在其中一个分片点对应的数据库中创建表和存储过程

①、我在schema.xml中配置信息如下:

<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
    <table name="users" dataNode="dn2"></table>
    <table name="orders" dataNode="dn1,dn2" rule="mod_rule"></table>
</schema>

<!--指定库名-->
<dataNode name="dn1" dataHost="host1" database="shopping" />
<dataNode name="dn2" dataHost="host2" database="shopping" />

譬如我在dn1中创建,对应的数据库名为shopping(为什么这里会涉及到datanode,因为后续的sequence_db_conf.properties文件会使用到),

注意,是登录到数据库中创建,而不是在mycat中创建

②、创建MYCAT_SEQUENCE表

##注意: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;

##name sequence名称
##current_value 当前value
##increment 增长步长! 可理解为mycat在数据库中一次读取多少个sequence. 当这些用完后, 下次再从数据库中读取.

③、插入sequence记录

##代表插入了一个名为mycat的sequence,当前值为1,步长为100。

INSERT INTO MYCAT_SEQUENCE(name,current_value,increment) VALUES ('ORDERS', 1, 100);

④、创建存储函数:

##必须在同一个数据库中创建,在本例中,是db2。一共要创建三个。

##获取当前sequence的值(返回当前值,增量)
DROP FUNCTION IF EXISTS mycat_seq_currval;
DELIMITER $
CREATE FUNCTION mycat_seq_currval(seq_name VARCHAR(50)) RETURNS varchar(64) CHARSET utf8
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 ;


##设置sequence值
DROP FUNCTION IF EXISTS mycat_seq_setval;
DELIMITER $
CREATE FUNCTION mycat_seq_setval(seq_name VARCHAR(50),value INTEGER) RETURNS     varchar(64) CHARSET utf8
DETERMINISTIC
BEGIN
UPDATE MYCAT_SEQUENCE
SET current_value = value
WHERE name = seq_name;
RETURN mycat_seq_currval(seq_name);
END $
DELIMITER ;


##获取下一个sequence值
DROP FUNCTION IF EXISTS mycat_seq_nextval;
DELIMITER $
CREATE FUNCTION mycat_seq_nextval(seq_name VARCHAR(50)) RETURNS varchar(64) CHARSET utf8
DETERMINISTIC
BEGIN
UPDATE MYCAT_SEQUENCE
SET current_value = current_value + increment WHERE name = seq_name;
RETURN mycat_seq_currval(seq_name);
END $
DELIMITER ;

3>、在mycat conf目录下的sequence_db_conf.properties文件中添加如下内容:

##ORDERS这个序列在dn1上,具体dn1节点在哪台机器上,参考schema.xml
##注意:ORDERS必须为大写,这个与表的数据是否大写无关,
##事实上,MYCAT_SEQUENCE中name是否大小写对结果没有影响。

GLOBAL=dn1
COMPANY=dn1
CUSTOMER=dn1
ORDERS=dn1

测试:

INSERT INTO orders(id,order_type,customer_id,amount) VALUES 
(next value for MYCATSEQ_ORDERS,101,100,100100);

4.4、自主生成全局序列–推荐

可在java项目里自己生成全局序列

①根据业务逻辑组合

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

③可以使用zk

但,自主生成需要单独在工程中用java代码实现,还是推荐使用Mycat自带全局序列。

//TODO 这个以后再单独拿出来写

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章