MySQL系列教程(五)

MyCAT


MyCat是基於阿里開源的Cobar產品而研發,Cobar的穩定性、可靠性、優秀的架構和性能以及衆多成熟的使用案例使得MYCAT一開始就擁有一個很好的起點,站在巨人的肩膀上,咱們能看到更遠。業界優秀的開源項目和創新思路被普遍融入到MYCAT的基因中,使得MYCAT在不少方面都領先於目前其餘一些同類的開源項目,甚至超越某些商業產品。html

MYCAT背後有一支強大的技術團隊,其參與者都是5年以上資深軟件工程師、架構師、DBA等,優秀的技術團隊保證了MYCAT的產品質量。
MYCAT並不依託於任何一個商業公司,所以不像某些開源項目,將一些重要的特性封閉在其商業產品中,使得開源項目成了一個擺設。
所以,MyCat你能夠認爲是從Amoeba->Cobar一路過來的最終版升級者。
因爲MyCat和Corba都是Amoeba框架上發展而來的,若是一個具備Amoeba配置經驗的開發者能夠幾乎不用看任何文檔而能夠直接使用MyCat來實現mySQL的讀寫分離更重要的是,基於myCat你能夠實現數據的垂直和水平切割,它使得了mySQL據有了真正的「集羣」的能力,併爲去IOE作好了最終的準備。

myCAT開源項目維護很頻繁,目前最新版已經到了1.5 Release(23天前剛維護過)。所以它的維護性、穩定性是獲得了保證的。同時myCat的文檔極其豐富,參於開發的人員又不少,因此它能夠應付不少在以往的Amoeba以及Corba上未能解決的問題,它是一個能夠真正被應用在生產環境上的數據庫中間件。在下面的章節咱們將使用myCat來實現mySQL的讀寫分離和垂直水平折分的具體案例。前端


myCat介紹


什麼是MyCAT?簡單的說,MyCAT就是:java

  •  一個完全開源的,面向企業應用開發的「大數據庫集羣」 支持事務、ACID、能夠替代Mysql的增強版數據庫
  • 一個能夠視爲「Mysql」集羣的企業級數據庫,用來替代昂貴的Oracle集羣 
  • 一個融合內存緩存技術、Nosql技術、HDFS大數據的新型SQL Server 
  • 結合傳統數據庫和新型分佈式數據倉庫的新一代企業級數據庫產品 
  • 一個新穎的數據庫中間件產品

目標

低成本的將現有的單機數據庫和應用平滑遷移到「雲」端,解決數據存儲和業務規模迅速增加狀況下的數據瓶頸問題。
關鍵特性
支持 SQL 92標準 支持Mysql集羣,能夠做爲Proxy使用 支持JDBC鏈接ORACLE、DB二、SQL Server,將其模擬爲MySQL Server使用 支持galera for mysql集羣,percona-cluster或者mariadb cluster,提供高可用性數據分片集羣,自動故障切換,高可用性 ,支持讀寫分離,支持Mysql雙主多從,以及一主多從的模式 ,支持全局表,數據自動分片到多個節點,用於高效表關聯查詢 ,支持獨有的基於E-R 關係的分片策略,實現了高效的表關聯查詢多平臺支持,部署和實施簡單。

優點

基於阿里開源的Cobar產品而研發,Cobar的穩定性、可靠性、優秀的架構和性能,以及衆多成熟的使用案例使得MyCAT一開始就擁有一個很好的起點,站在巨人的肩膀上,咱們能看到更遠。普遍吸收業界優秀的開源項目和創新思路,將其融入到MyCAT的基因中,使得MyCAT在不少方面都領先於目前其餘一些同類的開源項目,甚至超越某些商業產品。MyCAT背後有一隻強大的技術團隊,其參與者都是5年以上資深軟件工程師、架構師、DBA等,優秀的技術團隊保證了MyCAT的產品質量。 MyCAT並不依託於任何一個商業公司,所以不像某些開源項目,將一些重要的特性封閉在其商業產品中,使得開源項目成了一個擺設。


長期規劃

在支持Mysql的基礎上,後端增長更多的開源數據庫和商業數據庫的支持,包括原生支持PosteSQL、FireBird等開源數據庫,以及經過JDBC等方式間接支持其餘非開源的數據庫如Oracle、DB二、SQL Server等實現更爲智能的自我調節特性,如自動統計分析SQL,自動建立和調整索引,根據數據表的讀寫頻率,自動優化緩存和備份策略等實現更全面的監控管理功能與HDFS集成,提供SQL命令,將數據庫裝入HDFS中並可以快速分析集成優秀的開源報表工具,使之具有必定的數據分析的能力。

MyCat架構




安裝myCat


經過myCat的官方源碼網站得到(https://github.com/MyCATApache/Mycat-download)
本教程中使用的是myCat 8月出的穩定版:Mycat-server-1.5.1-RELEASE-20160810140521。




下載後解壓至一個目錄如:




 配置myCat


myCat的核心配置文件有以下幾個:



  • wrapper.conf – 系統環境配置
  • server.xml – mycat主配置文件,用於配置mycat對外數據庫表、用戶名、訪問權限等
  • schema.xml – 用於配置讀寫分離、水平垂直折分集羣
  • rule.xml – 用於配置數據水平垂直折分規則
  • router.xml – 配合rule.xml文件使用,當數據折分不符合規則時的走向,相似switch中的default做用

myCat配置讀寫分離


請看下面這組讀寫分離,咱們就用myCat來配置出這個實例吧。



先在物理上構建主從配置


先按照「Mysql主從配置」把:
  • 192.168.0.101 配成master
  • 192.168.0.102 爲 192.168.0.101的slave1
  • 192.168.0.103 爲 192.168.0.103的slave3

配置myCat讀寫分離


修改server.xml文件
打開server.xml文件,你會發覺好大一陀,所有刪了吧,改爲下面這個配置:
<?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="mk">
		<property name="password">aaaaaa</property>
		<property name="schemas">mycat</property>
		<property name="readOnly">false</property>
	</user>
</mycat:server>

上述配置定義了這麼一件事:
  • 定義了一個可供外部訪問的myCat的虛擬數據庫
  • 它的端口爲8806
  • schema名爲mycat
  • 客戶端訪問時的用戶名爲mk,密碼爲aaaaaa(六個a)

修改schema.xml文件

打開後也是很大的一陀,全刪了吧,改爲下面這一段:

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

<schema name="mycat" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"/>

	<dataNode name="dn1" dataHost="virtualHost" database="mk" />

	<dataHost name="virtualHost" maxCon="50" minCon="5" balance="3"
                writeType="0" dbType="mysql" dbDriver="native" switchType="1" >
                <heartbeat>select 1</heartbeat>
                <writeHost host="m1" url="192.168.0.101:3306" user="mk" password="aaaaaa">
			<readHost host="s1" url="192.168.0.102:3306" user="mk" password="aaaaaa" />
			<readHost host="s2" url="192.168.0.103:3306" user="mk" password="aaaaaa" />
                </writeHost>       
	</dataHost>

</mycat:schema>

這裏有三處須要注意:

balance="1"與writeType="0" ,switchType=」1」
balance 屬性負載均衡類型,目前的取值有 4 種:
  1. balance="0", 不開啓讀寫分離機制,全部讀操做都發送到當前可用的writeHost 上。
  2. balance="1",所有的 readHost 與 stand by writeHost 參與 select 語句的負載均衡,簡單的說,當雙主雙從模式(M1 ->S1 , M2->S2,而且 M1 與 M2 互爲主備),正常狀況下, M2,S1,S2 都參與 select 語句的負載均衡。
  3. balance="2",全部讀操做都隨機的在 writeHost、 readhost 上分發。
  4. balance="3", 全部讀請求隨機的分發到 wiriterHost 對應的 readhost 執行,writerHost 不負擔讀壓力,注意 balance=3 只在 1.4 及其之後版本有, 1.3 沒有。
writeType 屬性,負載均衡類型,目前的取值有 3 種
  1. writeType="0", 全部寫操做發送到配置的第一個 writeHost,第一個掛了切到還生存的第二個writeHost,從新啓動後已切換後的爲準,切換記錄在配置文件中:dnindex.properties .
  2. writeType="1",全部寫操做都隨機的發送到配置的 writeHost。
  3. writeType="2",沒實現。
switchType 屬性
  1. -1 表示不自動切換
  2. - 1 默認值,自動切換
  3. 2 基於MySQL 主從同步的狀態決定是否切換
<writeHost host="m1" url="192.168.0.101:3306" user="mk" password="aaaaaa">
<readHost host="s1" url="192.168.0.102:3306" user="mk" password="aaaaaa" />
<readHost host="s2" url="192.168.0.103:3306" user="mk" password="aaaaaa" />
</writeHost>

這裏是配置的咱們的myCat後臺鏈接的真實的 1主2從服務器以及它們的鏈接信息。

測試讀寫分離


1. 咱們把mycat啓動起來

./mycat start

看wrapper.log文件中的內容


看到mycat已經被啓動了。‘

2. 因而咱們打開一個mysql客戶端使用以下方式鏈接進入mycat


3. 咱們往mycat裏插入4條數據

insert into user_info(user_name)values(@@hostname);
insert into user_info(user_name)values(@@hostname);
insert into user_info(user_name)values(@@hostname);
insert into user_info(user_name)values(@@hostname);
commit;

單獨連上ymklinux和ymklinux2以及ymklinux3分別做





咱們能夠看到,因爲102,103爲101的slaver因此當下面這種模式:

<writeHost host="m1" url="192.168.0.101:3306" user="mk" password="aaaaaa">
<readHost host="s1" url="192.168.0.102:3306" user="mk" password="aaaaaa" />
<readHost host="s2" url="192.168.0.103:3306" user="mk" password="aaaaaa" />
</writeHost>

writeHost爲192.168.0.101(Master)被寫入數據時,而又由於192.168.0.101與102, 103爲Master-Slaver的關係所以102與103會自動同步192.168.0.101上被insert進入的數據。以下圖演示那樣。




考慮多Master的場景


咱們回到上棕這個場景來看,爲何説須要多Master?

<writeHost host="m1" url="192.168.0.101:3306" user="mk" password="aaaaaa">
<readHost host="s1" url="192.168.0.102:3306" user="mk" password="aaaaaa" />
<readHost host="s2" url="192.168.0.103:3306" user="mk" password="aaaaaa" />
</writeHost>

上述這個應用,有一個非致命的缺點:
即定義的writeHost一旦發生宕機那麼其相應對的readHost所有爲不可用,換而言之即整個myCat羣宕機。

熊掌與魚兼得法


按照myCat的配置咱們能夠配置多個writeHost並把writeType這個值設爲「0」。
來看writeType=」0」的含議:
writeType="0", 全部寫操做發送到配置的第一個 writeHost,第一個掛了切到還生存的第二個writeHost,從新啓動後已切換後的爲準,切換記錄在配置文件中:dnindex.properties .

第一種配置:

因而咱們來考慮下面這樣的配置:

<writeHost host="m1" url="192.168.0.101:3306" user="root" password="aaaaaa">
</writeHost>
<writeHost host="m2" url="192.168.0.104:3306" user="root" password="aaaaaa"/>

MyCat羣會在Master1發生宕機時自動探尋Master2是否還存活,若是Master2存活那麼把數據的讀和寫所有轉向以Master2爲表明的讀寫羣。
可是,這也帶來了一個問題。
若是此時Master1宕機了,Master2被頂了上來,那麼數據所有跑入了Master2羣內了。
當:
Master1再次恢復時。。。對於經過myCat客戶端調用者來説這一切是秀明的,數據依然仍是那些數據,但是此時,再來一次Master2宕機(剛纔是Master1宕機),此時myCat會把Master1做爲讀寫羣。
因而,客戶端再次經過myCat代理調用後,會發覺數據有差別了。

爲何?

由於Master2內的數據和Master1內的數據沒有同步。


第二種配置:

因而:

第一步:

咱們保留第一種配置即

<writeHost host="m1" url="192.168.0.101:3306" user="root" password="aaaaaa">
</writeHost>
<writeHost host="m2" url="192.168.0.104:3306" user="root" password="aaaaaa"/>

第二步:

咱們把Master1和Master2在物理上作成MySQL的主-備結構,如今來看場景推演。
Master1發生宕機,Master2頂上同時同步了Master1宕機前的數據,對於myCat用戶羣來説數據everything is ok。
Master1恢復,Master2宕機客戶端發覺經過myCat獲得的數據(來自於Master1)有異常,爲何?
由於Master2是Master1的Slaver,所以它只會「正向同步Master1的數據」,而不能逆向,所以這種配置和第一種配置徹底沒有區別。
那麼有人説咱們把Master1做成Master2的Slaver呢?那無非上第二種狀況的場景推演倒一倒而己,仍是不能保證任何一點宕機而且在恢復後如何保證數據的強一致性。

第三種配置:

因而,咱們想到了一個辦法,這個辦法來源於Master和Slaver在做配置時my.cnf配置文件 中的一個參數,它就是「log-slave-update」。
對於這個參數的解釋,mySQL官方是以下解釋的:當你的Master同時又是其它Master’的slaver 時,你須要設置此參數。
單從上述描述來看這段語句念起來有些晦澀,咱們仍是實際來看一個架構圖吧。




這種結構被稱爲「雙主結構」或者又稱爲「互爲主備」結構。

  • Master1是Master2的Slaver;
  • Master2是Master1的Slaver;
  • 同時,他們又是彼此下面如:Master1拖的S1, S2的Master

基於互爲主備結構的myCat羣搭建方法


第一步(必定要設log-slave-update)


把Master1做成Master2的Slaver,在Master1配置文件my.cnf中設置log-slave-update。
若是不設會發生下面這種狀況
1) 經過Master2 insert數據
2) 在Master1上查看,數據被從Master2上同步過來了
3) 經過Master1下掛載的它自己的幾個Slaver連入並進行查看,結果發覺沒有同步Master1的數據
4) 經過Master1 insert數據
5) 在Master2上查看,數據被從Master1上同步過來了
6) 經過Master2下掛載的它自己的幾個Slaver連入並進行查看,結果發覺沒有同步Master2的數據





第二步



把Master2做成Master1的Slaver,在Master2配置文件my.cnf中設置log-slave-update。
對於Master1或者是Master2下再外掛的其自己的s1,s2上不須要再設log-slave-update這個開關了。
若是Master1或者是Master2自己下面還有掛Slaver,請記得此時在其自己的Slaver1上再同步一下它們與Master之間的bin-log。
由於Master一旦做成了另外一個Master的Slaver,所以它的bin-log也改變了,你能夠直接在其自己的Slaver上使用change master命令。

第三步


在myCat的配件文件中做以下設置:

<dataHost name="virtualHost" maxCon="50" minCon="5" balance="3"
       writeType="0" dbType="mysql" dbDriver="native" switchType="2"  slaveThreshold="100">
   <heartbeat>show slave status</heartbeat>
   <writeHost host="m1" url="192.168.0.101:3306" user="root" password="aaaaaa">
         <readHost host="s1" url="192.168.0.102:3306" user="root" password="aaaaaa"/>
          <readHost host="s2" url="192.168.0.103:3306" user="root" password="aaaaaa"/>
   </writeHost>
   <writeHost host="m2" url="192.168.0.104:3306" user="root" password="aaaaaa"/>
</dataHost>

因而,咱們就有了這樣的結構了



測試


1) 咱們連上myCat,對myCat羣發送2條insert語句
2) 咱們單獨連上m1, s1, s2, m2查看,發覺各個mysql實例中的數據一致
3) 直接在m1上執行service mysqld stop
4) 再次連上myCat羣,對myCat羣再發送2條insert語句
5) 咱們單獨連上m2進行查看,如今m2上爲4條數據
6) 咱們分別連上s1和s2進行查看,發現s1和s2上仍是2條數據
7) 咱們把m1的mysql實例從新啓動起來使用命令service mysqld start
8) 單獨連上m1進行查看,發覺m1此時數據條數據爲4條
9)咱們分別連上s1和s2進行查看,發現s1和s2上已經從原來的2條數據變爲了4條數據

一個高可用的讀寫分離的集羣就此搭建完畢,絕逼完美!


mySQL的垂直與水平折分


在本書系統的《mySQL集羣(cluster)》中,咱們詳細介紹和對比了幾種集羣的方案,而且在該章節中我詳細描述過使用mySQL的分片原則是能夠取代集羣方案的。而且它比起集羣方案來説擁有着更多的優勢:
  • 廉價,軟件免費而且可使用廉價PC
  • 能夠大規模鋪設不受License的束縛
  • 穩定可監控
  • 對於已有應用程序來説,它是透明的

什麼是分片


通常的replication具備一個限制,即一旦數據庫過於龐大,尤爲是當寫入過於頻繁,很難由一臺主機支撐的時候,咱們仍是會面臨到擴展瓶頸。
所謂數據分片(sharding)便是經過某種特定的條件,將咱們存放在同一個數據庫中的數據分散存放到多個數據庫(主機)上面,以達到分散單臺設備負載的效果。。數據的切分同時還能夠提升系統的整體可用性,由於單臺設備Crash以後,只有整體數據的某部分不可用,而不是全部的數據。

數據的切分(Sharding)模式

一種是按照不一樣的表(或者Schema)來切分到不一樣的數據庫(主機)之上,這種切能夠稱之爲數據的垂直(縱向)切分;另一種則是根據表中的數據的邏輯關係,將同一個表中的數據按照某種條件拆分到多臺數據庫(主機)上面,這種切分稱之爲數據的水平(橫向)切分。

垂直切分


一個架構設計較好的應用系統,其整體功能確定是由不少個功能模塊所組成的,而每個功能模塊所須要的數據對應到數據庫中就是一個或者多個表。而在架構設計中,各個功能模塊相互之間的交互點越統一越少,系統的耦合度就越低,系統各個模塊的維護性以及擴展性也就越好。這樣的系統,實現數據的垂直切分也就越容易。
通常來講,若是是一個負載相對不是很大的系統,並且表關聯又很是的頻繁,那可能數據庫讓步,將幾個相關模塊合併在一塊兒減小應用程序的工做的方案能夠減小較多的工做量,這是一個可行的方案。一個垂直拆分的例子:

1.用戶模塊表:user,user_profile,user_group,user_photo_album
2.羣組討論表:groups,group_message,group_message_content,top_message
3.相冊相關表:photo,photo_album,photo_album_relation,photo_comment
4.事件信息表:event
羣組討論模塊和用戶模塊之間主要存在經過用戶或者是羣組關係來進行關聯。通常關聯的時候都會是經過用戶的id或者nick_name以及group的id來進行關聯,經過模塊之間的接口實現不會帶來太多麻煩;
相冊模塊僅僅與用戶模塊存在經過用戶的關聯。這兩個模塊之間的關聯基本就有經過用戶id關聯的內容,簡單清晰,接口明確;
事件模塊與各個模塊可能都有關聯,可是都只關注其各個模塊中對象的ID信息,一樣能夠作到很容易分拆。

垂直切分的優勢
  • 數據庫的拆分簡單明瞭,拆分規則明確;
  • 應用程序模塊清晰明確,整合容易;
  • 數據維護方便易行,容易定位;
垂直切分的缺點

  • 部分表關聯沒法在數據庫級別完成,須要在程序中完成;
  • 對於訪問極其頻繁且數據量超大的表仍然存在性能瓶頸,不必定能知足要求;
  • 事務處理相對更爲複雜;
  • 切分達到必定程度以後,擴展性會遇到限制;
  • 過分切分可能會帶來系統過渡複雜而難以維護。

水平切分


將某個訪問極其頻繁的表再按照某個字段的某種規則來分散到多個表之中,每一個表中包含一部分數據。
對於上面的例子:全部數據都是和用戶關聯的,那麼咱們就能夠根據用戶來進行水平拆分,將不一樣用戶的數據切分到不一樣的數據庫中。
如今互聯網很是火爆的Web2.0類型的網站,基本上大部分數據都可以經過會員用戶信息關聯上,可能不少核心表都很是適合經過會員ID來進行數據的水平切分。而像論壇社區討論系統,就更容易切分了,很是容易按照論壇編號來進行數據的水平切分。切分以後基本上不會出現各個庫之間的交互。

水平切分的優勢

表關聯基本可以在數據庫端所有完成;
不會存在某些超大型數據量和高負載的表遇到瓶頸的問題;
應用程序端總體架構改動相對較少;
事務處理相對簡單;
只要切分規則可以定義好,基本上較難遇到擴展性限制;

水平切分的缺點

切分規則相對更爲複雜,很難抽象出一個可以知足整個數據庫的切分規則;
後期數據的維護難度有所增長,人爲手工定位數據更困難;
應用系統各模塊耦合度較高,可能會對後面數據的遷移拆分形成必定的困難。


兩種切分結合用


通常來講,咱們數據庫中的全部表很難經過某一個(或少數幾個)字段所有關聯起來,因此很難簡單的僅僅經過數據的水平切分來解決全部問題。而垂直切分也只能解決部分問題,對於那些負載很是高的系統,即便僅僅只是單個表都沒法經過單臺數據庫主機來承擔其負載。咱們必須結合「垂直」和「水平」兩種切分方式同時使用
每個應用系統的負載都是一步一步增加上來的,在開始遇到性能瓶頸的時候,大多數架構師和DBA都會選擇先進行數據的垂直拆分,由於這樣的成本最早,最符合這個時期所追求的最大投入產出比。然而,隨着業務的不斷擴張,系統負載的持續增加,在系統穩定一段時期以後,通過了垂直拆分以後的數據庫集羣可能又再一次不堪重負,遇到了性能瓶頸。
若是咱們再一次像最開始那樣繼續細分模塊,進行數據的垂直切分,那咱們可能在不久的未來,又會遇到如今所面對的一樣的問題。並且隨着模塊的不斷的細化,應用系統的架構也會愈來愈複雜,整個系統極可能會出現失控的局面。
這時候咱們就必需要經過數據的水平切分的優點,來解決這裏所遇到的問題。並且,咱們徹底沒必要要在使用數據水平切分的時候,推倒以前進行數據垂直切分的成果,而是在其基礎上利用水平切分的優點來避開垂直切分的弊端,解決系統複雜性不斷擴大的問題。而水平拆分的弊端(規則難以統一)也已經被以前的垂直切分解決掉了,讓水平拆分能夠進行的駕輕就熟。
node


myCat實現數據分片






在數據切分處理中,特別是水平切分中,中間件最終要的兩個處理過程就是數據的切分、數據的聚合。
選擇合適的切分規則,相當重要,由於它決定了後續數據聚合的難易程度,甚至能夠避免跨庫的數據聚合
處理。


myCAT數據分片拆分原則:
mysql

  • 避免或減小跨庫join。
  • 選擇最合適的拆分維度。
Mycat拆分表解決方案:
  • MYCAT 全局表
  • ER關係
  • 表拆分
              --拆分維度
              --主鍵分片vs 非主鍵分片

myCAT對於後端多個mySQL的數據折分和折分後的聚合,對於myCAT前端調用來説是透明的。
linux


以實際例子來操做分片

在分片前,咱們須要事先了解一個重要的知識點,也算是一個須要引發重視的問題。
A和B互爲主從,咱們按照userID來實施分片,userID爲1,3,5的插入到一臺mysql實例,userID爲2,4,6的插入到另外一個mysql實例中去。而後在mycat前端select時mycat會自動進行聚合,即它會從2個mysql實例中選擇數據併合併成1,2,3,4,5,6…這樣的數據顯示給調用的客戶端。

git

1. 先往A上插入 一條數據(id:1 name: tom, gender: m),因而B上也立刻會REPLI(同步)一條數據,所以你在A和B上作SELECT查詢,均可以看到兩邊記錄同樣github

2. 往B上插入 一條數據(id:2 name: tom, gender: m),因而A上也立刻會REPLI一條數據,所以你在A和B上作SELECT查詢,均可以看到兩邊記錄同樣算法

3. 使用MYCAT讀寫分離,A爲寫,B爲讀,一切OKsql

4. 使用MYCAT作水平拆分,拆分規則爲(id % 2 case 0 then Server A; case 1 then Server B;),因而,規則開始起效。

  • 當插入id: 2的數據時,它會被insert到A上,接下去因爲A和B互爲主備,所以B立刻會同步一條數據。
  • 接下去再插入一條id:3的數據,因爲id爲3的數據根據咱們預先配置後的規則會被插入到Server B上, 而後A立刻也會被REPLI一條數據id爲3,此時, 此時A和B兩個MYSQL上就都有了一樣的記錄集,都爲1,2,3,即:
Server A Server B
1 1
2 2
3 3
因此數據分片等於沒用。再來看分片讀,讀的規則也走上述規則,因而當你經過mycat做爲mysql proxy時你會發生這樣的場景:

Mysql –u root –p aaaaaa –P8066 –h 192.168.0.1

Select * from person
此時你獲得的結果集爲:

1
2
3
1
2
3
或者你的ReadServer始終爲一臺,那你會獲得正確的1,2,3這樣的結果集。可是,原先所須要的數據的sharding功能。。。其實沒有作到。

myCAT分片前置條件(極其重要)


所以:

若是要考慮後期的水平和垂直折分,被折分的數據庫不能夠在其自己已經作了主從結構,而是須要把這個主從結構交由mycat來作。


 實際分片演示

咱們只須要修改rule.xml這個文件便可實現myCat的數據分片定義了。
可是按照前面小節的重要提示,在作分片前,咱們必定要讓咱們參於分片規則的mySQL實例保持獨立運行狀態,即多個mySQL實例間不要作任何的「主備」結構。

實驗前準備

咱們拿192.168.0.101與192.168.0.102兩臺機器來作分片實驗

在192.168.0.101上禁用binlog,關閉master slaver的關聯,同時斷開與原有192.168.0.104的master slaver關聯。

別忘了把「log-slave-update」也註釋掉。


重啓後登陸192.168.0.101,輸入如下命令


stop slave;
change master to master_host=' ';

在192.168.0.104上禁用binlog,關閉master slaver的關聯,同時斷開與原有192.168.0.101的master slaver關聯。




別忘了把「log-slave-update」也註釋掉。


重啓後登陸192.168.0.104,輸入如下命令


stop slave;
change master to master_host=' ';

配置數據分片

咱們有一張T_PERSON表,結構以下:

CREATE TABLE `t_person` (
  `person_id` int(11) NOT NULL,
  `person_name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`person_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
show slave status;

數據分片按照PERSON_ID的奇偶來分。

配置mycat的rule.xml文件


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:rule SYSTEM "rule.dtd">
<mycat:rule xmlns:mycat="http://org.opencloudb/">
	<tableRule name="mod-long">
		<rule>
			<columns>person_id</columns>
			<algorithm>mod-long</algorithm>
		</rule>
	</tableRule>
		<function name="mod-long" class="org.opencloudb.route.function.PartitionByMod">
		<!-- how many data nodes -->
		<property name="count">2</property>
	</function>
</mycat:rule>


經過配置能夠看到咱們按照person_id會把該表數據分在2個物理庫內,分片數據包括經過mycat的mysql client端insert時自動分片以及select時自動聚合。

應用分片

rule.xml文件定義後還須要應用它裏面的規則,纔可以最終讓數據分片在mycat中起效,爲此,咱們更改schema.xml文件,増加以下一段配置:


<schema name="split" checkSQLschema="false" sqlMaxLimit="100">
		 <table name="t_person" dataNode="splitNode1,splitNode2" rule="mod-long"/>
</schema>
<dataNode name="splitNode1" dataHost="host1" database="mk" />
<dataNode name="splitNode2" dataHost="host2" database="mk" />
<dataHost name="host1" maxCon="20" minCon="5" balance="0"
    writeType="0" dbType="mysql" dbDriver="native" switchType="1" >
    <heartbeat>select 1</heartbeat>
    <writeHost host="m1" url="192.168.0.101:3306" user="mk" password="aaaaaa"/>
</dataHost>
<dataHost name="host2" maxCon="20" minCon="5" balance="0"
    writeType="0" dbType="mysql" dbDriver="native" switchType="1" >
    <heartbeat>select 1</heartbeat>
    <writeHost host="s1" url="192.168.0.104:3306" user="mk" password="aaaaaa"/>
</dataHost>

接着咱們更改server.xml文件,増加以下一段配置:


	<user name="split">
		<property name="password">aaaaaa</property>
		<property name="schemas">split</property>
		<property name="readOnly">false</property>
	</user>


測試分片

咱們使用split/aaaaaa用戶連上mycat實例




咱們插入5條數據


insert into t_person(person_id,person_name)values('1','michael');
insert into t_person(person_id,person_name)values('2','tom');
insert into t_person(person_id,person_name)values('3','tonny');
insert into t_person(person_id,person_name)values('4','marry');
insert into t_person(person_id,person_name)values('5','jack');
commit;

而後咱們接着作一次查詢




連入192.168.0.101上進行查看,咱們能夠獲得2條數據,person_id爲偶數倍。



連入192.168.0.104上進行查看,咱們能夠獲得3條數據,person_id爲奇數倍。




分片成功!!!


myCAT的分片功能至關的強大,徹底能夠應付億萬級的數據,若是再結合適當的讀寫分離機制是徹底可讓你的網站飛起來的。
myCAT的分片規則還有不少,文後的附件中會給出一個myCAT分片規則大全。


附件 myCAT分片規則大全

經常使用的根據主鍵或非主鍵的分片規則配置:


1. 枚舉法


經過在配置文件中配置可能的枚舉id,本身配置分片,使用規則:'


<tableRule name="sharding-by-intfile">
    <rule>
      <columns>user_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>

partition-hash-int.txt 配置:
10000=0
10010=1
DEFAULT_NODE=1

上面columns 標識將要分片的表字段,algorithm 分片函數,
其中分片函數配置中,mapFile標識配置文件名稱,type默認值爲0,0表示Integer,非零表示String,
全部的節點配置都是從0開始,及0表明節點1


/**
*  defaultNode 默認節點:小於0表示不設置默認節點,大於等於0表示設置默認節點
* 
默認節點的做用:枚舉分片時,若是碰到不識別的枚舉值,就讓它路由到默認節點
*                若是不配置默認節點(defaultNode值小於0表示不配置默認節點),碰到
*                不識別的枚舉值就會報錯,
*                like this:can't find datanode for sharding column:column_name val:ffffffff    
*/

2. 固定分片hash算法

<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">2,1</property>
    <property name="partitionLength">256,512</property>
  </function>


配置說明:
上面columns 標識將要分片的表字段,algorithm 分片函數,
partitionCount 分片個數列表,partitionLength 分片範圍列表
分區長度:默認爲最大2^n=1024 ,即最大支持1024分區
約束 :
count,length兩個數組的長度必須是一致的。
1024 = sum((count[i]*length[i])). count和length兩個向量的點積恆等於1024
用法例子:
本例的分區策略:但願將數據水平分紅3份,前兩份各佔25%,第三份佔50%。(故本例非均勻分區)


        // |<---------------------1024------------------------>|
        // |<----256--->|<----256--->|<----------512---------->|
        // | partition0 | partition1 | partition2 |
        // | 共2份,故count[0]=2 | 共1份,故count[1]=1 |
        int[] count = new int[] { 2, 1 };
        int[] length = new int[] { 256, 512 };
        PartitionUtil pu = new PartitionUtil(count, length);

        // 下面代碼演示分別以offerId字段或memberId字段根據上述分區策略拆分的分配結果
        int DEFAULT_STR_HEAD_LEN = 8; // cobar默認會配置爲此值
        long offerId = 12345;
        String memberId = "qiushuo";

        // 若根據offerId分配,partNo1將等於0,即按照上述分區策略,offerId爲12345時將會被分配到partition0中
        int partNo1 = pu.partition(offerId);

        // 若根據memberId分配,partNo2將等於2,即按照上述分區策略,memberId爲qiushuo時將會被分到partition2中
        int partNo2 = pu.partition(memberId, 0, DEFAULT_STR_HEAD_LEN);

若是須要平均分配設置:平均分爲4分片,partitionCount*partitionLength=1024



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

3. 範圍約定

<tableRule name="auto-sharding-long">
    <rule>
      <columns>user_id</columns>
      <algorithm>rang-long</algorithm>
    </rule>
  </tableRule>
<function name="rang-long" class="org.opencloudb.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
或
0-10000000=0
10000001-20000000=1

配置說明:
上面columns 標識將要分片的表字段,algorithm 分片函數,
rang-long 函數中mapFile表明配置文件路徑
全部的節點配置都是從0開始,及0表明節點1,此配置很是簡單,即預先制定可能的id範圍到某個分片


4. 求模法

<tableRule name="mod-long">
    <rule>
      <columns>user_id</columns>
      <algorithm>mod-long</algorithm>
    </rule>
  </tableRule>
  <function name="mod-long" class="org.opencloudb.route.function.PartitionByMod">
   <!-- how many data nodes  -->
    <property name="count">3</property>
  </function> 

配置說明:
上面columns 標識將要分片的表字段,algorithm 分片函數,
此種配置很是明確即根據id進行十進制求模預算,相比方式1,此種在批量插入時須要切換數據源,id不連續


5. 日期列分區法


<tableRule name="sharding-by-date">
      <rule>
        <columns>create_time</columns>
        <algorithm>sharding-by-date</algorithm>
      </rule>
   </tableRule>  
<function name="sharding-by-date" class="org.opencloudb.route.function.PartitionByDate">
    <property name="dateFormat">yyyy-MM-dd</property>
    <property name="sBeginDate">2014-01-01</property>
    <property name="sPartionDay">10</property>
</function>
配置說明:
上面columns 標識將要分片的表字段,algorithm 分片函數,
配置中配置了開始日期,分區天數,即默認從開始日期算起,分隔10天一個分區




Assert.assertEquals(true, 0 == partition.calculate("2014-01-01"));
Assert.assertEquals(true, 0 == partition.calculate("2014-01-10"));
Assert.assertEquals(true, 1 == partition.calculate("2014-01-11"));
Assert.assertEquals(true, 12 == partition.calculate("2014-05-01"));


6. 通配取模


<tableRule name="sharding-by-pattern">
      <rule>
        <columns>user_id</columns>
        <algorithm>sharding-by-pattern</algorithm>
      </rule>
   </tableRule>
<function name="sharding-by-pattern" class="org.opencloudb.route.function.PartitionByPattern">
    <property name="patternValue">256</property>
    <property name="defaultNode">2</property>
    <property name="mapFile">partition-pattern.txt</property>

</function>

partition-pattern.txt 
# id partition range start-end ,data node index
###### first host configuration
1-32=0
33-64=1
65-96=2
97-128=3
######## second host configuration
129-160=4
161-192=5
193-224=6
225-256=7
0-0=7
配置說明:
上面columns 標識將要分片的表字段,algorithm 分片函數,patternValue 即求模基數,defaoultNode 默認節點,若是配置了默認,則不會按照求模運算
mapFile 配置文件路徑
配置文件中,1-32 即表明id%256後分布的範圍,若是在1-32則在分區1,其餘類推,若是id非數據,則會分配在defaoultNode 默認節點


String idVal = "0";
Assert.assertEquals(true, 7 == autoPartition.calculate(idVal));
idVal = "45a";
Assert.assertEquals(true, 2 == autoPartition.calculate(idVal));


7.  ASCII碼求模通配

<tableRule name="sharding-by-prefixpattern">
      <rule>
        <columns>user_id</columns>
        <algorithm>sharding-by-prefixpattern</algorithm>
      </rule>
   </tableRule>
<function name="sharding-by-pattern" class="org.opencloudb.route.function.PartitionByPattern">
    <property name="patternValue">256</property>
    <property name="prefixLength">5</property>
    <property name="mapFile">partition-pattern.txt</property>

  </function>

partition-pattern.txt

# range start-end ,data node index
# ASCII
# 48-57=0-9
# 6四、65-90=@、A-Z
# 97-122=a-z
###### first host configuration
1-4=0
5-8=1
9-12=2
13-16=3
###### second host configuration
17-20=4
21-24=5
25-28=6
29-32=7
0-0=7
配置說明:
上面columns 標識將要分片的表字段,algorithm 分片函數,patternValue 即求模基數,prefixLength ASCII 截取的位數
mapFile 配置文件路徑
配置文件中,1-32 即表明id%256後分布的範圍,若是在1-32則在分區1,其餘類推 

此種方式相似方式6只不過採起的是將列種獲取前prefixLength位列全部ASCII碼的和進行求模sum%patternValue ,獲取的值,在通配範圍內的
即 分片數,
/**
* ASCII編碼:
* 48-57=0-9阿拉伯數字
* 6四、65-90=@、A-Z
* 97-122=a-z
*
*/
如 

String idVal="gf89f9a";
Assert.assertEquals(true, 0==autoPartition.calculate(idVal));

idVal="8df99a";
Assert.assertEquals(true, 4==autoPartition.calculate(idVal));

idVal="8dhdf99a";
Assert.assertEquals(true, 3==autoPartition.calculate(idVal));

8. 編程指定


<tableRule name="sharding-by-substring">
      <rule>
        <columns>user_id</columns>
        <algorithm>sharding-by-substring</algorithm>
      </rule>
   </tableRule>
<function name="sharding-by-substring" class="org.opencloudb.route.function.PartitionDirectBySubString">
    <property name="startIndex">0</property> <!-- zero-based -->
    <property name="size">2</property>
    <property name="partitionCount">8</property>
    <property name="defaultPartition">0</property>
  </function>
配置說明:
上面columns 標識將要分片的表字段,algorithm 分片函數 
此方法爲直接根據字符子串(必須是數字)計算分區號(由應用傳遞參數,顯式指定分區號)。
例如id=05-100000002
在此配置中表明根據id中從startIndex=0,開始,截取siz=2位數字即05,05就是獲取的分區,若是沒傳默認分配到defaultPartition

9. 字符串拆分hash解析

<tableRule name="sharding-by-stringhash">
      <rule>
        <columns>user_id</columns>
        <algorithm>sharding-by-stringhash</algorithm>
      </rule>
   </tableRule>
<function name="sharding-by-substring" class="org.opencloudb.route.function.PartitionDirectBySubString">
    <property name=length>512</property> <!-- zero-based -->
    <property name="count">2</property>
    <property name="hashSlice">0:2</property>
  </function>
配置說明:
上面columns 標識將要分片的表字段,algorithm 分片函數 
函數中length表明字符串hash求模基數,count分區數,hashSlice hash預算位

即根據子字符串 hash運算

	

hashSlice : 0 means str.length(), -1 means str.length()-1

/**
     * "2" -> (0,2)<br/>
     * "1:2" -> (1,2)<br/>
     * "1:" -> (1,0)<br/>
     * "-1:" -> (-1,0)<br/>
     * ":-1" -> (0,-1)<br/>
     * ":" -> (0,0)<br/>
     */

例子:


String idVal=null;
 rule.setPartitionLength("512");
 rule.setPartitionCount("2");
 rule.init();
 rule.setHashSlice("0:2");
//		idVal = "0";
//		Assert.assertEquals(true, 0 == rule.calculate(idVal));
//		idVal = "45a";
//		Assert.assertEquals(true, 1 == rule.calculate(idVal));

 
 
 //last 4
 rule = new PartitionByString();
 rule.setPartitionLength("512");
 rule.setPartitionCount("2");
 rule.init();
 //last 4 characters
 rule.setHashSlice("-4:0");
 idVal = "aaaabbb0000";
 Assert.assertEquals(true, 0 == rule.calculate(idVal));
 idVal = "aaaabbb2359";
 Assert.assertEquals(true, 0 == rule.calculate(idVal));

10. 一致性hash


<tableRule name="sharding-by-murmur">
      <rule>
        <columns>user_id</columns>
        <algorithm>murmur</algorithm>
      </rule>
   </tableRule>
<function name="murmur" class="org.opencloudb.route.function.PartitionByMurmurHash">
      <property name="seed">0</property><!-- 默認是0-->
      <property name="count">2</property><!-- 要分片的數據庫節點數量,必須指定,不然無法分片-->
      <property name="virtualBucketTimes">160</property><!-- 一個實際的數據庫節點被映射爲這麼多虛擬節點,默認是160倍,也就是虛擬節點數是物理節點數的160倍-->
      <!--
      <property name="weightMapFile">weightMapFile</property>
                     節點的權重,沒有指定權重的節點默認是1。以properties文件的格式填寫,以從0開始到count-1的整數值也就是節點索引爲key,以節點權重值爲值。全部權重值必須是正整數,不然以1代替 -->
      <!--
      <property name="bucketMapPath">/etc/mycat/bucketMapPath</property>
                      用於測試時觀察各物理節點與虛擬節點的分佈狀況,若是指定了這個屬性,會把虛擬節點的murmur hash值與物理節點的映射按行輸出到這個文件,沒有默認值,若是不指定,就不會輸出任何東西 -->
  </function>


一致性hash預算有效解決了分佈式數據的擴容問題,前1-9中id規則都多少存在數據擴容難題,而10規則解決了數據擴容難點
關於一致性hash詳細:


一致性哈希算法在1997年由麻省理工學院提出的一種分佈式哈希(DHT)實現算法,設計目標是爲了解決因特網中的熱點(Hot spot)問題,初衷和CARP十分相似。一致性哈希修正了CARP使用的簡 單哈希算法帶來的問題,使得分佈式哈希(DHT)能夠在P2P環境中真正獲得應用。 
一致性hash算法提出了在動態變化的Cache環境中,斷定哈希算法好壞的四個定義:
一、平衡性(Balance):平衡性是指哈希的結果可以儘量分佈到全部的緩衝中去,這樣可使得全部的緩衝空間都獲得利用。不少哈希算法都可以知足這一條件。
二、單調性(Monotonicity):單調性是指若是已經有一些內容經過哈希分派到了相應的緩衝中,又有新的緩衝加入到系統中。哈希的結果應可以保證原有已分配的內容能夠被映射到原有的或者新的緩衝中去,而不會被映射到舊的緩衝集合中的其餘緩衝區。 
三、分散性(Spread):在分佈式環境中,終端有可能看不到全部的緩衝,而是隻能看到其中的一部分。當終端但願經過哈希過程將內容映射到緩衝上時,因爲不一樣終端所見的緩衝範圍有可能不一樣,從而致使哈希的結果不一致,最終的結果是相同的內容被不一樣的終端映射到不一樣的緩衝區中。這種狀況顯然是應該避免的,由於它致使相同內容被存儲到不一樣緩衝中去,下降了系統存儲的效率。分散性的定義就是上述狀況發生的嚴重程度。好的哈希算法應可以儘可能避免不一致的狀況發生,也就是儘可能下降分散性。 
四、負載(Load):負載問題其實是從另外一個角度看待分散性問題。既然不一樣的終端可能將相同的內容映射到不一樣的緩衝區中,那麼對於一個特定的緩衝區而言,也可能被不一樣的用戶映射爲不一樣 的內容。與分散性同樣,這種狀況也是應當避免的,所以好的哈希算法應可以儘可能下降緩衝的負荷。
在分佈式集羣中,對機器的添加刪除,或者機器故障後自動脫離集羣這些操做是分佈式集羣管理最基本的功能。若是採用經常使用的hash(object)%N算法,那麼在有機器添加或者刪除後,不少原有的數據就沒法找到了,這樣嚴重的違反了單調性原則。接下來主要講解一下一致性哈希算法是如何設計的:
環形Hash空間
按照經常使用的hash算法來將對應的key哈希到一個具備2^32次方個桶的空間中,即0~(2^32)-1的數字空間中。如今咱們能夠將這些數字頭尾相連,想象成一個閉合的環形。以下圖




把數據經過必定的hash算法處理後映射到環上
如今咱們將object一、object二、object三、object4四個對象經過特定的Hash函數計算出對
應的key值,而後散列到Hash環上。以下圖:
    Hash(object1) = key1;
    Hash(object2) = key2;
    Hash(object3) = key3;
    Hash(object4) = key4;



將機器經過hash算法映射到環上
在採用一致性哈希算法的分佈式集羣中將新的機器加入,其原理是經過使用與對象存儲同樣的Hash算法將機器也映射到環中(通常狀況下對機器的hash計算是採用機器的IP或者機器惟一的別名做爲輸入值),而後以順時針的方向計算,將全部對象存儲到離本身最近的機器中。
假設如今有NODE1,NODE2,NODE3三臺機器,經過Hash算法獲得對應的KEY值,映射到環中,其示意圖以下:
Hash(NODE1) = KEY1;
Hash(NODE2) = KEY2;
Hash(NODE3) = KEY3;



經過上圖能夠看出對象與機器處於同一哈希空間中,這樣按順時針轉動object1存儲到了NODE1中,object3存儲到了NODE2中,object二、object4存儲到了NODE3中。在這樣的部署環境中,hash環是不會變動的,所以,經過算出對象的hash值就能快速的定位到對應的機器中,這樣就能找到對象真正的存儲位置了。


機器的刪除與添加
普通hash求餘算法最爲不妥的地方就是在有機器的添加或者刪除以後會照成大量的對象存儲位置失效,這樣就大大的不知足單調性了。下面來分析一下一致性哈希算法是如何處理的。
1. 節點(機器)的刪除
以上面的分佈爲例,若是NODE2出現故障被刪除了,那麼按照順時針遷移的方法,object3將會被遷移到NODE3中,這樣僅僅是object3的映射位置發生了變化,其它的對象沒有任何的改動。以下圖:
                                                   2. 節點(機器)的添加 
若是往集羣中添加一個新的節點NODE4,經過對應的哈希算法獲得KEY4,並映射到環中,以下圖:




經過按順時針遷移的規則,那麼object2被遷移到了NODE4中,其它對象還保持這原有的存儲位置。經過對節點的添加和刪除的分析,一致性哈希算法在保持了單調性的同時,仍是數據的遷移達到了最小,這樣的算法對分佈式集羣來講是很是合適的,避免了大量數據遷移,減少了服務器的的壓力。


平衡性
根據上面的圖解分析,一致性哈希算法知足了單調性和負載均衡的特性以及通常hash算法的分散性,但這還並不能當作其被普遍應用的起因,由於還缺乏了平衡性。下面將分析一致性哈希算法是如何知足平衡性的。hash算法是不保證平衡的,如上面只部署了NODE1和NODE3的狀況(NODE2被刪除的圖),object1存儲到了NODE1中,而object二、object三、object4都存儲到了NODE3中,這樣就照成了很是不平衡的狀態。在一致性哈希算法中,爲了儘量的知足平衡性,其引入了虛擬節點。
    ——「虛擬節點」( virtual node )是實際節點(機器)在 hash 空間的複製品( replica ),一實際個節點(機器)對應了若干個「虛擬節點」,這個對應個數也成爲「複製個數」,「虛擬節點」在 hash 空間中以hash值排列。
以上面只部署了NODE1和NODE3的狀況(NODE2被刪除的圖)爲例,以前的對象在機器上的分佈很不均衡,如今咱們以2個副本(複製個數)爲例,這樣整個hash環中就存在了4個虛擬節點,最後對象映射的關係圖以下:




根據上圖可知對象的映射關係:object1->NODE1-1,object2->NODE1-2,object3->NODE3-2,object4->NODE3-1。經過虛擬節點的引入,對象的分佈就比較均衡了。那麼在實際操做中,正真的對象查詢是如何工做的呢?對象從hash到虛擬節點到實際節點的轉換以下圖:




 「虛擬節點」的hash計算能夠採用對應節點的IP地址加數字後綴的方式。例如假設NODE1的IP地址爲192.168.1.100。引入「虛擬節點」前,計算 cache A 的 hash 值:
Hash(「192.168.1.100」);
引入「虛擬節點」後,計算「虛擬節」點NODE1-1和NODE1-2的hash值:
Hash(「192.168.1.100#1」); // NODE1-1
Hash(「192.168.1.100#2」); // NODE1-2
以上全部規則每種都有特定使用場景,能夠選擇性使用!

相關文章
相關標籤/搜索