Mysql主從復(fù)制
1、主從復(fù)制的含義
在 MySQL 多服務(wù)器的架構(gòu)中,至少要有一個(gè)主節(jié)點(diǎn)(master),跟主節(jié)點(diǎn)相對(duì)的,我們把它叫做從節(jié)點(diǎn)(slave)
主從復(fù)制,就是把主節(jié)點(diǎn)的數(shù)據(jù)復(fù)制到一個(gè)或者多個(gè)從節(jié)點(diǎn),主服務(wù)器和從服務(wù)器可以在不同的 IP 上,通過(guò)遠(yuǎn)程連接來(lái)同步數(shù)據(jù),這個(gè)是異步的過(guò)程
2、主從復(fù)制的形式
一主一從/一主多從:

多主一從:

雙主復(fù)制:

級(jí)聯(lián)復(fù)制:

3、主從復(fù)制的用途
數(shù)據(jù)備份:把數(shù)據(jù)復(fù)制到不同的機(jī)器上,以免單臺(tái)服務(wù)器發(fā)生故障時(shí)數(shù)據(jù)丟失
讀寫(xiě)分離:讓主庫(kù)負(fù)責(zé)寫(xiě),從庫(kù)負(fù)責(zé)讀,從而提高讀寫(xiě)的并發(fā)度
高可用 HA:當(dāng)節(jié)點(diǎn)故障時(shí),自動(dòng)轉(zhuǎn)移到其他節(jié)點(diǎn),提高可用性
結(jié)合負(fù)載的機(jī)制,均攤所有的應(yīng)用訪問(wèn)請(qǐng)求,降低單機(jī) IO
4、binlog
客戶端對(duì) MySQL 數(shù)據(jù)庫(kù)進(jìn)行操作的時(shí)候,包括 DDL 和 DML 語(yǔ)句,服務(wù)端會(huì)在日志文件中用事件的形式記錄所有的操作記錄,這個(gè)文件就是 binlog 文件(屬于邏輯日志,跟 Redis 的 AOF 文件類似)
基于 binlog,我們可以實(shí)現(xiàn)主從復(fù)制和數(shù)據(jù)恢復(fù)
Binlog 默認(rèn)是不開(kāi)啟的,需要在服務(wù)端手動(dòng)配置,有一定的性能損耗*
1)binlog配置
編輯 /etc/my.cnf
log-bin=/var/lib/mysql/mysql-bin
server-id=1
重啟MYSQL服務(wù):
service mysqld stop
service mysqld start
## 如果出錯(cuò)查看日志
vi /var/log/mysqld.log
cd /var/lib/mysql
是否開(kāi)啟binlog:
show variables like 'log_bin%';

2)binlog格式
STATEMENT:記錄每一條修改數(shù)據(jù)的 SQL 語(yǔ)句(減少日志量,節(jié)約 IO)
ROW:記錄哪條數(shù)據(jù)被修改了,修改成什么樣子了(5.7 以后默認(rèn))
MIXED:結(jié)合兩種方式,一般的語(yǔ)句用 STATEMENT,函數(shù)之類的用 ROW
查看binlog格式:
show global variables like '%binlog_format%';

查看binlog列表:
show binary logs;
查看binlog內(nèi)容:
show binlog events in 'mysql-bin.000001(binlog名稱)';
用 mysqlbinlog 工具,基于時(shí)間查看 binlog:
/usr/bin/mysqlbinlog --start-datetime='2019-08-22 13:30:00' --stop-datetime='2019-08-22 14:01:01' -d vincent(數(shù)據(jù)庫(kù)名) /var/lib/mysql/mysql-bin.000001(日志名)
5、主從復(fù)制的原理
1)主從復(fù)制配置
1、主庫(kù)開(kāi)啟 binlog,設(shè)置 server-id
2、在主庫(kù)創(chuàng)建具有復(fù)制權(quán)限的用戶,允許從庫(kù)連接
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'192.168.8.146' IDENTIFIED BY '123456';
FLUSH PRIVILEGES;(刷新使權(quán)限生效)
3、從庫(kù)/etc/my.cnf 配置,重啟數(shù)據(jù)庫(kù)
server-id=2
log-bin=mysql-bin
relay-log=mysql-relay-bin
read-only=1
log-slave-updates=1
log-slave-updates 決定了在從 binlog 讀取數(shù)據(jù)時(shí),是否記錄 binlog,實(shí)現(xiàn)雙主和級(jí)聯(lián)的關(guān)鍵
4、在從庫(kù)執(zhí)行
stop slave;
change master to
master_host='192.168.8.146',master_user='repl',master_password='123456',master_log_file='mysql-bin.000001',
master_log_pos=4;
start slave;
5、查看同步狀態(tài)
SHOW SLAVE STATUS \G
2)主從復(fù)制原理

a)、slave 服務(wù)器執(zhí)行 start slave,開(kāi)啟主從復(fù)制開(kāi)關(guān), slave 服務(wù)器的 IO 線程請(qǐng)求從 master 服務(wù)器讀取 binlog(如果該線程追趕上了主庫(kù),會(huì)進(jìn)入睡眠狀態(tài))
b)、master 服務(wù)器創(chuàng)建 Log Dump 線程,把 binlog 發(fā)送給 slave 服務(wù)器,slave 服務(wù)器把讀取到的 binlog 日志內(nèi)容寫(xiě)入中繼日志 relay log(會(huì)記錄位置信息,以便下次繼續(xù)讀取)
c)、slave 服務(wù)器的 SQL 線程會(huì)實(shí)時(shí)檢測(cè) relay log 中新增的日志內(nèi)容,把 relay log解析成 SQL 語(yǔ)句,并執(zhí)行
Mycat高可用
目前 Mycat 沒(méi)有實(shí)現(xiàn)對(duì)多 Mycat 集群的支持,可以暫時(shí)使用 HAProxy 來(lái)做負(fù)載
HAProxy 對(duì) Mycat 進(jìn)行負(fù)載,Keepalived 實(shí)現(xiàn) VIP

Mycat注解
1、注解的作用
當(dāng)關(guān)聯(lián)的數(shù)據(jù)不在同一個(gè)節(jié)點(diǎn)的時(shí)候,Mycat 是無(wú)法實(shí)現(xiàn)跨庫(kù) join 的
例子:
如果直接在 150 插入主表數(shù)據(jù),151 插入明細(xì)表數(shù)據(jù),此時(shí)關(guān)聯(lián)查詢無(wú)法查詢出來(lái)
-- 150 節(jié)點(diǎn)插入
INSERT INTO `order_info` (`order_id`, `uid`, `nums`, `state`, `create_time`, `update_time`) VALUES (9, 1000003, 3, 1,
'2019-9-25 11:35:49', '2019-9-25 11:35:49');
?
-- 151 節(jié)點(diǎn)插入
INSERT INTO `order_detail` (`order_id`, `id`, `goods_id`, `price`, `is_pay`, `is_ship`, `status`) VALUES (9, 20180001,
85114752, 19.99, 1, 1, 1);
在 mycat 數(shù)據(jù)庫(kù)查詢,直接查詢沒(méi)有結(jié)果:
select a.order_id,b.goods_id from order_info a, order_detail b where a.order_id = b.order_id;
Mycat 作為一個(gè)中間件,有很多自身不支持的 SQL 語(yǔ)句,比如存儲(chǔ)過(guò)程,但是這些語(yǔ)句在實(shí)際的數(shù)據(jù)庫(kù)節(jié)點(diǎn)上是可以執(zhí)行的
Mycat的注解:在需要執(zhí)行的 SQL 語(yǔ)句前面加上一段代碼,幫助Mycat 找到我們的目標(biāo)節(jié)點(diǎn)
2、注解的用法
注解的形式:/*!mycat: sql=注解 SQL 語(yǔ)句*/
注解的使用方式:
/*!mycat: sql=注解 SQL 語(yǔ)句*/ 真正執(zhí)行的 SQL
使用時(shí)將 = 號(hào)后的 "注解 SQL 語(yǔ)句" 替換為需要的 SQL 語(yǔ)句即可
使用注解要注意的地方:
select:如果需要確定分片,則使用能確定分片的注解,比如 /*!mycat: sql=select * from users where user_id=1*/ 如果要在所有分片上執(zhí)行則可以不加能確定分片的條件
insert:使用 insert 的表作為注解 SQL,必須能確定到某個(gè)分片
原始 SQL 插入的字段必須包括分片字段
非分片表(只在某個(gè)節(jié)點(diǎn)上):必須能確定到某個(gè)分片
delete:使用 delete 的表作為注解 SQL
update:使用 update 的表作為注解 SQL
使用注解并不額外增加 MyCat 的執(zhí)行時(shí)間;從解析復(fù)雜度以及性能考慮,注解SQL 應(yīng)盡量簡(jiǎn)單,因?yàn)樗皇怯脕?lái)做路由的
3、注解使用示例
1)創(chuàng)建表或存儲(chǔ)過(guò)程
-- customer.id=1 全部路由到 146
-- 存儲(chǔ)過(guò)程
/*!mycat: sql=select * from customer where id =1 */ CREATE PROCEDURE test_proc() BEGIN END ;
?
-- 表
/*!mycat: sql=select * from customer where id =1 */ CREATE TABLE test2(id INT);
2)特殊語(yǔ)句自定義分片
Mycat 本身不支持 insert select,通過(guò)注解支持
/*!mycat: sql=select * from customer where id =1 */ INSERT INTO test2(id) SELECT id FROM order_detail;
3)多表ShareJoin
/*!mycat:catlet=io.mycat.catlets.ShareJoin */
select a.order_id,b.goods_id from order_info a, order_detail b where a.order_id = b.order_id;
4)讀寫(xiě)分離
讀寫(xiě)分離 : 配置 Mycat 讀寫(xiě)分離后,默認(rèn)查詢都會(huì)從讀節(jié)點(diǎn)獲取數(shù)據(jù),但是有些場(chǎng)景需要獲取實(shí)時(shí)數(shù)據(jù),如果從讀節(jié)點(diǎn)獲取數(shù)據(jù)可能因延時(shí)而無(wú)法實(shí)現(xiàn)實(shí)時(shí),Mycat 支持通過(guò)注解 /*balance*/ 來(lái)強(qiáng)制從寫(xiě)節(jié)點(diǎn)(write host)查詢數(shù)據(jù)
/*balance*/ select a.* from customer a where a.id=6666;
5)讀寫(xiě)分離數(shù)據(jù)庫(kù)選擇(1.6版本之后)
/*!mycat: db_type=master */ select * from customer;
/*!mycat: db_type=slave */ select * from customer;
/*#mycat: db_type=master */ select * from customer;
/*#mycat: db_type=slave */ select * from customer;
注解支持的'!'不被 mysql 單庫(kù)兼容
注解支持的'#'不被 MyBatis 兼容
4、注解原理
Mycat 在執(zhí)行 SQL 之前會(huì)先解析 SQL 語(yǔ)句,在獲得分片信息后再到對(duì)應(yīng)的物理節(jié)點(diǎn)上執(zhí)行,如果 SQL 語(yǔ)句無(wú)法解析,則不能被執(zhí)行,如果語(yǔ)句中有注解,則會(huì)先解析注解的內(nèi)容獲得分片信息,再把真正需要執(zhí)行的 SQL 語(yǔ)句發(fā)送對(duì)對(duì)應(yīng)的物理節(jié)點(diǎn)上
所以我們?cè)谑褂弥鳈C(jī)的時(shí)候,應(yīng)該清楚地知道目標(biāo) SQL 應(yīng)該在哪個(gè)節(jié)點(diǎn)上執(zhí)行,注解的 SQL 也指向這個(gè)分片,這樣才能使用,如果注解沒(méi)有使用正確的條件,會(huì)導(dǎo)致原始SQL 被發(fā)送到所有的節(jié)點(diǎn)上執(zhí)行,造成數(shù)據(jù)錯(cuò)誤
分片策略詳解
分片的目標(biāo)是:將大量數(shù)據(jù)和訪問(wèn)請(qǐng)求均勻分布在多個(gè)節(jié)點(diǎn)上,通過(guò)這種方式提升數(shù)據(jù)服務(wù)的存儲(chǔ)和負(fù)載能力
1、Mycat分片策略詳解
總體上分為:連續(xù)分片和離散分片,還有一種是連續(xù)分片和離散分片的結(jié)合,例如先范圍后取模

范圍分片(id 或者時(shí)間)就是典型的連續(xù)分片,單個(gè)分區(qū)的數(shù)量和邊界是確定的
離散分片的分區(qū)總數(shù)量和邊界是確定的,例如對(duì) key 進(jìn)行哈希運(yùn)算,或者再取模
1)連續(xù)分片
優(yōu)點(diǎn):
a)范圍條件查詢消耗資源少(不需要匯總數(shù)據(jù))
b)擴(kuò)容無(wú)需遷移數(shù)據(jù)(分片固定)
缺點(diǎn):
a)存在數(shù)據(jù)熱點(diǎn)的可能性
b)并發(fā)訪問(wèn)能力受限于單一或少量 DataNode(訪問(wèn)集中)
范圍分片:
<tableRule name="auto-sharding-long">
<rule>
<columns>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>
</function>
# range start-end ,data node index
# K=1000,M=10000.
0-500M=0
500M-1000M=1
1000M-1500M=2
特點(diǎn):容易出現(xiàn)冷熱數(shù)據(jù)
按自然月分片:
#建表語(yǔ)句
CREATE TABLE `sharding_by_month` (
`create_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`db_nm` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
#邏輯表
<schema name="catmall" checkSQLschema="false" sqlMaxLimit="100">
<table name="sharding_by_month" dataNode="dn1,dn2,dn3" rule="vincent-sharding-by-month" />
</schema>
#分片規(guī)則
<tableRule name="sharding-by-month">
<rule>
<columns>create_time</columns>
<algorithm>vincent-partbymonth</algorithm>
</rule>
</tableRule>
#分片算法
<function name="vincent-partbymonth" class="io.mycat.route.function.PartitionByMonth">
<property name="dateFormat">yyyy-MM-dd</property>
<property name="sBeginDate">2019-10-01</property>
<property name="sEndDate">2019-12-31</property>
</function>
columns 標(biāo)識(shí)將要分片的表字段,字符串類型,與 dateFormat 格式一致
algorithm 為分片函數(shù)
dateFormat 為日期字符串格式
sBeginDate 為開(kāi)始日期
sEndDate 為結(jié)束日期
注意:節(jié)點(diǎn)個(gè)數(shù)要大于月份的個(gè)數(shù)
測(cè)試語(yǔ)句:
INSERT INTO sharding_by_month (create_time,db_nm) VALUES ('2019-10-16', database());
INSERT INTO sharding_by_month (create_time,db_nm) VALUES ('2019-10-27', database());
INSERT INTO sharding_by_month (create_time,db_nm) VALUES ('2019-11-04', database());
INSERT INTO sharding_by_month (create_time,db_nm) VALUES ('2019-11-11', database());
INSERT INTO sharding_by_month (create_time,db_nm) VALUES ('2019-12-25', database());
INSERT INTO sharding_by_month (create_time,db_nm) VALUES ('2019-12-31', database());
另外還有按天分片(可以指定多少天一個(gè)分片)、按小時(shí)分片,可以自定義分片規(guī)則
2)離散分片
優(yōu)點(diǎn):
a)并發(fā)訪問(wèn)能力增強(qiáng)(負(fù)載到不同的節(jié)點(diǎn))
b)范圍條件查詢性能提升(并行計(jì)算)
缺點(diǎn):
a)數(shù)據(jù)擴(kuò)容比較困難,涉及到數(shù)據(jù)遷移問(wèn)題
b)數(shù)據(jù)庫(kù)連接消耗比較多
枚舉分片
將所有可能出現(xiàn)的值列舉出來(lái),指定分片,例如:全國(guó) 34 個(gè)省,要將不同的省的數(shù)據(jù)存放在不同的節(jié)點(diǎn),可用枚舉的方式
#建表語(yǔ)句
CREATE TABLE `sharding_by_intfile` (
`age` int(11) NOT NULL,
`db_nm` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
#邏輯表
<table name="sharding_by_intfile" dataNode="dn$1-3" rule="vincent-sharding-by-intfile" />
#分片規(guī)則
<tableRule name="sharding-by-intfile">
<rule>
<columns>sharding_id</columns>
<algorithm>hash-int</algorithm>
</rule>
</tableRule>
#分片算法
<function name="hash-int" class="org.opencloudb.route.function.PartitionByFileMap">
<property name="mapFile">partition-hash-int.txt</property>
<property name="type">0</property>
<property name="defaultNode">0</property>
</function>
type:默認(rèn)值為 0,0 表示 Integer,非零表示 String
PartitionByFileMap.java,通過(guò) map 來(lái)實(shí)現(xiàn)
#策略文件 partition-hash-int.txt 內(nèi)容
16=0
17=1
18=2
#插入數(shù)據(jù)測(cè)試
INSERT INTO `sharding_by_intfile` (age,db_nm) VALUES (16, database());
INSERT INTO `sharding_by_intfile` (age,db_nm) VALUES (17, database());
INSERT INTO `sharding_by_intfile` (age,db_nm) VALUES (18, database());
特點(diǎn):適用于枚舉值固定的場(chǎng)景
一致性hash哈希
一致性 hash 有效解決了分布式數(shù)據(jù)的擴(kuò)容問(wèn)題
#建表語(yǔ)句
CREATE TABLE `sharding_by_murmur` (
`id` int(10) DEFAULT NULL,
`db_nm` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
#邏輯表
<schema name="test" checkSQLschema="false" sqlMaxLimit="100">
<table name="sharding_by_murmurhash" primaryKey="id" dataNode="dn$1-3" rule="sharding-by-murmur" />
</schema>
#分片規(guī)則
<tableRule name="sharding-by-murmur">
<rule>
<columns>id</columns>
<algorithm>vincent-murmur</algorithm>
</rule>
</tableRule>
#分片算法
<function name="qs-murmur" class="io.mycat.route.function.PartitionByMurmurHash">
<property name="seed">0</property>
<property name="count">3</property>
<property name="virtualBucketTimes">160</property>
</function>
#測(cè)試語(yǔ)句
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (1, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (2, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (3, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (4, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (5, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (6, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (7, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (8, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (9, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (10, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (11, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (12, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (13, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (14, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (15, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (16, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (17, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (18, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (19, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (20, database());
特點(diǎn):可以一定程度減少數(shù)據(jù)的遷移
十進(jìn)制取模分片
根據(jù)分片鍵進(jìn)行十進(jìn)制求模運(yùn)算
<tableRule name="mod-long">
<rule>
<columns>sid</columns>
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<!-- how many data nodes -->
<property name="count">3</property>
</function>
特點(diǎn):分布均勻,但是遷移工作量比較大
固定分片哈希
這是先求模得到邏輯分片號(hào),再根據(jù)邏輯分片號(hào)直接映射到物理分片的一種散列算法
#建表語(yǔ)句
CREATE TABLE `sharding_by_long` (
`id` int(10) DEFAULT NULL,
`db_nm` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
#邏輯表
<schema name="test" checkSQLschema="false" sqlMaxLimit="100">
<table name="sharding_by_long" dataNode="dn$1-3" rule="vincent-sharding-by-long" />
</schema>
#分片規(guī)則
<tableRule name="qs-sharding-by-long">
<rule>
<columns>id</columns>
<algorithm>vincent-sharding-by-long</algorithm>
</rule>
</tableRule>
例子1:平均分成 8 片(%1024 的余數(shù),1024=128*8)
<function name="vincent-sharding-by-long" class="io.mycat.route.function.PartitionByLong">
<property name="partitionCount">8</property>
<property name="partitionLength">128</property>
</function>
partitionCount :為指定分片個(gè)數(shù)列表
partitionLength :為分片范圍列表

例子2:兩個(gè)數(shù)組,分成不均勻的 3 個(gè)節(jié)點(diǎn)(%1024 的余數(shù),1024=2256+1512)
<function name="vincent-sharding-by-long" class="io.mycat.route.function.PartitionByLong">
<property name="partitionCount">2,1</property>
<property name="partitionLength">256,512</property>
</function>
3 個(gè)節(jié)點(diǎn),對(duì) 1024 取模余數(shù)的分布

#測(cè)試語(yǔ)句
INSERT INTO `sharding_by_long` (id,db_nm) VALUES (222, database());
INSERT INTO `sharding_by_long` (id,db_nm) VALUES (333, database());
INSERT INTO `sharding_by_long` (id,db_nm) VALUES (666, database());
特點(diǎn):在一定范圍內(nèi) id 是連續(xù)分布的
取模范圍分片
#建表語(yǔ)句
CREATE TABLE `sharding_by_pattern` (
`id` varchar(20) DEFAULT NULL,
`db_nm` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
#邏輯表
<schema name="test" checkSQLschema="false" sqlMaxLimit="100">
<table name="sharding_by_pattern" primaryKey="id" dataNode="dn$0-10" rule="vincent-sharding-by-pattern" />
</schema>
#分片規(guī)則
<tableRule name="sharding-by-pattern">
<rule>
<columns>user_id</columns>
<algorithm>sharding-by-pattern</algorithm>
</rule>
</tableRule>
#分片算法
<function name="sharding-by-pattern" class=" io.mycat.route.function.PartitionByPattern">
<property name="patternValue">100</property>
<property name="defaultNode">0</property>
<property name="mapFile">partition-pattern.txt</property>
</function>
patternValue 取?;鶖?shù),這里設(shè)置成 100
partition-pattern.txt,一共 3 個(gè)節(jié)點(diǎn):
id=19%100=19,在 dn1;
id=222%100=22,在dn2;
id=371%100=71,在dn3
# id partition range start-end ,data node index
###### first host configuration
1-20=0
21-70=1
71-100=2
0-0=0
#測(cè)試語(yǔ)句
INSERT INTO `sharding_by_pattern` (id,db_nm) VALUES (19, database());
INSERT INTO `sharding_by_pattern` (id,db_nm) VALUES (222, database());
INSERT INTO `sharding_by_pattern` (id,db_nm) VALUES (371, database());
特點(diǎn):可以調(diào)整節(jié)點(diǎn)的數(shù)據(jù)分布
范圍取模分片
#建表語(yǔ)句
CREATE TABLE `sharding_by_rang_mod` (
`id` bigint(20) DEFAULT NULL,
`db_nm` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
#邏輯表
<schema name="test" checkSQLschema="false" sqlMaxLimit="100">
<table name="sharding_by_rang_mod" dataNode="dn$1-3" rule="vincent-sharding-by-rang-mod" />
</schema>
#分片規(guī)則
<tableRule name="vincent-sharding-by-rang-mod">
<rule>
<columns>id</columns>
<algorithm>vincent-rang-mod</algorithm>
</rule>
</tableRule>
#分片算法
<function name="vincent-rang-mod" class="io.mycat.route.function.PartitionByRangeMod">
<property name="mapFile">partition-range-mod.txt</property>
</function>
#partition-range-mod.txt
# range start-end ,data node group size
0-20000=1
20001-40000=2
解讀:先范圍后取模,Id 在 20000 以內(nèi)的,全部分布到 dn1,Id 在 20001-40000的,%2 分布到 dn2,dn3
#插入數(shù)據(jù)
INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (666, database());
INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (6667, database());
INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (16666, database());
INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (21111, database());
INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (22222, database());
INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (23333, database());
INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (24444, database());
特點(diǎn):擴(kuò)容的時(shí)候舊數(shù)據(jù)無(wú)需遷移
其他分片規(guī)則
應(yīng)用指定分片: PartitionDirectBySubString
日期范圍哈希: PartitionByRangeDateHash
冷熱數(shù)據(jù)分片:PartitionByHotDate
自定義分片規(guī)則 : extends AbstractPartitionAlgorithm implements RuleAlgorithm
3)切分規(guī)則的選擇
選擇:
a、找到需要切分的大表和關(guān)聯(lián)的表
b、確定分片字段(盡量使用主鍵),一般用最頻繁使用的查詢條件
c、考慮單個(gè)分片的存儲(chǔ)容量和請(qǐng)求、數(shù)據(jù)增長(zhǎng)(業(yè)務(wù)特性)、擴(kuò)容和數(shù)據(jù)遷移問(wèn)題
問(wèn)題:按照什么遞增?序號(hào)還是日期?主鍵是否有業(yè)務(wù)意義?
一般來(lái)說(shuō),分片數(shù)要比當(dāng)前規(guī)劃的節(jié)點(diǎn)數(shù)要大
總結(jié):根據(jù)業(yè)務(wù)場(chǎng)景,合理地選擇分片規(guī)則
案例分析:
總問(wèn)題:4 億的數(shù)據(jù)怎么分表?我是不是分成 3 臺(tái)服務(wù)器?
問(wèn)題1:一年內(nèi)到達(dá)多少??jī)赡陜?nèi)到達(dá)多少?(數(shù)據(jù)的增長(zhǎng)速度)?
一臺(tái)設(shè)備每秒鐘往 3 張表各寫(xiě)入一條數(shù)據(jù),一共 4 臺(tái)設(shè)備,每張表一天86400*4=345600 條。每張表一個(gè)月 10368000 條
分析:增長(zhǎng)速度均勻,可以用日期切分,每個(gè)月分一張表
問(wèn)題2:什么業(yè)務(wù)?所有的數(shù)據(jù)都會(huì)訪問(wèn),還是訪問(wèn)新數(shù)據(jù)為主?
訪問(wèn)新數(shù)據(jù)為主,但是所有的數(shù)據(jù)都可能會(huì)訪問(wèn)到
問(wèn)題3:表結(jié)構(gòu)和表數(shù)據(jù)是什么樣的?一個(gè)月消耗多少空間?
字段不多,算過(guò)了,三年數(shù)據(jù)量有 4 億,35G
分析:35G 沒(méi)必要分庫(kù),浪費(fèi)機(jī)器
問(wèn)題4:訪問(wèn)量怎么樣?并發(fā)壓力大么?
并發(fā)有一點(diǎn)
分析:如果并發(fā)量不大,不用分庫(kù),只需要在單庫(kù)分表,不用引入 Mycat 中間件了,如果要自動(dòng)路由的話可以用 Sharding-JDBC,否則就是自己拼裝表名
問(wèn)題5:3 張表有沒(méi)有關(guān)聯(lián)查詢之類的操作?
沒(méi)有
分析:還是拼裝表名簡(jiǎn)單一點(diǎn)
Mycat離線擴(kuò)縮容
當(dāng)我們規(guī)劃了數(shù)據(jù)分片,而數(shù)據(jù)已經(jīng)超過(guò)了單個(gè)節(jié)點(diǎn)的存儲(chǔ)上限,或者需要下線節(jié)點(diǎn)的時(shí)候,就需要對(duì)數(shù)據(jù)重新分片
1、Mycat自帶的工具
1)準(zhǔn)備工作
a、mycat 所在環(huán)境安裝 mysql 客戶端程序
b、mycat 的 lib 目錄下添加 mysql 的 jdbc 驅(qū)動(dòng)包
c、對(duì)擴(kuò)容縮容的表所有節(jié)點(diǎn)數(shù)據(jù)進(jìn)行備份,以便遷移失敗后的數(shù)據(jù)恢復(fù)
2)步驟
以取模分片表 sharding-by-mod 縮容(減少一個(gè)節(jié)點(diǎn))為例:
遷移前的數(shù)據(jù) dn0 3,6 dn1 1,4 dn3 2,5
遷移后數(shù)據(jù) dn0 2,4,6 dn1 1,3,5
a、復(fù)制 schema.xml、rule.xml 并重命名為 newSchema.xml、newRule.xml 放于 conf 目錄下
b、修改 newSchema.xml 和 newRule.xml 配置文件為擴(kuò)容縮容后的 mycat配置參數(shù)(表的節(jié)點(diǎn)數(shù)、數(shù)據(jù)源、路由規(guī)則)
只有節(jié)點(diǎn)變化的表才會(huì)進(jìn)行遷移,僅分片配置變化不會(huì)遷移
#newSchema.xml
<table name="sharding_by_mod" dataNode="dn1,dn2,dn3" rule="vincent-sharding-by-mod" />
#改成(減少了一個(gè)節(jié)點(diǎn))
<table name="sharding_by_mod" dataNode="dn1,dn2" rule="vincent-sharding-by-mod" />
#newRule.xml 修改 count 個(gè)數(shù)
<function name="qs-sharding-by-mod-long" class="io.mycat.route.function.PartitionByMod">
<property name="count">2</property>
</function>
c、修改 conf 目錄下的 migrateTables.properties 配置文件,告訴工具哪些表需要進(jìn)行擴(kuò)容或縮容,沒(méi)有出現(xiàn)在此配置文件的 schema 表不會(huì)進(jìn)行數(shù)據(jù)遷移
格式:
不遷移的表,不要修改 dn 個(gè)數(shù),否則會(huì)報(bào)錯(cuò)
ER 表,因?yàn)橹挥兄鞅碛蟹制?guī)則,子表不會(huì)遷移
catmall=sharding-by-mod
d、dataMigrate.sh 中這個(gè)必須要配置
通 過(guò) 命 令 "find / -name mysqldump" 查 找 mysqldump 路 徑 為
"/usr/bin/mysqldump",指定#mysql bin 路徑為"/usr/bin/"
#mysql bin 路徑
RUN_CMD="$RUN_CMD -mysqlBin= /usr/bin/"
e、停止 mycat 服務(wù)
f、執(zhí)行執(zhí)行 bin/ dataMigrate.sh 腳本
必須要配置 Java 環(huán)境變量,不能用 openjdk
g、腳本執(zhí)行完成, 如果最后的數(shù)據(jù)遷移驗(yàn)證通過(guò),就可以 將之前 的newSchema.xml 和 newRule.xml 替換之前的 schema.xml 和 rule.xml 文件,并重啟 mycat 即可
注意點(diǎn):
1)保證分片表遷移數(shù)據(jù)前后路由規(guī)則一致(取模——取模)
2)保證分片表遷移數(shù)據(jù)前后分片字段一致
3)全局表將被忽略
4)不要將非分片表配置到 migrateTables.properties 文件中
5)暫時(shí)只支持分片表使用 MySQL 作為數(shù)據(jù)源的擴(kuò)容縮容 migrate 限制比較多,還可以使用 mysqldump
2、mysqldump方式
系統(tǒng)第一次上線,把單張表遷移到 Mycat,也可以用 mysqldump
mysql導(dǎo)出:
mysqldump -uroot -p123456 -h127.0.0.1 -P3306 -c -t --skip-extended-insert vincentmall > mysql-1001.sql
-c 代表帶列名
-t代表只要數(shù)據(jù),不要建表語(yǔ)句
--skip-extended-insert 代表生成多行 insert(mycat childtable 不支持多行插入ChildTable multi insert not provided)
Mycat導(dǎo)入:
mysql -uroot -p123456 -h127.0.0.1 -P8066 vincentmall < mysql-1001.sql
Mycat導(dǎo)出:
mysqldump -h192.168.8.151 -uroot -p123456 -P8066 -c -t --skip-extended-insert vincentmall customer > mycat-cust.sql
其他導(dǎo)入方式:
load data local infile '/mycat/customer.txt' into table customer;
source sql '/mycat/customer.sql';
核心流程總結(jié)

1、啟動(dòng)
1)MycatServer 啟動(dòng),解析配置文件,包括服務(wù)器、分片規(guī)則等
2)創(chuàng)建工作線程,建立前端連接和后端連接
2、執(zhí)行SQL
1)前端連接接收 MySQL 命令
2)解析 MySQL,Mycat 用的是 Druid 的 DruidParser
3)獲取路由
4)改寫(xiě) MySQL,例如兩個(gè)條件在兩個(gè)節(jié)點(diǎn)上,則變成兩條單獨(dú)的 SQL
例如 select * from customer where id in(5000001, 10000001);
改寫(xiě)成:
select * from customer where id = 5000001;(dn2 執(zhí)行)
select * from customer where id = 10000001;(dn3 執(zhí)行)
又比如多表關(guān)聯(lián)查詢,先到各個(gè)分片上去獲取結(jié)果,然后在內(nèi)存中計(jì)算
5)與后端數(shù)據(jù)庫(kù)建立連接
6)發(fā)送 SQL 語(yǔ)句到 MySQL 執(zhí)行
7)獲取返回結(jié)果
8)處理返回結(jié)果,例如排序、計(jì)算等等
9)返回給客戶端
3、源碼下載與調(diào)試環(huán)境搭建
1)下載源代碼,導(dǎo)入工程
git clone https://github.com/MyCATApache/Mycat-Server
2)配置
schema.xml文件
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100">
<table name="travelrecord" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
<table name="company" primaryKey="ID" type="global" dataNode="dn1,dn2,dn3" />
<table name="hotnews" primaryKey="ID" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="mod-long" />
</schema>
?
<dataNode name="dn1" dataHost="localhost1" database="db1" />
<dataNode name="dn2" dataHost="localhost1" database="db2" />
<dataNode name="dn3" dataHost="localhost1" database="db3" />
<dataHost name="localhost1" maxCon="20" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="127.0.0.1:3306" user="root" password="123456">
</writeHost>
</dataHost>
</mycat:schema>
3)表結(jié)構(gòu)
本地?cái)?shù)據(jù)庫(kù)創(chuàng)建 db1、db2、db3 數(shù)據(jù)庫(kù),全部執(zhí)行建表腳本
CREATE TABLE `company` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(64) DEFAULT '',
`market_value` bigint(20) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
?
CREATE TABLE `hotnews` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`title` varchar(64) DEFAULT '',
`content` varchar(512) DEFAULT '0',
`time` varchar(8) DEFAULT '',
`cat_name` varchar(10) DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
?
CREATE TABLE `travelrecord` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`city` varchar(32) DEFAULT '',
`time` varchar(8) DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4)邏輯表配置
travelrecord 表配置
<table name="travelrecord" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
<tableRule name="auto-sharding-long">
<rule>
<columns>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>
</function>
hotnews 表配置
<table name="hotnews" primaryKey="ID" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="mod-long" />
<tableRule name="mod-long">
<rule>
<columns>id</columns>
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<!-- how many data nodes -->
<property name="count">3</property>
</function>
company 表配置
<table name="company" primaryKey="ID" type="global" dataNode="dn1,dn2,dn3" />
5)debug方式啟動(dòng)
debug 方式啟動(dòng) main 方法
Mycat-Server-1.6.5-RELEASE\src\main\java\io\mycat\MycatStartup.java
6)連接本機(jī)的Mycat服務(wù)
#測(cè)試語(yǔ)句
insert into travelrecord(`id`, `city`, `time`) values(1, '廣州', '20191024');
?
insert into hotnews(`title`, `content`) values('xxx', '無(wú)敵');
?
insert into company(`name`, `market_value`) values('vincent', 666);
7)調(diào)試入口
連接入口:
io.mycat.net.NIOAcceptor#accept
SQL 入口:
io.mycat.server.ServerQueryHandler#query