1. 程式人生 > >MyCat相關知識及測試要點

MyCat相關知識及測試要點

1 重要定義及相關配置

1.1 垂直切分

1.1.1 垂直切分定義

資料的垂直切分,也可以稱為縱向切分。將資料庫想象成由很多個一大塊一大塊的“資料塊”(表)組成,垂直地將這些“資料塊”切開,然後把它們分散到多臺資料庫主機上面。這樣的切分方法就是垂直(縱向)的資料切分。

一個架構設計較好的應用系統,其總體功能肯定是由很多個功能模組所組成的,而每一個功能模組所需要的資料對應到資料庫中就是一個或多個表。而在架構設計中,各個功能模組相互之間的互動點越統一、越少,系統的耦合度就越低,系統各個模組的維護性及擴充套件性也就越好。這樣的系統,實現資料的垂直切分也就越容易

1.1.2 優缺點

垂直切分優點:
(1)資料庫的拆分簡單明瞭,拆分規則明確;
(2)應用程式模組清晰明確,整合容易;
(3)資料維護方便易行,容易定位。

垂直切分缺點:
(1)部分表關聯無法在資料庫級別完成,要在程式中完成;
(2)對於訪問極其頻繁且資料量超大的表仍然存在效能瓶頸,不一定能滿足要求;
(3)事務處理相對複雜;
(4)切分達到一定程度之後,擴充套件性會受到限制;
(5)過度切分可能會帶來系統過於複雜而難以維護。

1.1.2.1 垂直切分實現

在如下的例項中,需要將
<!-- -->
<dataHost name="jdbchost2" maxCon="500" minCon="10" balance="0"
       writeType="0" dbType="mysql" dbDriver="native">
<heartbeat>select 1</heartbeat>
<writeHost host="maste1" url="192.168.0.5:3306" user="root" password="root">
  <!-- <readHost host="readshard" url="192.168.0.6:3306" user="root" password="root"/> -->
</writeHost>
<writeHost host="maste2" url="192.168.0.6:3307" user="root" password="root">
  <!-- <readHost host="readshard" url="192.168.0.8:3306" user="root" password="root"/> -->
</writeHost>
</writeHost>
</dataHost>

</mycat:schema>

編輯MYCAT_HOME/conf/schema.xml檔案,修改dataHost和schema對應的連線資訊,user,pay 垂直切分後的配置如下所示:

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://org.opencloudb/">
    <schema name="user" checkSQLschema="false" sqlMaxLimit="100" dataNode="user" />
    <schema name="pay"  checkSQLschema="false" sqlMaxLimit="100" dataNode="pay" />

    <dataNode name="user" dataHost="host1" database="user" />
    <dataNode name="pay" dataHost="host2" database="pay" />

    <dataHost name="host1" maxCon="1000" minCon="10" balance="0"
       writeType="0" dbType="mysql" dbDriver="native">
       <heartbeat>select 1</heartbeat>
       <!-- can have multi write hosts -->
       <writeHost host="hostM1" url="192.168.0.2:3306" user="root" password="root" />
       <!--<writeHost host="hostM2" url="192.168.0.3:3306" user="root" password="root" />-->
    </dataHost> 
    <dataHost name="host2" maxCon="1000" minCon="10" balance="0"
       writeType="0" dbType="mysql" dbDriver="native">
       <heartbeat>select 1</heartbeat>
       <!-- can have multi write hosts -->
       <writeHost host="hostM2" url="192.168.0.3:3306" user="root" password="root" />
    </dataHost>
</mycat:schema>
注意:writeHost/readHost中的location,user,password的值需要根據實際的MySQL的連線資訊進行修改。檢視conf/server.xml檔案,該檔案是Mycat伺服器引數調整和使用者授權的配置檔案,預設的MyCat的資料庫連線的使用者名稱/密碼為mycat/mycat,檔案內容參考如下:<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:server SYSTEM "server.dtd">
<mycat:server xmlns:mycat="http://org.opencloudb/">
	<system>
		<property name="defaultSqlParser">druidparser</property>

	</system>
	<user name="mycat">
		<property name="password">mycat</property>
		<property name="schemas">user,pay</property>
	</user>

</mycat:server>

上述檔案中的schemas屬性需要配置對應的schema(在schema.xml)中進行配置。
重啟MyCat,使用MySQL客戶端連線MyCat,需要注意的是,預設資料埠為8066,管理埠為9066,在MySQL客戶端連線MyCat時,注意填寫埠為8066,使用者名稱/密碼根據server.xml中的配置進行填寫。

連線後可檢視後端連線的資料庫。

經常遇到的問題:
1)、Caused by: org.xml.sax.SAXParseException; lineNumber: 106; columnNumber: 16; The content of element type "mycat:schema" must match "(schema*,dataNode*,dataHost*)".
原因:要按照schema、datanode 、datahost的順序放,不能打亂。也就是所有schema要放一起,然後接著才能放datanode。。。。

2)、報1184錯誤,是因為沒有 把datahost主機的許可權授予mycat所在主機。
<writeHost host="hadoop2" url="192.168.0.3:3306" user="root" password="123456" />
這裡的 使用者要授予mycat所在主機遠端訪問許可權:
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456'

1.2 水平分庫

1.2.1 水平切分定義

水平切分所指的是通過一系列的切分規則將資料水平分佈到不同的DB或table中,在通過相應的DB路由 或者table路由規則找到需要查詢的具體的DB或者table以進行Query操作,比如根據使用者ID將使用者表切分到多臺資料庫上。
將某個訪問極其頻繁的表再按照某個欄位的某種規則來分散到多個表之中,每個表中包含一部分資料。
例如,所有資料都是和使用者關聯的,那麼我們就可以根據使用者來進行水平拆分,將不同使用者的資料切分到不同的資料庫中。
現在網際網路非常火爆的web 2.0型別的網站,基本上大部分資料都能夠通過會員使用者資訊關聯上,可能很多核心表都非常適合通過會員ID來進行資料的水平切分。而像論壇社群討論系統,就更容易切分了,非常容易按照論壇編號來進行資料的水平切分。切分之後基本上不會出現各個庫之間的互動。

1.2.2 優缺點

水平切分的優點:
    表關聯基本能夠在資料庫端全部完成;
    不會存在某些超大型資料量和高負載的表遇到瓶頸的問題;
    應用程式端整體架構改動相對較少;
    事務處理相對簡單;
    只要切分規則能夠定義好,基本上較難遇到擴充套件性限制。
水平切分的缺點:
    切分規則相對更為複雜,很難抽象出一個能夠滿足整個資料庫的切分規則;
    後期資料的維護難度有所增加,人為手工定位資料更困難;
    應用系統各模組耦合度較高,可能會對後面資料的遷移拆分造成一定的困難。

1.2.3 水平切分實現

在一般的應用系統中,使用者表及其密切相關的關聯表,可根據“使用者表”(eg:t_user)中的“使用者ID”(user_id)進行水平切分,並基於MyCAT的E-R關係分片策略將其密切相關的表(eg:t_user_class_rel)也分到對應的庫中。

(1)建立庫,表結構

         在建表語句參考如下:
CREATE DATABASE IF NOT EXISTS `mycat_node1`  ;

/**
  user 使用者表
*/ 
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
 `user_id` int(11) NOT NULL COMMENT '使用者ID',
 `receive_address` varchar(256) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '收貨地址',
 `create_time` datetime NOT NULL,
 `province_code` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL,
 PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='使用者資訊表';
 
 /**
  `t_area` 全域性表
*/
CREATE TABLE `t_area` (
 `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
 `caller` varchar(16) CHARACTER SET utf8 DEFAULT NULL COMMENT '呼叫方系統表示',
 `province_code` varchar(10) CHARACTER SET utf8 NOT NULL COMMENT '省份編碼',
 `area_code` varchar(10) CHARACTER SET utf8 NOT NULL COMMENT '區域編碼',
 `area_name` varchar(100) CHARACTER SET utf8 DEFAULT NULL COMMENT '區域名稱',
 `parent_area_code` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '父區域編碼',
 `create_time` datetime NOT NULL COMMENT '建立時間',
 `modify_time` datetime DEFAULT NULL COMMENT '修改時間',
 PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3792 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

 /**
  user 節點測試表
*/ 
 DROP TABLE IF EXISTS `t_node`;
CREATE TABLE `t_node` (
 `vid` int(11) NOT NULL COMMENT 'ID',
 `user_id` int(11) NOT NULL COMMENT '使用者ID',
 `note` varchar(256) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'note',
 `create_time` datetime NOT NULL,
 PRIMARY KEY (`vid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='note表';
(2)配置schema.xml檔案
首先配置schema.xml檔案,新增mycat_node1資料庫的dataNode設定,並新增t_user和t_area表的schema設定,本次配置了雙主,讀寫分離配置,
同一個表多個分片的配置可以用dataNode="dn$1-100" 通配方式。
修改後的schema.xml檔案內容如下所示:
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://org.opencloudb/">

<schema name="mycat" checkSQLschema="true" sqlMaxLimit="100">
<table name="t_user" primaryKey="user_id" dataNode="dn1,dn2" rule="rule1" />
        <table name="t_node" primaryKey="vid" autoIncrement="true" dataNode="dn1,dn2" rule="rule1" />
<table name="t_area" type="global" primaryKey="ID" dataNode="dn1,dn2" />

</schema>

<dataNode name="dn1" dataHost="jdbchost" database="mycat_node1" />
<dataNode name="dn2" dataHost="jdbchost2" database="mycat_node1" />

<dataHost name="jdbchost" maxCon="500" minCon="10" balance="0"
       writeType="0" dbType="mysql" dbDriver="native">
<heartbeat>select 1</heartbeat>
<writeHost host="maste1" url="192.168.0.1:3306" user="root" password="root">
  <!-- <readHost host="readshard" url="192.168.0.2:3306" user="root" password="root"/> -->
</writeHost>
<writeHost host="maste2" url="192.168.0.3:3306" user="root" password="root">
  <!-- <readHost host="readshard" url="192.168.0.4:3306" user="root" password="root"/> -->
</writeHost>

</dataHost>

(3)配置rule.xml檔案

在schema.xml的檔案內容中可看到t_user表指定的分片規則是rule1,需要在conf/rule.xml檔案中設定rule1的規則為根據user_id進行分片,並按照類“org.opencloudb.route.function.PartitionByLong”的規則進行分片,即將user_id模除1024後每256內分到一個數據庫中,即模除後0~255到user0資料庫庫,256~511到user1資料庫,512~767到user2資料庫,768~1023到user3資料庫。

         該檔案的參考內容如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:rule SYSTEM "rule.dtd">
<mycat:rule xmlns:mycat="http://org.opencloudb/"> 
 <tableRule name="rule1">
    <rule>
      <columns>user_id</columns>
      <algorithm>func1</algorithm>
    </rule>
 </tableRule>

 <function name="func1" class="org.opencloudb.route.function.PartitionByLong">
    <property name="partitionCount">4</property>
    <property name="partitionLength">256</property>
 </function>
</mycat:rule>

總結心得:普通取模演算法,連續的id會路由到不同的分片。增大了批量插入的事務控制難度,而固定分片hash演算法根據二進位制則可能會分到連續的分片,減少插入事務事務控制難度。

(4)配置server.xml檔案
         在server.xml檔案中的schemas屬性中新增test_mycat的schema。修改後的檔案如下所示:

<!DOCTYPE mycat:server SYSTEM "server.dtd">
<mycat:server xmlns:mycat="http://org.opencloudb/">
    <system>
        <property name="sequnceHandlerType">0</property> 
    </system>
    <user name="mycat">
       <property name="password">mycat</property>
       <property name="schemas">mycat</property>
     </user>
</mycat:server>
(5)水平切分測試
         重啟MyCAT,使用MySQL客戶端連線後,連線後可在mycat資料庫下看到t_user和t_area,t_node表。
         在MySQL客戶端連線的MyCat的test_mycat資料庫的t_user表執行如下插入語句,插入2000條資料:
INSERT INTO `t_user` VALUES ('1', '廣州市越秀區廣州大道中599號', '2014-07-17 10:53:15', 'GD');
INSERT INTO `t_user` VALUES ('2', '廣州市越秀區廣州大道中599號', '2014-07-17 10:53:17', 'GD');
INSERT INTO `t_user` VALUES ('3', '廣州市越秀區廣州大道中599號', '2014-07-17 10:53:17', 'GD');
INSERT INTO `t_user` VALUES ('4', '廣州市越秀區廣州大道中599號', '2014-07-17 10:53:17', 'GD');
INSERT INTO `t_user` VALUES ('5', '廣州市越秀區廣州大道中599號', '2014-07-17 10:53:17', 'GD');
……
INSERT INTO `t_user` VALUES (2000, '廣州市越秀區廣州大道中599號', '2014-07-17 10:54:37', 'GD');

1.3 讀寫分離

1.3.1 讀寫分離定義

為了確保資料庫產品的穩定性,很多資料庫擁有雙機熱備功能。也就是,第一臺資料庫伺服器,是對外提供增刪改查業務的生產伺服器;第二臺資料庫伺服器,僅僅接收來自第一臺伺服器的備份資料。一般來說,為了配置方便,以及穩定性,這兩臺資料庫伺服器,都用的是相同的配置。在實際執行中,第一臺資料庫伺服器的壓力,遠遠大於第二臺資料庫伺服器。因此,很多人希望合理利用第二臺資料庫伺服器的空閒資源。從資料庫的基本業務來看,資料庫的操作無非就是增刪改查這4個操作。但對於“增刪改”這三個操作,如果是雙機熱備的環境中做,一臺機器做了這三個操作的某一個之後,需要立即將這個操作,同步到另一臺伺服器上。出於這個原因,第二臺備用的伺服器,就只做了查詢操作。進一步,為了降低第一臺伺服器的壓力,乾脆就把查詢操作全部丟給第二臺資料庫伺服器去做,第一臺資料庫伺服器就只做增刪改了。

1.3.2 優缺點

優點:合理利用從資料庫伺服器的空閒資源。缺點:本來第二臺資料庫伺服器,是用來做熱備的,它就應該在一個壓力非常小的環境下,保證執行的穩定性。而讀寫分離,卻增加了它的壓力,也就增加了不穩定性。因此,讀寫分離,實質上是一個在資金比較缺乏,但又需要保證資料安全的需求下,在雙機熱備方案上,做出的一種折中的擴充套件方案。

1.3.3 讀寫分離實現

MyCAT的讀寫分離機制如下:       事務內的SQL,全部走寫節點,除非某個select語句以註釋/*balance*/開頭;       自動提交的select語句會走讀節點,並在所有可用讀節點中間隨機負載均衡;       當某個主節點宕機,則其全部讀節點都不再被使用,因為此時,同步失敗,資料已經不是最新的,MyCAT會採用另外一個主節點所對應的全部讀節點來實現select負載均衡。       當所有主節點都失敗,則為了系統高可用性,自動提交的所有select語句仍將提交到全部存活的讀節點上執行,此時系統的很多頁面還是能出來資料,只是使用者修改或提交會失敗。例如將本機作為寫庫,10.18.96.133作為讀庫,MyCAT的讀寫分離的配置如下:
<dataHost name="testhost" maxCon="1000" minCon="10" balance="1"
       writeType="0" dbType="mysql" dbDriver="native">
       <heartbeat>select user()</heartbeat>
       <!-- can have multi write hosts -->
       <writeHost host="hostM1" url="localhost:3306" user="root" password="">
           <readHost host="hostM2" url="10.18.96.133:3306" user="test" password="test" />
       </writeHost>
</dataHost>
dataHost的balance屬性設定為:
0,不開啟讀寫分離機制
1,全部的readHost與stand by writeHost參與select語句的負載均衡,簡單的說,當雙主雙從模式(M1->S1,M2->S2,並且M1與 M2互為主備),正常情況下,M2,S1,S2都參與select語句的負載均衡。
2,所有的readHost與writeHost都參與select語句的負載均衡,也就是說,當系統的寫操作壓力不大的情況下,所有主機都可以承擔負載均衡。
一個dataHost元素,表明進行了資料同步的一組資料庫,DBA需要保證這一組資料庫伺服器是進行了資料同步複製的。writeHost相當於Master DB Server,而其下的readHost則是與從資料庫同步的Slave DB Server。當dataHost配置了多個writeHost的時候,任何一個writeHost宕機,Mycat 都會自動檢測出來,並嘗試切換到下一個可用的writeHost。

在沒有配置資料同步複製的情況下,重啟後進行測試,可使用MySQL客戶端直接連線讀庫,插入幾條資料後,使用MySQL客戶端連線MyCat,執行select語句驗證是否在讀庫上執行。

多主,多讀寫分離參考:
其中分為dn1,dn2兩個分片,每個分片有兩個寫入,寫入之間開啟mysql開啟2進位制複製,mycat會從當前可用的寫入host隨機獲取一個寫入,mycat在任何時候   只會單點寫入。如果要開啟讀寫分離在放開註釋read配置,balance=1或2。

writeType=1 代表所有節點都是寫入寫入,慎重開啟,多節點寫入順序為預設寫入根據配置順序,第一個掛掉切換另一個。

MyCat讀寫分離機制如下:

      事務內的SQL,全部走寫節點,除非某個select語句以註釋/*balance*/開頭。
      自動提交的select語句會走讀節點,並在所有可用讀節點中間隨機負載均衡。
      當某個主節點宕機,則其全部讀節點都不再被使用,因為此時,同步失敗,資料已經不是最新的,MyCAT會採用另外一個主節點所對應的全部讀節點來實現select負載均衡。
      當所有主節點都失敗,則為了系統高可用性,自動提交的所有select語句仍將提交到全部存活的讀節點上執行,此時系統的很多頁面還是能出來資料,只是使用者修改或提交會失敗。

1.4 全域性表

1.4.1 全域性表定義

一個真實的業務系統中,往往存在大量的類似字典表的表格,它們與業務表之間可能有關係,這種關係,可以理解為“標籤”,而不應理解為通常的“主從關係”,這些表基本上很少變動,可以根據主鍵ID進行快取,下面這張圖說明了一個典型的“標籤關係”圖:
         在分片的情況下,當業務表因為規模而進行分片以後,業務表與這些附屬的字典表之間的關聯,就成了比較棘手的問題,考慮到字典表具有以下幾個特性:
變動不頻繁;
資料量總體變化不大;
資料規模不大,很少有超過數十萬條記錄。
鑑於此,MyCAT定義了一種特殊的表,稱之為“全域性表”,全域性表具有以下特性:

全域性表的插入、更新操作會實時在所有節點上執行,保持各個分片的資料一致性
全域性表的查詢操作,只從一個節點獲取
全域性表可以跟任何一個表進行JOIN操作
將字典表或者符合字典表特性的一些表定義為全域性表,則從另外一個方面,很好的解決了資料JOIN的難題。通過全域性表+基於E-R關係的分片策略,MyCAT可以滿足80%以上的企業應用開發。

1.4.2 全域性表實現

(1)建立表結構
         在各個庫分別建立全域性表(例如:t_area)的表結構,表結構保持一致,例如:
DROP TABLE IF EXISTS `t_area`;
CREATE TABLE `t_area` (
 `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
 `caller` varchar(16) CHARACTER SET utf8 DEFAULT NULL COMMENT '呼叫方系統表示',
 `province_code` varchar(10) CHARACTER SET utf8 NOT NULL COMMENT '省份編碼',
 `area_code` varchar(10) CHARACTER SET utf8 NOT NULL COMMENT '區域編碼',
 `area_name` varchar(100) CHARACTER SET utf8 DEFAULT NULL COMMENT '區域名稱',
 `parent_area_code` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '父區域編碼',
 `create_time` datetime NOT NULL COMMENT '建立時間',
 `modify_time` datetime DEFAULT NULL COMMENT '修改時間',
 PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3792 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
(2)配置schema.xml
全域性表配置比較簡單,不用寫Rule規則,在schema.xml中修改test_schema,新增t_area的table子元素,參考如下配置即可:
<schema name="test_mycat" checkSQLschema="false" sqlMaxLimit="100">
       <!-- auto sharding by id (long) -->
       <table name="t_user" dataNode="dn1,dn2" rule="rule1">
           <childTable name="t_user_class_rel" primaryKey="id" joinKey="user_id" parentKey="user_id" />
       </table>
       <table name="t_area" primaryKey="id" type="global" dataNode="dn1,dn2" />
</schema>
(3)全域性表測試
         執行如下insert語句,往test_mycat的t_area表插入10條資料,如下所示:
INSERT INTO `t_area` VALUES ('100', 'test', 'ZX', '1', '全國', '0', '2012-09-25 08:30:23', null);
INSERT INTO `t_area` VALUES ('101', 'test', 'BJ', '110000', '北京市', '1', '2012-09-25 08:30:23', null);
INSERT INTO `t_area` VALUES ('102', 'test', 'BJ', '110100', '市轄區', '110000', '2012-09-25 08:30:23', null);
INSERT INTO `t_area` VALUES ('103', 'test', 'BJ', '110101', '東城區', '110100', '2012-09-25 08:30:23', null);
INSERT INTO `t_area` VALUES ('104', 'test', 'BJ', '110102', '西城區', '110100', '2012-09-25 08:30:23', null);
INSERT INTO `t_area` VALUES ('105', 'test', 'BJ', '110103', '崇文區', '110100', '2012-09-25 08:30:23', null);
INSERT INTO `t_area` VALUES ('106', 'test', 'BJ', '110104', '宣武區', '110100', '2012-09-25 08:30:23', null);
INSERT INTO `t_area` VALUES ('107', 'test', 'BJ', '110105', '朝陽區', '110100', '2012-09-25 08:30:23', null);
INSERT INTO `t_area` VALUES ('108', 'test', 'BJ', '110106', '豐臺區', '110100', '2012-09-25 08:30:23', null);
INSERT INTO `t_area` VALUES ('109', 'test', 'BJ', '110107', '石景山區', '110100', '2012-09-25 08:30:23', null);
插入後去dn1,dn2分片中查詢,可看到這2個分片中的t_area表都被插入10條資料。執行select語句能返回t_area表的對應記錄,執行update和delete語句能對應對全域性表相關的分片中的記錄進行更新和刪除操作。

根據以上內容可以做到:
        1、庫表雜湊
        2、讀寫分離
        3、為mysql資料庫叢集提供統一檢視
        4、配合ha可做高可用資料庫叢集
        其他等

最最後舉一個同時包含垂直切分、水平切分、全域性表的schema.xml配置,以供大家參考:

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

         <schema name="qs" checkSQLschema="false" sqlMaxLimit="100">
                <!-- 垂直切分 -->
                <table name="test1" primaryKey="id" type="global" dataNode="nodeTest1" />
                <table name="test2" primaryKey="id" type="global" dataNode="nodeTest2" />

                <!-- 全域性表的配置如下(比如配置檔案的資料,資料不大很少變動的,但是經常用到查詢的) -->
                <!-- <table name="t_area" primaryKey="id" type="global" dataNode="dn1,dn2" /> -->

                <!-- 水平切分 -->
                <!-- ER分片表:如t_user表和t_user_detail表的關聯欄位user_id;保證相同user_id的資料在同一塊片區上 -->
                <table name="t_user" primaryKey="user_id" dataNode="nodeUser01,nodeUser02" rule="mod-long">
                    <childTable name="t_user_detail" primaryKey="id" joinKey="user_id" parentKey="user_id" />
                </table>
         </schema>

         <dataNode name="nodeUser01" dataHost="dataHost01" database="db3" />
         <dataNode name="nodeUser02" dataHost="dataHost02" database="db3" />

         <dataNode name="nodeTest1" dataHost="dataHost01" database="db1" />
         <dataNode name="nodeTest2" dataHost="dataHost02" database="db1" />

        <dataHost name="dataHost01" maxCon="1000" minCon="10" balance="0"
                writeType="0" dbType="mysql" dbDriver="native">
                <heartbeat>select user()</heartbeat>
                <writeHost host="hostM1" url="192.168.0.83:3306" user="root" password="root"/>
        </dataHost>

        <dataHost name="dataHost02" maxCon="1000" minCon="10" balance="0"
                writeType="0" dbType="mysql" dbDriver="native">
                <heartbeat>select user()</heartbeat>
                <writeHost host="hostM1" url="192.168.0.165:3306" user="root" password="root"/>
        </dataHost>
</mycat:schema>

2 相關測試點

2.1 垂直切分測試

在完成以上重直切分配置後開展測試,主要通過schema.xml的配置

通過編制測試指令碼,大批量往多表(各表實現分庫)中插入資料,以驗證重直切分效果。

重直切分不作重點論述,可以結合水平切分測試一起開展,之半無關聯的表可以垂直切分,有關聯的表可以考慮水平切分或定義全域性表。

2.2 水平切分測試

可以按照以上水平切分配置,完成t_user表分庫(按照rule.xml配置實現單表分庫),但本次測試的例子要複雜一點,設定ER關係的兩表,實現有關係表情況下的水平切分。

1、分別建立4個庫:user0、user1、user2、user3。我這裡4個庫建在4個獨立的主機上。
 CREATE DATABASE  user0 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

2、建立表結構
         在user0~user2建立同樣的表結構,t_user和t_user_class_rel的建表語句參考如下:

DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
 `user_id` int(11) NOT NULL COMMENT '使用者ID',
 `receive_address` varchar(256) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '收貨地址',
 `create_time` datetime NOT NULL,
 `province_code` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL,
 PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='使用者資訊表';

DROP TABLE IF EXISTS `t_user_class_rel`;
CREATE TABLE `t_user_class_rel` (
 `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
 `caller` varchar(16) CHARACTER SET utf8 NOT NULL COMMENT '呼叫方系統表示',
 `province_code` varchar(10) CHARACTER SET utf8 DEFAULT NULL COMMENT '省份編碼',
 `user_id` int(11) NOT NULL COMMENT '使用者ID',
 `class_id` int(11) NOT NULL COMMENT '班級ID',
 `role_type` int(11) DEFAULT NULL COMMENT '使用者在該班的角色(1學生2家長3教師)',
 `create_time` datetime NOT NULL COMMENT '建立時間',
 `modify_time` datetime DEFAULT NULL COMMENT '修改時間',
 PRIMARY KEY (`id`),
 UNIQUE KEY `idx_rel_user_class_id` (`user_id`,`class_id`,`role_type`),
 KEY `idx_rel_user_id` (`user_id`) USING BTREE,
 KEY `idx_rel_class_id` (`class_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
3、新增schema:加了一點內容:不分表的情況測試(只對部分表進行切分。其實這種時候,沒有切分的表,應該是不需要跟已經切分過的表進行關聯,否則就會垮庫join。既然是這樣,那業務就比較獨立了,為什麼不把這部分表垂直切分出去呢?) 

總結心得:

(1)如果某張表進行水平切分了,那麼跟他有事物關聯的表,要麼搞全域性表,要麼進行er分片,不然就會導致垮庫join。而沒有關聯關係的表或者非事物關聯的表,實際上可以垂直切分出去(如果有必要)。

(2)dataHost可以理解成一個主機組,可以是單機,可以是主從,可以是galera 等搭建起來的叢集。讀寫分離就是在這裡處理的。ha、讀寫分離等都在這裡進行配置,都是針對datahost。

<schema name="test_sharding" checkSQLschema="false" sqlMaxLimit="100">
       <!-- auto sharding by id (long) -->
       <table name="t_user" dataNode="user0,user1,user2,user3" rule="rule_wyh">
           <childTable name="t_user_class_rel" primaryKey="id" joinKey="user_id" parentKey="user_id" />
       </table>
           <!-- 此處測試不分表的情況。要先在這裡配置,然後可以在mycat建立t_user_1表,也可以在user3對應的local建立表。如果這裡沒事先配置,無法在mycat建表,會報錯。這個還可以通過制定預設datanode實現,更簡單,配置方法:在shcema標籤上加上datanode -->
           <table name="t_user_1" dataNode="user3" >
       </table>
 </schema>
 <dataNode name="user0" dataHost="host0" database="user0" />
 <dataNode name="user1" dataHost="host1" database="user1" />
 <dataNode name="user2" dataHost="host2" database="user2" />
 <dataNode name="user3" dataHost="host3" database="user3" />
4、新增datahost:host3
<dataHost name="host3" maxCon="1000" minCon="10" balance="0"
   writeType="0" dbType="mysql" dbDriver="native">
   <heartbeat>select user()</heartbeat>
   <!-- can have multi write hosts -->
   <writeHost host="ys-fs" url="192.168.168.238:3306" user="root" password="youngsun" />
</dataHost>
在238上授權授權:
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'youngsun';
flush privileges;
5、配置rule.xml檔案
在schema.xml的檔案內容中可看到t_user表指定的分片規則是rule1,需要在conf/rule.xml檔案中設定rule1的規則為根據user_id進行分片,並按照類“org.opencloudb.route.function.PartitionByLong”的規則進行分片,即將user_id模除1024後每256內分到一個數據庫中,即模除後0~255到user0資料庫庫,256~511到user1資料庫,512~767到user2資料庫,768~1023到user3資料庫。

總結心得:普通取模演算法,連續的id會路由到不同的分片。增大了批量插入的事務控制難度,而固定分片hash演算法根據二進位制則可能會分到連續的分片,減少插入事務事務控制難度。
         該檔案的參考內容如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:rule SYSTEM "rule.dtd">
<mycat:rule xmlns:mycat="http://org.opencloudb/"> 
 <tableRule name="rule_wyh">
    <rule>
      <columns>user_id</columns>
      <algorithm>func_4p</algorithm>
    </rule>
 </tableRule>

 <function name="func_4p" class="org.opencloudb.route.function.PartitionByLong">
    <property name="partitionCount">4</property>
    <property name="partitionLength">256</property>
 </function>
</mycat:rule>
6、配置server.xml檔案
         在server.xml檔案中的schemas屬性中新增test_sharding的schema。修改後的檔案如下所示:
<!DOCTYPE mycat:server SYSTEM "server.dtd">
<mycat:server xmlns:mycat="http://org.opencloudb/">
    <system>
        <property name="sequnceHandlerType">0</property> 
    </system>
    <user name="test">
       <property name="password">test</property>
       <property name="schemas">weixin,yixin,photo,test_sharding</property>
    </user>
</mycat:server>
7、水平切分測試
         重啟MyCAT,使用MySQL客戶端連線後,連線後可在test_sharding資料庫下看到t_user和t_user_class_rel表,
         在MySQL客戶端連線的MyCat的test_sharding資料庫的t_user表執行如下插入語句,插入user_id=1、255、256、511、512、1023、1024、50、300、1000的資料:注意insert into 必須帶上欄位名列表,不然報錯插不進去。
INSERT INTO  t_user( user_id  , receive_address  , create_time  , province_code  ) VALUES('1', '廣州市越秀區廣州大道中599號', '2014-07-17 10:53:15', 'GD');
INSERT INTO  t_user( user_id  , receive_address  , create_time  , province_code  ) VALUES('255', '廣州市越秀區廣州大道中599號', '2014-07-17 10:53:15', 'GD');
INSERT INTO  t_user( user_id  , receive_address  , create_time  , province_code  ) VALUES('256', '廣州市越秀區廣州大道中599號', '2014-07-17 10:53:15', 'GD');
INSERT INTO  t_user( user_id  , receive_address  , create_time  , province_code  ) VALUES('511', '廣州市越秀區廣州大道中599號', '2014-07-17 10:53:15', 'GD');
INSERT INTO  t_user( user_id  , receive_address  , create_time  , province_code  ) VALUES('512', '廣州市越秀區廣州大道中599號', '2014-07-17 10:53:15', 'GD');
INSERT INTO  t_user( user_id  , receive_address  , create_time  , province_code  ) VALUES('1023', '廣州市越秀區廣州大道中599號', '2014-07-17 10:53:15', 'GD');
INSERT INTO  t_user( user_id  , receive_address  , create_time  , province_code  ) VALUES('1024', '廣州市越秀區廣州大道中599號', '2014-07-17 10:53:15', 'GD');
INSERT INTO  t_user( user_id  , receive_address  , create_time  , province_code  ) VALUES('50', '廣州市越秀區廣州大道中599號', '2014-07-17 10:53:15', 'GD');
INSERT INTO  t_user( user_id  , receive_address  , create_time  , province_code  ) VALUES('300', '廣州市越秀區廣州大道中599號', '2014-07-17 10:53:15', 'GD');
INSERT INTO  t_user( user_id  , receive_address  , create_time  , province_code  ) VALUES('1000', '廣州市越秀區廣州大道中599號', '2014-07-17 10:53:15', 'GD');
        而後在MyCAT的test_sharding資料庫的t_user表執行select檢視記錄執行情況。進入localhost的user0~user3資料庫,檢視資料是否按照之前確定的rule1的規則寫入不同的資料庫。
        讀者可在test_sharding資料庫的t_user表執行update和delete等語句,並去分庫檢視執行結果,可得知MyCAT對MySQL客戶端基本透明,對程式也幾乎透明,在select語句執行時,MyCAT會自行去各個分庫按照規則獲取合併結果。
        接著測試按照ER關係策略分片的t_user_class_rel表是否按照user_id的分片策略,同樣user_id的資料分佈在同一個user庫的t_user表和t_user_class_rel表。
INSERT INTO `t_user_class_rel`( `id`  , `caller` , `province_code` , `user_id` , `class_id` , `role_type` , `create_time` , `modify_time`) VALUES ('257', 'eip', 'GD', '2', '35', '3', '2012-08-05 17:32:13', '2013-12-27 16:07:32');
INSERT INTO `t_user_class_rel`( `id`  , `caller` , `province_code` , `user_id` , `class_id` , `role_type` , `create_time` , `modify_time`) VALUES ('1', 'eip', 'GD', '257', '35', '3', '2012-08-05 17:32:13', '2013-12-27 16:07:32');
INSERT INTO `t_user_class_rel`( `id`  , `caller` , `province_code` , `user_id` , `class_id` , `role_type` , `create_time` , `modify_time`) VALUES ('2', 'eip', 'GD', '513', '35', '3', '2012-08-05 17:32:13', '2013-12-27 16:07:32');
INSERT INTO `t_user_class_rel`( `id`  , `caller` , `province_code` , `user_id` , `class_id` , `role_type` , `create_time` , `modify_time`) VALUES ('3', 'eip', 'GD', '769', '35', '3', '2012-08-05 17:32:13', '2013-12-27 16:07:32');
        而後在MyCAT的test_mycat資料庫的t_user_class_rel表執行select檢視記錄執行情況。進入localhost的user0~user3資料庫,檢視資料是否按照之前確定的rule1的規則和ER分片策略寫入不同的資料庫。
        分片join解決方案心得小結:如果一張表做分片了,其他有一張表要跟這張表做關聯,方案如下:
       (1)全域性表(適合做的才做):非跨分片join
       (2)另一張表也搞分片:非跨分片join
       (3)share join(只能2個表join):跨分片join
       (4)另一張表裡join用到的欄位冗餘到 已經做了分片的那張表上去:不用join    (該方案可用性不錯)
       (5)另一張表裡join用到的欄位 搞成一張全域性表:非跨分片join

8、最後使用壓力測試工具(參照我的部落格:http://blog.csdn.net/smooth00/article/details/67631669和http://blog.csdn.net/smooth00/article/details/67632264)大批量的往t_user中插入資料,以驗證水平切分的效能和穩定性。

2.3 讀寫分離測試

mycat提供了幾種請求負載均衡分發形式,通過定義dataHost標籤的balance屬性來修改
balance=0,不開啟讀寫分離機制,所有讀寫操作都發送到當前可用的writeHost上
balance=1,全部的readHost和stand by writeHost參與select語句的負載均衡,如雙主雙從模式,M2、S1、S2都分發請求
balance=2,所有讀操作隨機在writeHost和readHost上分發
balance=3,所有讀請求隨機的分發到writeHost上對應的readHost上執行,writeHost不承擔讀壓力。(balance=3只支援mycat1.4以上版本)

設定balance=1並將log4j修改為debug模式,觀察執行結果。

執行寫入操作,可以看出分發到writeHost

mysql> insert into test.t_user(id,area_name) values(2,'深圳市');
執行查詢操作,可以看出分發到readHost
mysql> select * from test.t_user where id = 2;

2.4 測試故障切換

mycat通過配置檔案schema.xml當中的dataHost標籤的writeType和switchType來定義故障切換方式。
writeType=0,表示所有寫都發送到配置的第一個writeHost上,第一個掛了切換到第二個writeHost上,重啟後以切換後的為準,切換記錄在conf/dnindex.properties
writeType=1,表示所有寫都隨機發送到配置的writeHost上,mycat1.5以上已廢棄不推薦。
switchType=-1,表示不自動切換
switchType=1,預設值,表示自動切換
switchType=2,基於MySQL主從同步的狀態決定是否切換,心跳語句為show slave status。
switchType=3,基於galaru cluster的切換機制(合適叢集),mycat1.4以上支援,心跳語句為show status like 'wsrep%'。

修改switchType=2,writeType=0,心跳語show slave status
關閉Master

shell> service mysql stop
通過mycat執行插入操作,發現請求分發到第二個writeHost上
mysql> insert into test.t_user values('3', '中山市');
Query OK, 1 row affected (0.04 sec)
重新啟動原Master
shell> service mysql start
檢視當前寫入節點,0表示第一個,1表示第二個
shell> more conf/dnindex.properties
#update
#Fri Mar 04 15:51:39 CST 2016
cluster1=1
通過mycat寫入資料
mysql> insert into test.user values(4,'珠海市');
Query OK, 1 row affected (0.04 sec)

mysql> select * from test.t_user where id = 4;
+----+-----------+
| id | name      |
+----+-----------+
|  4 | 珠海市     |
+----+-----------+
1 row in set (0.01 sec)
檢視主庫資料
mysql> select * from test.t_user;
+----+----------+
| id | name     |
+----+----------+
|  1 | 廣州市    |
|  2 | 深圳市    |
+----+----------+
2 rows in set (0.00 sec)
檢視從庫資料
mysql> select * from test.t_user;
+----+-----------+
| id | name      |
+----+-----------+
|  1 | 廣州市     |
|  2 | 深圳市     |
|  3 | 中山市     |
|  4 | 珠海市     |
+----+-----------+
4 rows in set (0.00 sec)

當原主庫恢復之後,mycat並沒有切換回第一個寫入節點,而是需要重新配置主從狀態。mycat的故障切換不會同步主從binlog差異資料,不能完整的保證資料一致性,而且沒有記錄切換後的writeHost提供服務時的binlog位置,其他從庫指定新主庫時,需要手工備份完整的資料,可考慮與MHA結合使用。

2.5 效能測試

1、基準測試驗證效能測試指令碼的增、刪、改、查,以及MyCat能力測試驗證各個節點DB的效能,確保效能無明顯差別2、壓力測試單節點和多節點對比測試高併發量測試長時間高負載測試

2.6 降低單節點效能

1、分片表單節點鎖定

(1)執行效能測試指令碼,並讓MyCat的多個節點其中一個節點在執行指令碼過程中做線上的ddl;

(2)執行效能測試指令碼,並讓MyCat的多個節點其中一個節點在執行指令碼過程中全表鎖定;

2、分片表索引摘除測試update影響
(1)修改指令碼(從insert改成以主鍵查詢update)。執行指令碼,讓MyCat的多個節點其中一個節點在執行指令碼過程中刪除主鍵索引(表是以主鍵作為分片id);
(2)修改指令碼(從insert改成以非主鍵(unique欄位)查詢update)。執行指令碼,讓MyCat的多個節點其中一個節點在執行指令碼過程中刪除非主鍵(unique)索引(表是以主鍵作為分片id)。