MyCat簡介與實踐

簡介

MyCat是一個開源的分佈式數據庫系統,是一個實現了MySQL協議的服務器,前端用戶可以把它看作是一個數據庫代理(類似於Mysql Proxy),用MySQL客戶端工具和命令行訪問,而其後端可以用MySQL原生協議與多個MySQL服務器通信,也可以用JDBC協議與大多數主流數據庫服務器通信,其核心功能是分表分庫,即將一個大表水平分割爲N個小表,存儲在後端MySQL服務器裏或者其他數據庫裏。

MyCat發展到目前的版本,已經不是一個單純的MySQL代理了,它的後端可以支持MySQL、SQL Server、Oracle、DB2、PostgreSQL等主流數據庫,也支持MongoDB這種新型NoSQL方式的存儲,未來還會支持更多類型的存儲。而在最終用戶看來,無論是那種存儲方式,在MyCat裏,都是一個傳統的數據庫表,支持標準的SQL語句進行數據的操作,這樣一來,對前端業務系統來說,可以大幅降低開發難度,提升開發速度。

MyCat可以簡單概括爲
-  一個徹底開源的,面向企業應用開發的大數據庫集羣
-  支持事務、ACID、可以替代MySQL的加強版數據庫
-  一個可以視爲MySQL集羣的企業級數據庫,用來替代昂貴的Oracle集羣
-  一個融合內存緩存技術、NoSQL技術、HDFS大數據的新型SQL Server
-  結合傳統數據庫和新型分佈式數據倉庫的新一代企業級數據庫產品
-  一個新穎的數據庫中間件產品

MyCat關鍵特性

-  支持SQL92標準
-  遵守Mysql原生協議,跨語言,跨平臺,跨數據庫的通用中間件代理
-  基於心跳的自動故障切換,支持讀寫分離,支持MySQL主從,以及galera cluster集羣
-  支持Galera for MySQL集羣,Percona Cluster或者MariaDB cluster
-  基於Nio實現,有效管理線程,高併發問題
-  支持數據的多片自動路由與聚合,支持sum,count,max等常用的聚合函數,支持跨庫分頁
-  支持單庫內部任意join,支持跨庫2表join,甚至基於caltlet的多表join
-  支持通過全局表,ER關係的分片策略,實現了高效的多表join查詢
-  支持多租戶方案
-  支持分佈式事務(弱xa)
-  支持全局序列號,解決分佈式下的主鍵生成問題
-  分片規則豐富,插件化開發,易於擴展
-  強大的web,命令行監控
-  支持前端作爲mysql通用代理,後端JDBC方式支持Oracle、DB2、SQL Server 、 mongodb 、巨杉
-  支持密碼加密
-  支持服務降級
-  支持IP白名單
-  支持SQL黑名單、sql注入攻擊攔截
-  支持分表(1.6)
-  集羣基於ZooKeeper管理,在線升級,擴容,智能優化,大數據處理(2.0開發版)

爲什麼要用MyCat

這裏要先搞清楚Mycat和MySQL的區別(Mycat的核心作用)。我們可以把上層看作是對下層的抽象,例如操作系統是對各類計算機硬件的抽象。那麼我們什麼時候需要抽象?假如只有一種硬件的時候,我們需要開發一個操作系統嗎?再比如一個項目只需要一個人完成的時候不需要leader,但是當需要幾十人完成時,就應該有一個管理者,發揮溝通協調等作用,而這個管理者對於他的上層來說就是對項目組的抽象。

同樣的,當我們的應用只需要一臺數據庫服務器的時候我們並不需要Mycat,而如果你需要分庫甚至分表,這時候應用要面對很多個數據庫的時候,這個時候就需要對數據庫層做一個抽象,來管理這些數據庫,而最上面的應用只需要面對一個數據庫層的抽象或者說數據庫中間件就好了,這就是Mycat的核心作用。所以可以這樣理解:數據庫是對底層存儲文件的抽象,而Mycat是對數據庫的抽象。

工作原理

MyCat的原理最重要的一個動詞是“”攔截“”,它攔截了用戶發送過來的SQL語句,首先對SQL語句做了一些特定的分析:如分片分析,路由分析,讀寫分離分析,緩存分析等,然後將此SQL發往後端的真實的數據庫,並將返回的結果做適當的處理,最終再返回給用戶。

應用場景

MyCat發展到現在,適用的場景已經很豐富,而且不斷有新用戶給出新的創新性的方案,以下是幾個典型的應用場景:
-   單純的讀寫分離,此時配置最爲簡單,支持讀寫分離,主從切換;
-   分表分庫,對於超過1000萬的表進行分片,最大支持1000億的單表分片;
-   多租戶應用,每個應用一個庫,但應用程序只連接Mycat,從而不改造程序本身,實現多租戶化;
-   報表系統,藉助於Mycat的分表能力,處理大規模報表的統計;
-   替代Hbase,分析大數據;
-   作爲海量數據實時查詢的一種簡單有效方案,比如100億條頻繁查詢的記錄需要在3秒內查詢出來結果,除了基於主鍵的查詢,還可能存在範圍查詢或其他屬性查詢,此時Mycat可能是最簡單有效的選擇;
-   Mycat長期路線圖;
-   強化分佈式數據庫中間件的方面的功能,使之具備豐富的插件、強大的數據庫智能優化功能、全面的系統監控能力、以及方便的數據運維工具,實現在線數據擴容、遷移等高級功能;
-   進一步挺進大數據計算領域,深度結合Spark Stream和Storm等分佈式實時流引擎,能夠完成快速的巨表關聯、排序、分組聚合等 OLAP方向的能力,並集成一些熱門常用的實時分析算法,讓工程師以及DBA們更容易用Mycat實現一些高級數據分析處理功能。
-   不斷強化Mycat開源社區的技術水平,吸引更多的IT技術專家,使得Mycat社區成爲中國的Apache,並將Mycat推到Apache
基金會,成爲國內頂尖開源項目,最終能夠讓一部分志願者成爲專職的Mycat開發者,榮耀跟實力一起提升。

不適合的應用場景

-  設計使用Mycat時有非分片字段查詢,請慎重使用Mycat,可以考慮放棄!
-  設計使用Mycat時有分頁排序,請慎重使用Mycat,可以考慮放棄!
-  設計使用Mycat時如果要進行表JOIN操作,要確保兩個表的關聯字段具有相同的數據分佈,否則請慎重使用Mycat,可以考慮放棄!
-  設計使用Mycat時如果有分佈式事務,得先看是否得保證事務得強一致性,否則請慎重使用Mycat,可以考慮放棄!

需要注意:  在生產環境中, Mycat節點最好使用雙節點, 即雙機熱備環境, 防止Mycat這一層出現單點故障. 可以使用的高可用集羣方式有:  Keepalived+Mycat+Mysql, Keepalived+LVS+Mycat+Mysql, Keepalived+Haproxy+Mycat+Mysql。本案例採用的一主一從模式的兩個mysql實例,並且針對單一的數據庫名進行測試;大多數mycat使用場景都是在多主多從模式並針對多個庫進行的

實現讀寫分離

搭建mysql主從複製環境

參考https://blog.csdn.net/qq_24313635/article/details/105611675

 安裝配置MyCat

1、下載Mycat-server-1.6.7.1-release-20190627191042-linux.tar.gz,解壓縮文件夾拷貝到/usr/local/目錄下

官網上對於這MyCat配置文件的描述是非常詳細的,MyCAT 配置主要涉及三個 XML配置文件:

  • server.xml:MyCat框架的系統參數/用戶參數配置文件
  • schema.xml: MyCat框架的邏輯庫表與分片的配置文件
  • rule.xml :MyCat框架的邏輯庫表分片規則的配置文件

2、修改server.xml

爲了和mysql用戶做區分,我們將mycat的用戶名改爲mycat

<?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:server SYSTEM "server.dtd">
<mycat:server xmlns:mycat="http://io.mycat/">
	<system>
	<property name="nonePasswordLogin">0</property> <!-- 0爲需要密碼登陸、1爲不需要密碼登陸 ,默認爲0,設置爲1則需要指定默認賬戶-->
	<property name="useHandshakeV10">1</property>
	<property name="useSqlStat">0</property>  <!-- 1爲開啓實時統計、0爲關閉 -->
	<property name="useGlobleTableCheck">0</property>  <!-- 1爲開啓全加班一致性檢測、0爲關閉 -->

		<property name="sequnceHandlerType">1</property>
		<!--<property name="sequnceHandlerPattern">(?:(\s*next\s+value\s+for\s*MYCATSEQ_(\w+))(,|\)|\s)*)+</property>-->
		<!--必須帶有MYCATSEQ_或者 mycatseq_進入序列匹配流程 注意MYCATSEQ_有空格的情況-->
		<property name="sequnceHandlerPattern">(?:(\s*next\s+value\s+for\s*MYCATSEQ_(\w+))(,|\)|\s)*)+</property>
	<property name="subqueryRelationshipCheck">false</property> <!-- 子查詢中存在關聯查詢的情況下,檢查關聯字段中是否有分片字段 .默認 false -->
      <!--  <property name="useCompression">1</property>--> <!--1爲開啓mysql壓縮協議-->
        <!--  <property name="fakeMySQLVersion">5.6.20</property>--> <!--設置模擬的MySQL版本號-->
	<!-- <property name="processorBufferChunk">40960</property> -->
	<!-- 
	<property name="processors">1</property> 
	<property name="processorExecutor">32</property> 
	 -->
        <!--默認爲type 0: DirectByteBufferPool | type 1 ByteBufferArena | type 2 NettyBufferPool -->
		<property name="processorBufferPoolType">0</property>
		<!--默認是65535 64K 用於sql解析時最大文本長度 -->
		<!--<property name="maxStringLiteralLength">65535</property>-->
		<!--<property name="sequnceHandlerType">0</property>-->
		<!--<property name="backSocketNoDelay">1</property>-->
		<!--<property name="frontSocketNoDelay">1</property>-->
		<!--<property name="processorExecutor">16</property>-->
		<!--
			<property name="serverPort">8066</property> <property name="managerPort">9066</property> 
			<property name="idleTimeout">300000</property> <property name="bindIp">0.0.0.0</property> 
			<property name="frontWriteQueueSize">4096</property> <property name="processors">32</property> -->
		<!--分佈式事務開關,0爲不過濾分佈式事務,1爲過濾分佈式事務(如果分佈式事務內只涉及全局表,則不過濾),2爲不過濾分佈式事務,但是記錄分佈式事務日誌-->
		<property name="handleDistributedTransactions">0</property>
		
			<!--
			off heap for merge/order/group/limit      1開啓   0關閉
		-->
		<property name="useOffHeapForMerge">0</property>

		<!--
			單位爲m
		-->
        <property name="memoryPageSize">64k</property>

		<!--
			單位爲k
		-->
		<property name="spillsFileBufferSize">1k</property>

		<property name="useStreamOutput">0</property>

		<!--
			單位爲m
		-->
		<property name="systemReserveMemorySize">384m</property>


		<!--是否採用zookeeper協調切換  -->
		<property name="useZKSwitch">false</property>

		<!-- XA Recovery Log日誌路徑 -->
		<!--<property name="XARecoveryLogBaseDir">./</property>-->

		<!-- XA Recovery Log日誌名稱 -->
		<!--<property name="XARecoveryLogBaseName">tmlog</property>-->
		<!--如果爲 true的話 嚴格遵守隔離級別,不會在僅僅只有select語句的時候在事務中切換連接-->
		<property name="strictTxIsolation">false</property>
		
		<property name="useZKSwitch">true</property>
		
	</system>
	
	<!-- 全局SQL防火牆設置 -->
	<!--白名單可以使用通配符%或着*-->
	<!--例如<host host="127.0.0.*" user="root"/>-->
	<!--例如<host host="127.0.*" user="root"/>-->
	<!--例如<host host="127.*" user="root"/>-->
	<!--例如<host host="1*7.*" user="root"/>-->
	<!--這些配置情況下對於127.0.0.1都能以root賬戶登錄-->
	<!--
	<firewall>
	   <whitehost>
	      <host host="1*7.0.0.*" user="root"/>
	   </whitehost>
       <blacklist check="false">
       </blacklist>
	</firewall>
	-->

	<user name="mycat" defaultAccount="true">
		<property name="password">123456</property>
		<property name="schemas">MYCATDB</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>

	<user name="user">
		<property name="password">user</property>
		<property name="schemas">MYCATDB</property>
		<property name="readOnly">true</property>
	</user>

</mycat:server>

3、修改schema.xml

配置讀寫主機的dataNode

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
	<schema name="MYCATDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
	</schema>
	<dataNode name="dn1" dataHost="host1" database="testdb" />
	<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.190.131:3306" user="root" password="123456">
		<readHost host="hostS1" url="192.168.190.132:3306" user="root"  password="123456" />
		</writeHost>
	</dataHost>
</mycat:schema>

 標籤中 balance參數的含義:

  • balance="0":不開啓讀寫分離機制,即讀請求僅分發到 writeHost上
  • balance="1":讀請求隨機分發到當前 writeHost對應的 readHost和 standby writeHost上
  • balance="2":讀請求隨機分發到當前 dataHost內所有的 writeHost / readHost上
  • balance="3":讀請求隨機分發到當前 writeHost對應的 readHost上

4、mycat命令

 

驗證

mycat作爲數據庫中間件要和數據庫部署在不同的機器上,所以先驗證遠程訪問的情況

mysql -uroot -p123456 -h 192.168.190.131 -P 3306
mysql -uroot -p123456 -h 192.168.190.132 -P 3306

驗證無誤後,控制檯啓動mycat,/usr/local/mycat/bin目錄下執行

./mycat console &

mycat啓動無誤後,登錄mycat數據窗口

[root@localhost ~]# mysql -umycat -p123456 -P 8066 -h 192.168.190.131
Warning: Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 6
Server version: 5.6.29-mycat-1.6.7.1-release-20190627191042 MyCat Server (OpenCloudDB)

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 

爲了驗證mycat讀寫分離的效果,我們將mycat的日誌輸出級別改完debug(默認是info級別),在conf/log4j2.xml裏配置,然後去查詢去添加數據在/logs/mycat.log日誌文件裏查看sql被路由到了哪個服務器上面

log4j2.xml,level從info改爲debug

<asyncRoot level="debug" includeLocation="true">

重啓mycat

/usr/local/mycat/bin/mycat restart

打開mycat日誌

 tail -f /usr/local/mycat/logs/mycat.log 

然後我們開始在mycat中執行insert和select語句,觀察日誌。

讀操作,轉發到了132機子上面

MySQLConnection [id=14, lastTime=1587382166167, user=root, schema=testdb, old shema=testdb, borrowed=true, fromSlaveDB=true, threadId=40, charset=utf8, txIsolation=3, autocommit=true, attachment=dn1{SELECT * from user_info}, respHandler=SingleNodeHandler [node=dn1{SELECT * from user_info}, packetId=0], host=192.168.190.132, port=3306, statusSync=null, writeQueue=0, modifiedSQLExecuted=false]
 to send query cmd:
SELECT * from user_info
 in pool
DBHostConfig [hostName=hostS1, url=192.168.190.132:3306]

寫操作,轉發到了131機子上面

2020-04-20 19:32:29.603 DEBUG [$_NIOREACTOR-0-RW] (io.mycat.backend.mysql.nio.MySQLConnection.synAndDoExecute(MySQLConnection.java:463)) - con need syn ,total syn cmd 1 commands SET names utf8;schema change:false con:MySQLConnection [id=3, lastTime=1587382349602, user=root, schema=testdb, old shema=testdb, borrowed=true, fromSlaveDB=false, threadId=50, charset=utf8, txIsolation=3, autocommit=true, attachment=dn1{insert into user_info (id ,name) values (1,"test")}, respHandler=SingleNodeHandler [node=dn1{insert into user_info (id ,name) values (1,"test")}, packetId=0], host=192.168.190.131, port=3306, statusSync=null, writeQueue=0, modifiedSQLExecuted=true]

查詢語句不要加事務,否則讀操作會被分發到寫服務器上。

執行inser語句時,字段列名不可省略

實現分庫

把原本存儲於一個庫的數據存儲到多個庫上
  由於對數據庫的讀寫都是對同一個庫進行操作,所以單庫並不能解決大規模併發寫入的問題。例如,我們會建立定義數據庫 workDB、商品數據庫 payDB、用戶數據庫 userDB、日誌數據庫 logDB 等,分別用於存儲項目數據定義表、商品定義表、用戶數據表、日誌數據表等。

優點

  1. 減少增量數據寫入時的鎖對查詢的影響。
  2. 由於單表數量下降,常見的查詢操作由於減少了需要掃描的記錄,使得單表單次查詢所需的檢索行數變少,減少了磁盤 IO,時延變短。

缺點:無法解決單表數據量太大的問題,以及分佈式事務的控制。

我們希望將customer相關的表分佈在132從機上,其他的表因爲之前mysql主從複製的配置的緣故,所以在131,132機子上都會有。

1、修改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="customer_info" dataNode="dn2"></table>
	</schema>
	<dataNode name="dn1" dataHost="host1" database="testdb" />
        <dataNode name="dn2" dataHost="host2" database="testdb" />
	<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.190.131: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.190.132:3306" user="root"
				   password="123456">
		</writeHost>
	</dataHost>
</mycat:schema>

驗證

重啓mycat,連接mycat創建表customer_info,order_info,分別插入一些測試數據

create table customer_info(id int,name varchar(50));
insert into customer_info(id,name) values(1,'consumer1');
insert into customer_info(id,name) values(2,'consumer2');

create table order_info(id int,remark varchar(50));
insert into order_info(id,remark) values(1,'order1');
insert into order_info(id,remark) values(1,'order2');

我們分別查看131,132,mycat上面的庫表數據。觀察發現customer_info表只存在132機子上,其他表在131和132機子上都有。

分在不同機子上面的庫表,是不能進行join關聯查詢的。

mycat:

[SQL]SELECT * from customer_info c left join order_info co on c.id=co.id;
[Err] 1064 - can't find table define in schema ORDER_INFO schema:MYCATDB

 而如果在132機子上,這兩張表是進行join關聯查詢:

mysql> SELECT * from customer c left join customer_order co on c.id=co.id;
+------+-----------+------+--------+
| id   | name      | id   | name   |
+------+-----------+------+--------+
|    1 | consumer1 |    1 | order1 |
|    1 | consumer1 |    1 | order2 |
|    2 | consumer2 | NULL | NULL   |
+------+-----------+------+--------+
3 rows in set (0.00 sec)

 所以分庫的策略,應該是一些業務相關聯的表,劃分在同一個庫中,因爲跨數據庫是無法進行連接查詢的。

實現分表

把原本存儲於一個表的數據分塊存儲到多個表上。
  當一個表中的數據量過大時,我們可以把該表的數據按照某種規則,進行劃分,然後存儲到多個結構相同的表,和不同的庫上。例如,我們 userDB 中的 userTable 中數據量很大,那麼可以把 userDB 切分爲結構相同的多個 userDB:part0DB、part1DB 等,再將 userDB 上的 userTable,切分爲很多userTable:userTable0、userTable1 等,然後將這些表按照一定的規則存儲到多個 userDB 上。

優點

  1. 單表的併發能力提高了,磁盤 I/O 性能也提高了。
  2. 如果出現高併發的話,總表可以根據不同的查詢,將併發壓力分到不同的小表裏面。

缺點:無法實現表連接查詢(ER表來處理),以及分佈式事務的控制。

相對於垂直拆分,水平拆分不是將表做分類,而是按照某個字段的某種規則來分散到多個庫之中,每個表中包含一部分數據。簡單來說,我們可以將數據的水平切分理解爲是按照數據行的切分,就是將表中的某些行切分到一個數據庫,而另外的某些行又切分到其他的數據庫中。

我們新建一個product_info表,希望通過product_type對數據進行取模,水平分表

1、修改schema.xml

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
        <schema name="MYCATDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
		<table name="customer" dataNode="dn2"></table>
                <table name="product_info" dataNode="dn1,dn2" rule="mod_rule"></table>
	</schema>
	<dataNode name="dn1" dataHost="host1" database="testdb" />
        <dataNode name="dn2" dataHost="host2" database="testdb" />
	<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.190.131: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.190.132:3306" user="root"
				   password="123456">
		</writeHost>
	</dataHost>
</mycat:schema>

2、修改rule.xml

新增一個自定義rule

<tableRule name="mod_rule">
	<rule>
		<columns>product_type</columns>
		<algorithm>mod-long</algorithm>
	</rule>
</tableRule>

因爲只有兩個數據庫,所以將mod-long的的count改爲2

<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
	<property name="count">2</property>
</function>

驗證

重啓mycat,連接mycat創建表product_info,插入一些測試數據

create table product_info(id int,product_type int,remark varchar(50));
insert into  product_info(id,product_type ,remark ) values(1,1,'remark');
insert into  product_info(id,product_type ,remark ) values(2,2,'remark');
insert into  product_info(id,product_type ,remark ) values(3,3,'remark');
insert into  product_info(id,product_type ,remark ) values(4,4,'remark');
insert into  product_info(id,product_type ,remark ) values(5,5,'remark');
insert into  product_info(id,product_type ,remark ) values(6,1,'remark');
insert into  product_info(id,product_type ,remark ) values(7,2,'remark');
insert into  product_info(id,product_type ,remark ) values(8,3,'remark');

product_info表product_type爲偶數的在131機子上面,product_type爲奇數的在132機子上面,實現了根據product_type水平的分表

分片的join

MyCat借鑑了NewSQL領域的新秀FoundationDB的設計思路,FoundationDB創新性的提出了Table Group的概念,其將指標的存儲位置依賴於主表,並且物理上緊鄰存放,因此徹底解決了join的效率和新呢個的問題,根據這一思路,提出了ER關係的數據分片策略,指標的記錄與所關聯的父表記錄存放在同一個數據分片上。

我們希望product_info表中對應的product_detail的數據的分片能同product_info中的數據分片保持一致。

修改schema.xml,新增childTable

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
        <schema name="MYCATDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
		<table name="customer" dataNode="dn2"></table>
                <table name="product_info" dataNode="dn1,dn2" rule="mod_rule">
			<childTable name="product_detail" primaryKey="id" joinKey="product_id" parentKey="id"></childTable >
		</table>
	</schema>
	<dataNode name="dn1" dataHost="host1" database="testdb" />
        <dataNode name="dn2" dataHost="host2" database="testdb" />
	<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.190.131: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.190.132:3306" user="root"
				   password="123456">
		</writeHost>
	</dataHost>
</mycat:schema>

驗證

重啓mycat,連接mycat創建表product_detail,插入一些測試數據

create table product_detail(id int,product_id int);
insert into  product_detail(id,product_id ) values(1,1);
insert into  product_detail(id,product_id ) values(2,2);
insert into  product_detail(id,product_id ) values(3,3);
insert into  product_detail(id,product_id ) values(4,4);
insert into  product_detail(id,product_id ) values(5,5);

product_detail表的數據跟着product_info表,通過product_id字段關聯來進行水平分片。

全局表

在分片的情況下,當業務表因爲規模而進行分片以後,業務表與這些附屬的字典表之間的關聯,就成了比較棘手的問題,考慮到字典表具有以下幾個特性

  • 變動不頻繁
  • 數據量總體變化不大
  • 數據規模不大,很少有超過數十萬條記錄

鑑於此, MyCat定義了一種特殊的表,稱之爲“全局表”,全局表具有以下特點:

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

將字典表或者符合字典表特性的一些表定義爲全局表,這從另外一個方面,很好的解決了數據join的難題。通過全局表+基於ER關係的分片策略,MyCat可以滿足80%以上的企業有與開發需求。

修改schema.xml,添加global表的配置

<?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="customer" dataNode="dn2"></table>
                <table name="orders" dataNode="dn1,dn2" rule="mod_rule">
			<childTable name="orders_detail" primaryKey="id" joinKey="order_id" parentKey="id"></childTable >
		</table>
		<table name="dict_order_type" dataNode="dn1,dn2" type="global"></table>
	</schema>
	<dataNode name="dn1" dataHost="host1" database="testdb" />
        <dataNode name="dn2" dataHost="host2" database="testdb" />
	<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.190.131: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.190.132:3306" user="root"
				   password="123456">
		</writeHost>
	</dataHost>
</mycat:schema>

 重啓mycat,連接mycat創建表dict_order_type,插入一些測試數據

create table dict_order_type(id int,type varchar(50));
insert into  dict_order_type(id,type) values(1,'普通');
insert into  dict_order_type(id,type) values(2,'團購');

 131機子:

mysql> select * from dict_order_type;
+------+--------+
| id   | type   |
+------+--------+
|    1 | 普通   |
|    2 | 團購   |
+------+--------+
2 rows in set (0.00 sec)

132機子:

mysql> select * from dict_order_type;
+------+--------+
| id   | type   |
+------+--------+
|    1 | 普通   |
|    2 | 團購   |
+------+--------+
2 rows in set (0.00 sec)

 mycat:

mysql> select * from dict_order_type;
+------+--------+
| id   | type   |
+------+--------+
|    1 | 普通   |
|    2 | 團購   |
+------+--------+
2 rows in set (0.00 sec)

常用的分片規則

取模分片

此規則對分片字段求模運算。也是水平分表最常用的規則,我們上面orders表就是採用了此規則。

枚舉分片

通過在配置文件中配置可能的枚舉id,自己配置分片,本規則適用於特定的場景,比如有些業務需要按照省份或者區縣來做保存,而全國省份區縣固定,這類業務使用本條規則。

我們希望order_ware_info表中字段areacode=110的數據保存在131機子上,areacode=120的數據保存在132機子上。

1、修改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="customer" dataNode="dn2"></table>
                <table name="orders" dataNode="dn1,dn2" rule="mod_rule">
			<childTable name="orders_detail" primaryKey="id" joinKey="order_id" parentKey="id"></childTable >
		</table>
		<table name="dict_order_type" dataNode="dn1,dn2" type="global"></table>
                <table name="order_ware_info" dataNode="dn1,dn2" rule="sharding_by_intfile"></table>
	</schema>
	<dataNode name="dn1" dataHost="host1" database="testdb" />
        <dataNode name="dn2" dataHost="host2" database="testdb" />
	<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.190.131: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.190.132:3306" user="root"
				   password="123456">
		</writeHost>
	</dataHost>
</mycat:schema>

2、修改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>
	<property name="type">1</property>
	<property name="defaultNode">0</property>
</function>

3、修改partition-hash-int.txt文件:

110=0
120=1

110存在第一個節點,120存在第二個節點

重啓mycat,連接mycat創建表order_ware_info,插入一些測試數據

create table order_ware_info(id int,areacode varchar(50));
insert into order_ware_info(id,areacode) values(1,"110");
insert into order_ware_info(id,areacode) values(2,"110");
insert into order_ware_info(id,areacode) values(3,"120");
insert into order_ware_info(id,areacode) values(4,"120");

 131機子:

mysql> select * from order_ware_info;
+------+----------+
| id   | areacode |
+------+----------+
|    1 | 110      |
|    2 | 110      |
+------+----------+
2 rows in set (0.00 sec)

 132機子:

mysql> select * from order_ware_info;
+------+----------+
| id   | areacode |
+------+----------+
|    3 | 120      |
|    4 | 120      |
+------+----------+
2 rows in set (0.00 sec)

 mycat:

mysql> select * from order_ware_info;
+------+----------+
| id   | areacode |
+------+----------+
|    1 | 110      |
|    2 | 110      |
|    3 | 120      |
|    4 | 120      |
+------+----------+
4 rows in set (0.00 sec)

範圍分片

此分片適用於,提前規劃好分片字段某個範圍屬於哪個分片。

我們希望payment_info表中,order_id小於102的保存在131機子,102-200的保存在132機子上。

1、修改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="customer" dataNode="dn2"></table>
                <table name="orders" dataNode="dn1,dn2" rule="mod_rule">
			<childTable name="orders_detail" primaryKey="id" joinKey="order_id" parentKey="id"></childTable >
		</table>
		<table name="dict_order_type" dataNode="dn1,dn2" type="global"></table>
                <table name="order_ware_info" dataNode="dn1,dn2" rule="sharding_by_intfile"></table>
 		<table name="payment_info" dataNode="dn1,dn2" rule="auto_sharding_long"></table>
	</schema>
	<dataNode name="dn1" dataHost="host1" database="testdb" />
        <dataNode name="dn2" dataHost="host2" database="testdb" />
	<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.190.131: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.190.132:3306" user="root"
				   password="123456">
		</writeHost>
	</dataHost>
</mycat:schema>

2、修改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>

 3、修改autopartition-long.txt文件:

0-102=0
103-200=1

 重啓mycat,連接mycat創建表payment_info,插入一些測試數據

create table payment_info(order_id int,name varchar(50));
insert into payment_info(order_id,name) values(1,"test");
insert into payment_info(order_id,name) values(2,"test");
insert into payment_info(order_id,name) values(150,"test");
insert into payment_info(order_id,name) values(151,"test");

131機子:

mysql> select * from payment_info;
+----------+------+
| order_id | name |
+----------+------+
|        1 | test |
|        2 | test |
+----------+------+
2 rows in set (0.00 sec)

132機子:

mysql> select * from payment_info;
+----------+------+
| order_id | name |
+----------+------+
|      150 | test |
|      151 | test |
+----------+------+
2 rows in set (0.00 sec)

 mycat:

mysql> select * from payment_info;
+----------+------+
| order_id | name |
+----------+------+
|        1 | test |
|        2 | test |
|      150 | test |
|      151 | test |
+----------+------+
4 rows in set (0.02 sec)

 日期分片

此規則爲按天分片。設定時間格式,範圍。

我們希望login_info表中,login_date從2019-01-01開始,每隔2天,分別保存在131機子,132機子上。

1、修改shema.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="customer" dataNode="dn2"></table>
                <table name="orders" dataNode="dn1,dn2" rule="mod_rule">
			<childTable name="orders_detail" primaryKey="id" joinKey="order_id" parentKey="id"></childTable >
		</table>
		<table name="dict_order_type" dataNode="dn1,dn2" type="global"></table>
                <table name="order_ware_info" dataNode="dn1,dn2" rule="sharding_by_intfile"></table>
 		<table name="payment_info" dataNode="dn1,dn2" rule="auto_sharding_long"></table>
		<table name="login_info" dataNode="dn1,dn2" rule="sharding__by_date"></table>
	</schema>
	<dataNode name="dn1" dataHost="host1" database="testdb" />
        <dataNode name="dn2" dataHost="host2" database="testdb" />
	<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.190.131: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.190.132:3306" user="root"
				   password="123456">
		</writeHost>
	</dataHost>
</mycat:schema>

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

 spartionDay:分區天數,即默認從開始日期算起,每隔2天一個分區。

重啓mycat,連接mycat創建表login_info,插入一些測試數據

create table login_info(login_date date,name varchar(50));
insert into login_info(login_date,name) values("2019-01-01","test");
insert into login_info(login_date,name) values("2019-01-02","test");
insert into login_info(login_date,name) values("2019-01-03","test");
insert into login_info(login_date,name) values("2019-01-04","test");
insert into login_info(login_date,name) values("2019-01-05","test");
insert into login_info(login_date,name) values("2019-01-06","test");
insert into login_info(login_date,name) values("2019-01-07","test");
insert into login_info(login_date,name) values("2019-01-08","test");
insert into login_info(login_date,name) values("2019-01-09","test");
insert into login_info(login_date,name) values("2019-01-10","test");

131機子:

mysql> select * from login_info;
+------------+------+
| login_date | name |
+------------+------+
| 2019-01-01 | test |
| 2019-01-02 | test |
| 2019-01-05 | test |
| 2019-01-06 | test |
| 2019-01-09 | test |
| 2019-01-10 | test |
+------------+------+
6 rows in set (0.00 sec)

132機子:

mysql> select * from login_info;
+------------+------+
| login_date | name |
+------------+------+
| 2019-01-03 | test |
| 2019-01-04 | test |
| 2019-01-07 | test |
| 2019-01-08 | test |
+------------+------+
4 rows in set (0.00 sec)

 mycat:

mysql> select * from login_info;
+------------+------+
| login_date | name |
+------------+------+
| 2019-01-01 | test |
| 2019-01-02 | test |
| 2019-01-05 | test |
| 2019-01-06 | test |
| 2019-01-09 | test |
| 2019-01-10 | test |
| 2019-01-03 | test |
| 2019-01-04 | test |
| 2019-01-07 | test |
| 2019-01-08 | test |
+------------+------+
10 rows in set (0.01 sec)

全局序列

利用數據庫的一個表來進行計數累加,但是並不是每次生成序列都是讀寫數據庫,這樣效率太低。MyCat會預加載一部分號段到MyCat的內存中,這樣大部分讀寫序列都是在內存中完成的。如果內存中的號段用完了,myCat會再向數據庫要一次。如果MyCat奔潰了,那麼內存中的序列是丟失,重啓後,丟失的是當前號段沒用完的序列,當時不會因此出現主鍵重複。下面我們採用數據庫自增的方式來生成全局序列。

1、創建序列表和相關函數:

我們在131機子上面執行

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 ;
INSERT INTO MYCAT_SEQUENCE(NAME,current_value,increment) VALUES ('GLOBAL', 100000, 100);
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 ;
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 ;
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 ;

2、修改server.xml文件:

修改全局序列類型爲數據庫的方式,0,本地文件,1數據庫方式,2時間戳方式

<property name="sequnceHandlerType">1</property>

3、sequence_db_conf.properties

ORDERS=dn1

4、向MYCAT_SEQENCE表插入orders表的序列規則,order表的主鍵從1開始自增,mycat服務器每次從庫表獲取100天序列緩存。

insert into MYCAT_SEQENCE(NAME,current_value,increment) values("ORDERS",1,100)

重啓mycat,連接mycat向orders表插入一些測試數據

insert into orders(id,customer_id,name) values(next value for MYCATSEQ_ORDERS,1,"test");
insert into orders(id,customer_id,name) values(next value for MYCATSEQ_ORDERS,2,"test");
insert into orders(id,customer_id,name) values(next value for MYCATSEQ_ORDERS,3,"test");
insert into orders(id,customer_id,name) values(next value for MYCATSEQ_ORDERS,4,"test");
insert into orders(id,customer_id,name) values(next value for MYCATSEQ_ORDERS,5,"test");
insert into orders(id,customer_id,name) values(next value for MYCATSEQ_ORDERS,6,"test");
insert into orders(id,customer_id,name) values(next value for MYCATSEQ_ORDERS,7,"test");
insert into orders(id,customer_id,name) values(next value for MYCATSEQ_ORDERS,8,"test");
insert into orders(id,customer_id,name) values(next value for MYCATSEQ_ORDERS,9,"test");
insert into orders(id,customer_id,name) values(next value for MYCATSEQ_ORDERS,10,"test");

插入成功,order表的主鍵從1開始自增

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