手把手基於Mycat實現MySQL數據拆分

點贊多大膽,就有多大產!開源促使進步,獻給每一位技術使用者和愛好者!
乾貨滿滿,擺好姿勢,點贊發車

路漫漫其修遠兮,吾將上下而求索

前言

  數據庫拆分的理論知識有一篇不錯的文章,沒有必要再複製一遍,不過還是建議大家先看看這篇文章,再動手實現,我們這篇文章主要是基於Mycat去實現一下數據庫拆分
https://www.cnblogs.com/butterfly100/p/9034281.html

垂直拆分-分庫

配置mycat的schema配置文件

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
	<!--dataNode配置數據庫地址,對應下方dataNode標籤name屬性值-->
	<schema name="MYCATDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
		<!--新增table標籤,配置customer表在dn2上-->
		<table name="customer" dataNode="dn2"></table>
	</schema>
	<!--兩個dataNode,database都是orders-->
	<dataNode name="dn1" dataHost="host1" database="orders" />
	<dataNode name="dn2" dataHost="host2" database="orders" />
	<!--庫1,修改balance=0,url-->
	<dataHost name="host1" maxCon="1000" minCon="10" balance="0"
			  writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
		<heartbeat>select user()</heartbeat>
		<writeHost host="hostM1" url="192.168.11.201:3306" user="root"
				   password="123456">
		</writeHost>
	</dataHost>
	<!--庫1,修改balance=0,url-->
	<dataHost name="host2" maxCon="1000" minCon="10" balance="0"
			  writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
		<heartbeat>select user()</heartbeat>
		<writeHost host="hostM2" url="192.168.11.202:3306" user="root"
				   password="123456">
		</writeHost>
	</dataHost>
</mycat:schema>

在兩個mysql實例中分別創建orders數據庫

CREATE DATABASE orders;

登陸Mycat創建四張表

-- 用戶表,假如有20W用戶
CREATE TABLE customer(
	id INT AUTO_INCREMENT,
	NAME VARCHAR(20),
	PRIMARY KEY (id)
);
-- 訂單表,假如有2000W個訂單
CREATE TABLE orders(
	id INT AUTO_INCREMENT,
	order_type INT,
	customer_id INT,
	amount DECIMAL(10,2),
	PRIMARY KEY (id)
);
-- 訂單詳情表,數據量和訂單表一樣
CREATE TABLE order_detail(
	id INT AUTO_INCREMENT,
	detail VARCHAR(20),
	order_id INT,
	PRIMARY KEY (id)
);
-- 字典表,數據量假如有20條,對應訂單的類型字典,類型說明數字對應字符串,訂單表中只需要存儲數字即可
CREATE TABLE dict_order_type(
	id INT AUTO_INCREMENT,
	order_type VARCHAR(20),
	PRIMARY KEY (id)
);

查看錶

如下圖,在Mycat上創建完之後Mycat窗口可以查詢出四張表,stt202上有一張customer表,stt203上有三張表,和我們理想效果一樣
在這裏插入圖片描述

水平拆分-分庫分表

  我們發現order和order_detail兩張表中數據量非常多,如果存儲在同一個節點上的同一個庫中性能會受到影響,我們考慮將order表和order_detail表進行拆分,分佈式存儲全量數據,平均存儲在兩臺節點上。

切片規則

  • 我們切分表中數據需要按照一定的規則切分,比如按照時間,id,用戶id等
  • 如果按照時間切分,老的數據存儲在一起,新的數據存儲在一起,用戶一般查詢的是新的數據,所以會導致新數據所在節點的負載要高於舊數據節點
  • 如果按照id分區與日期效果類似,一樣會導致節點負載不均勻
  • 在本例中我們可以按照customer_id分配,具體的項目需求大家在具體考慮,儘可能讓數據平均分配,節點負載均衡

配置mycat的schema.xml配置文件

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
	<!--dataNode配置數據庫地址,對應下方dataNode標籤name屬性值-->
	<schema name="MYCATDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
		<!--新增table標籤,配置customer表在dn2上-->
		<table name="customer" dataNode="dn2"></table>
		<!--配置orders表分佈在dn1和dn2上,分片規則爲mod_rule(自定義的)與rule.xml文件一致-->
		<table name="orders" dataNode="dn1,dn2" rule="mod_rule"></table>
	</schema>
	
	<dataNode name="dn1" dataHost="host1" database="orders" />
	<dataNode name="dn2" dataHost="host2" database="orders" />
	
	<dataHost name="host1" maxCon="1000" minCon="10" balance="0"
			  writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
		<heartbeat>select user()</heartbeat>
		<writeHost host="hostM1" url="192.168.11.201:3306" user="root"
				   password="123456">
		</writeHost>
	</dataHost>
	
	<dataHost name="host2" maxCon="1000" minCon="10" balance="0"
			  writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
		<heartbeat>select user()</heartbeat>
		<writeHost host="hostM2" url="192.168.11.202:3306" user="root"
				   password="123456">
		</writeHost>
	</dataHost>
</mycat:schema>

配置rule.xml配置文件

<!--新增分片規則-->
<tableRule name="mod_rule">
    <rule>
    	<!--根據表中的customer_id列分配-->
        <columns>customer_id</columns>
        <!--分片算法,mod-long取模算法是Mycat已經定義好的-->
        <algorithm>mod-long</algorithm>
    </rule>
</tableRule>
<!--Mycat提供的分片算法-->
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
    <!-- how many data nodes ,配置有幾個節點我們有兩個改爲2-->
    <property name="count">2</property>
</function>

在dn2上創建orders表,重啓mycat,登陸mycat新增數據到orders表中

-- 我們以前添加,sql語法表名後的字段名可以省略,但是mycat分庫分表添加數據不可省略,因爲需要指明哪一列數據是customer_id
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);

在這裏插入圖片描述

通過上圖可以看出我們在mycat中添加6條數據,在mycat端可以全量查出,但是順序並不是按照id排序的,如果想要飄絮可以使用order by語句,在stt201和stt202上分別查出3條數據,這樣就實現了數據的水平拆分

水平拆分的join關聯查詢

在這裏插入圖片描述

看上圖可以發現我們使用join內關聯查詢時會提示說order_detail表找不到,我們對orders表進行了切分也需要對orders的子表order_detail也進行切分配置

schema.xml文件

<!--配置orders表分佈在dn1和dn2上,分片規則爲mod_rule(自定義的)-->
<table name="orders" dataNode="dn1,dn2" rule="mod_rule">
	<!--添加childTable 標籤
	name:子表表名
	primaryKey:主鍵字段
	joinKey:兩表連接字段
	parentKey:父表主鍵
	-->
	<childTable name="order_detail" primaryKey="id" joinKey="order_id" parentKey="id"/>
</table>

在dn2上創建order_detail表,重啓mycat插入數據再做查詢

-- 插入數據和查詢都是在mycat端操作
-- 插入數據
INSERT INTO order_detail(id,detail,order_id)VALUES(1,'detail',1);
INSERT INTO order_detail(id,detail,order_id)VALUES(2,'detail',2);
INSERT INTO order_detail(id,detail,order_id)VALUES(3,'detail',3);
INSERT INTO order_detail(id,detail,order_id)VALUES(4,'detail',4);
INSERT INTO order_detail(id,detail,order_id)VALUES(5,'detail',5);
INSERT INTO order_detail(id,detail,order_id)VALUES(6,'detail',6);
-- 連接查詢
SELECT * FROM orders o inner join order_detail od on o.id = od.order_id;

在這裏插入圖片描述

到此我們的垂直拆分和水平拆分就告一段落,當然還沒有結束,真是XXXX了,咋還沒完心態炸裂,不慌大家老規矩喝杯茶繼續搞。

全局表

  我們的業務表比如orders、order_detail表數據量很多時就需要切分,但是還一些附屬表,比如我們這裏的dict_order_type(字典表),他們之間也要關聯,字典表數據並不多,數據變動不頻繁進行切片就沒有必要,這種表Mycat中定義爲全局表

特點

  • 全局表的插、更新操作會實時在所有節點上執行,保持各個分片的一致性
  • 全局表的查詢操作,只從一個節點獲取
  • 全局表可以跟任意一個表進行JOIN操作

修改schema.xml配置文件

<table name="orders" dataNode="dn1,dn2" rule="mod_rule">
	<childTable name="order_detail" primaryKey="id" joinKey="order_id" parentKey="id"/>
</table>
<!--新建table 標籤,type爲global-->
<table name="dict_order_type" dataNode="dn1,dn2" type="global"></table>

保存在dn2上創建字典表,重啓mycat

INSERT INTO dict_order_type(id,order_type) VALUES(101,'type1');
INSERT INTO dict_order_type(id,order_type) VALUES(102,'type2');

我們查詢數據在dn1和dn2都有完整的兩條數據,雖然存在數據冗餘,但是好在這些表中的數據並不多,不用切分實現JOIN查詢

常用分片規則

  我們在上邊的例子中切分數據時使用的是取模切分,這裏我們說一說其他開發中經常用到的數據切分方式

枚舉分片

  在配置文件中配置可能用到的枚舉ID,自己設置分片,比如按照省份或者區縣來做保存,而全國的省份區縣是固定的,可以使用在這些場景下

修改schema.xml配置文件

<!--訂單地址表-->
<table name="orders_ware_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.mycat.route.function.PartitionByFileMap">
	<!--標識配置文件名稱,-->
	<property name="mapFile">partition-hash-int.txt</property>
	<!--type:0位int型,非0位String型,我們的areacode字段是varchar類型-->
	<property name="type">1</property>
	<!--defaultNode
		默認節點:小於0表示不設置默認節點,大於等於0表示設置默認節點
					設置默認節點如果碰到不是別的枚舉值,就由它路由到默認節點,如果不設置就報錯
	-->
	<property name="defaultNode">0</property>
</function>

修改partition-hash-int.txt配置文件

110=0
120=1

重啓mycat,創建表插入數據

-- 創建表
CREATE TABLE orders_ware_info(
	id INT AUTO_INCREMENT,
	order_id INT,
	address VARCHAR(20),
	areacode VARCHAR,
	PRIMARY KEY(id)
);
-- 插入數據
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');

在這裏插入圖片描述

根據查詢結果在mycat上查詢是兩條數據,在stt201上是北京,在stt202上是天津

範圍約定分片

  比如我們的用戶id,將0-100000、100001-200000等這些按照範圍存儲,適用於範圍提前規定好的場景,我們這裏使用一張支付信息表爲例

配置schema.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>

修改autopartition-long.txt文件

注意:將原本有的配置刪除

0-102 = 0
103-200=1

重啓mycat,創建表,插入數據

CREATE TABLE payment_info(
	id INT AUTO_INCREMENT,
	order_id INT,
	payment_status INT,
	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);

在這裏插入圖片描述

我們可以看到在mycat上查詢全量數據,在stt201上展示兩條,在stt202上展示兩條,並且數據分佈也正確

按照日期分片

  我們按照天進行劃分,設定時間格式、範圍

修改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>
	<!--開始日期-->
	<property name="sBeginDate">2020-04-01</property>
	<!--結束日期,代表數據到達這個日期的分片後循環從開始分片插入
		5號6號又要分片到新的節點上,但是隻有2個節點,這時如果配置了結束時間會從開始時間重新分
		不會報錯
	-->
	<property name="sEndDate">2020-04-04</property>
	<!--分區天數,2天分到一個區-->
	<property name="sPartionDay">2</property>
</function>

重啓Mycat,創建表插入數據

CREATE TABLE login_info(
	id INT AUTO_INCREMENT,
	user_id INT,
	login_date date,
	PRIMARY KEY (id)
);
INSERT INTO login_info(id,user_id,login_date) VALUES (1,101,'2020-04-01');
INSERT INTO login_info(id,user_id,login_date) VALUES (2,102,'2020-04-02');
INSERT INTO login_info(id,user_id,login_date) VALUES (3,103,'2020-04-03');
INSERT INTO login_info(id,user_id,login_date) VALUES (4,104,'2020-04-04');
INSERT INTO login_info(id,user_id,login_date) VALUES (5,103,'2020-04-05');
INSERT INTO login_info(id,user_id,login_date) VALUES (6,104,'2020-04-06');

在這裏插入圖片描述

看到效果,stt201上四條數據因爲超過結束日期重新開始分區,stt202上兩條數據,大家可以按照自己的想法去操作,看看是否和自己預想的效果一樣,好好體會體會!到此我們完成了基於Mycat的數據庫切分操作以及常用的切分方式作爲參考

全局序列

  在分庫分表的情況下,數據庫自增主鍵已無法保證自增主鍵的唯一性,爲此Mycat提供了全局序列,提供了本地配置和數據庫配置多種實現方式

本地文件

  此方式Mycat將sequence配置到文件中,當使用到sequence中的配置後,Mycat會更新該值

  • 優勢:本地加載,讀取速度較快
  • 弊端:抗風險性差,mycat宕機無法讀取配置文件,重啓之後序列會重新開始,造成重複

數據庫方式(推薦使用)

  利用數據庫的一個表來進行累加,並不是每次生成序列都讀寫數據庫,這樣太慢,Mycat會預先加載一部分到Mycat內存中,這樣大部分讀寫都在內存中完成,如果內存中號段用完Mycat再向數據庫要一次

在dn1上創建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;

創建函數獲取當前sequence的值

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的值

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的值

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 ;

初始化序列表

-- 新增一條數據,序列名爲ORDERS,初始值爲400000,increment100,這個設置的是Mycat重啓之後的值遞增100,這個大家根據業務自己設置
INSERT INTO MYCAT_SEQUENCE(NAME,current_value,increment) VALUES('ORDERS',400000,100);

修改schmea.xml文件

<table name="mycat_sequence" primaryKey="name" dataNode="dn1"/>

修改Mycat的sequence_db_conf.properties文件

前邊爲序列名後邊爲所在節點,我們序列名爲ORDERS就是在dn1上創建的,如果你是在dn2上創建的序列表,則改爲dn2

#sequence stored in datanode
GLOBAL=dn1
COMPANY=dn1
CUSTOMER=dn1
ORDERS=dn1

修改server.xml文件

在這裏插入圖片描述

<property name="sequnceHandlerType">改爲1,配置使用序列的哪種方式,Mycat提供了三種方式,0爲本地文件,1爲數據庫方式,2爲時間戳方式

添加數據

語法就是將ID的值改爲next value for MYCATSEQ_SeqName咱麼這裏的序列名爲ORDERS。

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

查詢數據

SELECT * FROM orders;

在這裏插入圖片描述

時間戳方式

   全局序列ID=64位二進制(42(毫秒)+5(機器ID)+5(業務編碼)+12(重複累加))換算成十進制爲18位的long類型,每毫秒可以併發12位二進制累加

  • 優勢:配置簡單
  • 弊端:太長

自主生成

  可以在項目中自己編寫生成序列的代碼,或者使用redis的incr生成序列,這種方式也行但是需要在程序中進行編碼,我們還是推薦使用Mycat自帶的全局序列,也就是第二種方式

總結

  • 實現制定好切分方式或者說切分計劃
  • 準備好物理Mysql,這些Mysql應該都是白白的很乾淨的
  • 安裝好Mycat,配置Mycat的配置文件
  • 啓動Mycat創建表插入數據等操作,通過Mycat會將表和數據創建並且插入到真正的物理MySQL中維護
  • Mycat提供三種全局序列,解決分佈式數據庫主鍵ID唯一問題,我們使用數據庫方式

本文若有任何看不懂,或者有錯誤的地方歡迎大家評論區留言,我時時關注哦

我是添添,用你勤勞的雙手點個贊吧,這將是我創作更多優質文章的動力!

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