點贊多大膽,就有多大產!開源促使進步,獻給每一位技術使用者和愛好者!
乾貨滿滿,擺好姿勢,點贊發車
路漫漫其修遠兮,吾將上下而求索
前言
數據庫拆分的理論知識有一篇不錯的文章,沒有必要再複製一遍,不過還是建議大家先看看這篇文章,再動手實現,我們這篇文章主要是基於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唯一問題,我們使用數據庫方式
本文若有任何看不懂,或者有錯誤的地方歡迎大家評論區留言,我時時關注哦