1. 程式人生 > >Mysql叢集和一主多從之後如何分庫分表的方案實現(三)

Mysql叢集和一主多從之後如何分庫分表的方案實現(三)

4-3、使用MyCat配置橫向拆分

之前文章中我們介紹瞭如何使用MyCat進行讀寫分離,類似的關係型資料庫的讀寫分離儲存方案可以在保持上層業務系統透明度的基礎上滿足70%業務系統的資料承載規模要求和效能要求。比起單純使用LVS + Replicaion的讀寫分離方案而言最大的優勢在於更能增加對上層業務系統的透明性。當然如果 
您覺得單個MyCat節點在高可用範疇或者效能範疇上還需要增強,還可以使用Keepalived、LVS等元件在多個MyCat節點上組成高可用叢集或者負載叢集。

但是這個方案也有一個明顯的問題,那就是它沒有解決資料儲存規模的瓶頸。如果單個節點上某個單表的資料規模超過了千萬級,那麼這個節點的讀操作也會產生效能瓶頸。所以我們還需要進一步使用MyCat的分片技術對業務資料表進行橫向拆分

這裡寫圖片描述

要說清楚MyCat對橫向拆分的支援,就首先要說清楚關係型資料庫橫向拆分所面臨的主要問題,以及MyCat為了解決這些問題所作的努力。總的來說橫向拆分的所面臨的問題主要分為兩大類,一類是資料讀的問題一類是資料寫的問題。本節我們首先分析討論一下資料讀的問題,後文中介紹MyCat對分散式事務的支援時我們再來討論橫向拆分時的資料寫問題。

4-3-1、資料分片中的資料讀操作問題

select TableA.*,TableB.xname,TableC.xcode from TableA
left join TableB on TableB.id = TableA.b_codeid
left join
TableC on TableC.a_id = TableA.id where TableA.groupname = 'XXXX'
  • 1
  • 2
  • 3
  • 4

以上查詢語句是我們在業務系統資料查詢的過程中經常使用的一種查詢型別,是一種多個數據表進行左外連線的查詢語句。其中TableA業務表擁有大量的資料且變化頻率非常高,是需要進行拆分的主要資料表;TableB業務表可能是一張字典表,雖然它有比較大的資料,但遠遠沒有達到千萬級別並且變化頻率很低(每天最多有10000次資料寫操作);TableC業務表中的資料量也很大,從技術角度上說該業務表可以做拆分也可以不做拆分,其中的TableC.a_id欄位和TableA.id欄位也是一種弱關聯,也就是說TableC中的業務資料就算沒有關聯TableA中的業務資料也可以相對獨立的工作。當然以上說的是一種可能的業務資料狀態,實際情況還可能更復雜。

如果這些業務表同在一個數據庫中,甚至是存在於同一個MySQL例項的不同資料庫中,那麼執行以上查詢語句基本上沒有什麼難度,技術人員使用MySQL的執行計劃也可以很清晰的看到查詢語句的執行過程:

這裡寫圖片描述

但是如果在分庫狀態下,那麼查詢過程就沒有這麼簡單了。首先來說,主要需要進行資料拆分處理的TableA中的資料分佈在不同的資料庫中,這些資料庫工作在不同的MySQL例項上。另外業務表TableC中的資料也進行了拆分,但是拆分時並沒有參考和其可能有關聯的TableA中的業務資料儲存分片情況,也就是說原來已有的關聯在拆分儲存後可能就消失了,而且即使拆分後的資料關聯還存在,但拆分前和拆分後執行資料排序操作的結構也可能是不同的。至於字典表TableB,由於可預見的時間內資料總規模不大,所以可以不進行拆分——所有拆分後的資料庫中TableB資料表的資料內容完全一樣。下圖展示了一種資料表中資料進行隨機拆分後可能的儲存結構和產生的問題:

這裡寫圖片描述

這樣來看,資料表橫向拆分過程中至少需要考慮以下讀操作問題:

  • 橫向拆分後資料表之間的邏輯關聯問題:資料表間存在各種關聯,有的關聯甚至還存在外來鍵約束。資料拆分後的關聯關係應該和拆分前的關聯關係保持一致,至少應該保證通過資料庫中介軟體查詢得到的資料關聯結果和拆分前的關聯結果保持一致。

  • 橫向拆分後資料的排序和分頁問題:由於資料拆分後,排序動作會分別在各個拆分後的資料庫中單獨執行,這可能就會導致拆分後的排序和分頁結果和拆分前的結果不一致。那麼資料庫中介軟體需要保證能夠將這些結果集合進行整合並還原成拆分前的排序和分頁結果。

  • 橫向拆分後資料的分組操作問題:分組和統計操作同樣存在和以上描述類似的問題,各個拆分後的資料庫將單獨執行分組和統計,這就可能導致用一個分割槽條件在各個拆分資料庫中都有分組和統計結果。資料庫中介軟體還是需要保證能夠合併這些分組統計結果,並保證它們和拆分前的資料庫操作結果一致。

4-3-2、全域性表

在資料庫的橫向拆分過程中,各種資料字典表基本上不需要進行拆分。這是因為這些資料表的資料規模都不會太大且變化頻率較低,另外一個原因是減少橫向拆分後表關聯操作的難度。類似省市縣資訊、手機區號資訊、功能選單資訊等資料都應算作字典資料。根據實際工作經驗,並不會出現所有業務表的資料規模都達到或超過千萬級規模,只有部分關鍵業務表的資料規模會出現這樣的情況,基於這樣的情況只有這些業務表和與它們直接關聯的部分資料表需要進行資料拆分設計

MyCat資料庫中介軟體中為除了以上情況外,各個不需要進行資料拆分的資料表提供了一種冗餘複製方案:全域性表。如果一張業務表在schema.xml配置檔案配設定成了全域性表,那麼MyCat將在涉及這張業務表的所有分片節點上保持這張數居表中資料完全一致。Mycat在Join操作中,業務表與全域性表進行Join聚合會優先選擇相同分片內的全域性表join,避免跨庫 Join;在進行資料插入操作時,MyCat將把資料分發到全域性表對應的所有分片執行,在進行資料讀取時候將會隨機獲取一個節點讀取資料。schema.xml配置檔案中的全域性表配置類似如下:

......
<schema ......>
    # 請注意這裡的type屬性,屬性值為“global”,代表全域性表
    # 這樣,在dn1和dn2兩個分片節點中的t_area業務表中,其資料將保持完全一致。
    <table name="t_area" primaryKey="id" type="global" dataNode="dn1,dn2" />
</schema>
......
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

4-3-3、分片表

為了在表關聯查詢效能和表關聯處理難易程度之間取得平衡,MyCat提供了兩種分片表型別和多種分片規則。對於業務關聯較為獨立的需要進行資料分片的業務表可以採用普通分片。然而有一類情況是,需要進行資料分片的業務表有一些非常重要的關聯資料也同時需要進行分片,例如訂單(order)資料表和訂單明細(order_detail)資料表。很明顯訂單資料和訂單明細資料是經常需要進行關聯查詢的,並且既然訂單資料達到了一定的規模需要進行資料分片,那麼只會比它資料量更大的訂單明細表也同時需要進行分片。在這樣的關聯分片情況下,MyCat需要保證訂單明細A1、A2、A3、A4資料能夠正確的寫入到他們關聯的訂單資訊A所在的分片上。這樣才能保證訂單A在join查詢訂單明細時,向請求者返回正確的查詢結果。MyCat提供的這種分片模式稱為ER分片/智慧分片

在後續4-4、4-5和4-6節中,本文將和讀者一起來討論MyCat中支援資料分片的兩種關鍵分片表型別,普通分片和智慧分片。我們還會一起討論MyCat中主要支援的資料分片規則,包括mod-long、partbymonth、rang-mod、rang-long、hash-int等分片規則。MyCat還支援開發人員自定義分片規則,這個自定義方式也會進行介紹。

4-3-4、Share join和catlet(人工智慧)

MyCat還向技術人員提供了兩種不同分片的查詢彙總功能,其中Share join是一個簡單的跨分片Join方式,目前支援 2 個表的 join,原理就是解析 SQL 語句,拆分成單表的 SQL 語句執行,然後把各個節點的資料匯 
集;另外一種catlet人工智慧分片查詢功能,是將Join查詢語句分析後,從指定分片提取查詢結果的前半部分,然後將查詢結果送入其它分片以便可以結合到這個結果所關聯的其他資料。Share join查詢的做法和人工智慧分片查詢的做法往往無法實現高效能處理,所以這兩種不同分片的資料關聯查詢方式只適合開發人員使用,不建議在生產環境中使用

4-4、普通分片場景示例

資料表普通分片是比較好理解的概念,即是說一個擁有相對獨立業務的資料表,按照一定的拆分規則將資料分別儲存在若干個獨立的資料庫中的操作。能夠進行這種分片操作的資料表的特點是,業務耦合度一般較低或者屬於基礎性功能模組;這種資料表也可能存在和其它資料的關聯,但是關聯的是一個或者多個字典資料表;即使這種資料表存在直接關聯的其它業務資料,那麼後者的資料規模和變化頻率也不會在可預見的時間內進行資料分片操作。這種場景在實際業務中是比較常見的,典型的就是使用者基礎資訊:

這裡寫圖片描述

在產品第N次迭代時,考慮了後續幾個月內註冊使用者量將突破1000萬大關,且半年內將繼續成幾何級增長。這時架構師就必須考慮對“使用者中心子系統”中使用者基本資訊進行分庫處理。使用者基本資訊快速遷移/割接的問題很好解決,由於目前使用者基本資訊只有百萬左右,所以可以考慮在每個分片庫先做整體冗餘,然後再後續運維工作中再進行資料清掃。也可以在最初階段就考慮合適的分片規則,保證這幾百萬資料在後續儲存方案升級中將可以作為整個MySQL分庫分表叢集的第一個分片節點組(後文在講解分片規則時會詳細講到)“使用者中心子系統”中我們為可能的5000萬用戶資料規模規劃了5個分片,每個分片中做兩組讀寫分離,每組讀寫分離包含一個寫節點和二至三個讀節點。並且這兩個組的寫節點互為主從。

這裡寫圖片描述

以下是schema.xml主配置檔案中重要的設定內容:

<mycat:schema xmlns:mycat="http://io.mycat/">
    <!-- 在這個測試示例中一共有三張邏輯表 -->
    <schema name="usercenterSchema" checkSQLschema="false" sqlMaxLimit="200">
        <!-- 
        以下是若干張不需要進行資料分片的字典性質的資料表
        它們都以全域性表的形式在每個分片節點上擁有完全一致的資料
        -->
        <table name="dictionaryA" primaryKey="Id" type="global" dataNode="dn1,dn2,dn3,dn4,dn5" />
        <table name="dictionaryB" primaryKey="Id" type="global" dataNode="dn1,dn2,dn3,dn4,dn5" />
        <table name="dictionaryC" primaryKey="Id" type="global" dataNode="dn1,dn2,dn3,dn4,dn5" />
        <table name="dictionaryD" primaryKey="Id" type="global" dataNode="dn1,dn2,dn3,dn4,dn5" />
        <!-- 
        這是在本示例中我們需要進行分片的使用者基本資訊資料表
        -->
        <table name="usertable" primaryKey="Id" dataNode="dn1,dn2,dn3,dn4,dn5" rule="mod-long"/>
    </schema>

    <!-- 
    設定五個邏輯節點/分片節點
    這裡注意一個問題,如果為了節約成本可以將某兩個或者某幾個邏輯節點
    執行在相同的MySQL物理機群上,那麼建議database屬性取不同的名字
    例如stacks01、stacks02、stacks03...
    -->
    <dataNode name="dn1" dataHost="dataHost1" database="stacks" />
    <dataNode name="dn2" dataHost="dataHost2" database="stacks" />
    <dataNode name="dn3" dataHost="dataHost3" database="stacks" />
    <dataNode name="dn4" dataHost="dataHost4" database="stacks" />
    <dataNode name="dn5" dataHost="dataHost5" database="stacks" />

    <!-- 
    第一個分片節點中定義了兩個寫操作節點和其對應的讀操作節點,
    writeType設定為0,表示一般情況下所有寫操作都發送到配置的第一個寫節點上,
    另一個寫節點和讀節點充當standby的角色。
    -->
    <dataHost name="dataHost1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="2">
        <heartbeat>select user()</heartbeat>
        <writeHost host="dataHost1_hostM1" url="192.168.61.140:3306" user="root" password="123456">
            <readHost host="dataHost1_hostS11" url="192.168.61.141:3306" user="root" password="123456"/>
            <readHost host="dataHost1_hostS12" url="192.168.61.142:3306" user="root" password="123456"/>
        </writeHost>
        <writeHost host="dataHost1_hostM2" url="192.168.61.150:3306" user="root" password="123456">
            <readHost host="dataHost1_hostS21" url="192.168.61.151:3306" user="root" password="123456"/>
            <readHost host="dataHost1_hostS22" url="192.168.61.152:3306" user="root" password="123456"/>
        </writeHost>
    </dataHost>
    .......
</mycat:schema>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

以上配置示例中關於分片規則的部分(table標籤的rule屬性),我們將在後文中專門進行介紹。讀者在這裡只需要知道“mod-long”是一種長整形取餘的分片方式就可以了,另外全域性表和分片表唯一的設定差別就是全域性表需要明確指定type屬性,而分片表不需要。很明顯至少從現在的情況看,使用者基本資訊雖然有直接關聯的資料資訊,但是關聯的都是字典性質的資料表,這些資料表都被設定為全域性表,所以在任何分片中使用者基本資訊都可以找到與它正確關聯的資訊。最後需要再次注意的MyCat並不負責資料同步過程,所以所有節點的資料同步還需要技術人員根據頂層設計自行配置

4-5、ER分片及使用限制

4-5-1、ER分片基本使用

經過上一節示例的技術迭代過程,為不久的將來線上業務系統達到5000萬級別使用者基本資訊的資料儲存規模的準備工作就完成了,但是新的要求又來了:我們需要記錄最近一年時間內使用者對基本資訊的修改情況。這部分修改情況可能來源於另一套日誌採集系統(例如一套基於Apache Flume + Apache Kafka + Apache Storm的日誌資料實時採集分析平臺)也可能直接來源於業務系統對資料變化的判斷,這裡我們並不討論資料的來源問題,而只討論這部分使用者基本資訊變化資料的儲存情況——假設技術團隊已經決定使用關係型資料庫儲存這些變化資料。

很顯然使用者基本資訊的修改明細和使用者基本資訊存在很強的關聯關係,且使用者基本資訊的修改明細也需要進行分片。當用戶基本資訊A進入分片資料庫X時,需要和這個使用者基本資訊進行關聯的修改明細資訊也必須正確進入資料庫X,這樣才能保持資料關聯的正確性。這是因為:

  • 如果資料表存在外來鍵約束設定,那麼使用者資訊修改明細錯誤寫入分片時就會導致寫操作直接報錯——分片資料庫無法找到關聯的使用者資訊。而使用外來鍵約束又是明確被建議的資料庫設計方式。

  • 即使資料表不存在外來鍵約束設定,雖然使用者資訊修改明細可以寫入和使用者基本資訊不一致的分片,但是在基於使用者基本資訊進行關聯查詢時就無法查詢到正確的關聯資訊。

看來要在保持效能的前提下解決這個問題,就必須保證父級表和子級表在同時需要分片時,相關聯資料能夠正確寫入相同的分片中,MyCat稱這樣的分片表為ER分片表

這裡寫圖片描述

MyCat的主配置檔案中使用table標籤的子標籤childTable對ER分片表的關係進行標識。如下示例:

......
<table name="usertable" primaryKey="Id" dataNode="dn1,dn2" rule="mod-long">
    <childTable name="booktable" primaryKey="Id" joinKey="authorid" parentKey="Id"/>
</table>
......
  • 1
  • 2
  • 3
  • 4
  • 5

關於table標籤已經在上文中介紹過了,這裡的使用方式相似。需要注意的是childTable標籤的幾個關鍵屬性:

  • primaryKey屬性:該屬性和table標籤中的primaryKey屬性意義相同,表示該邏輯表對應真實表的主鍵。

  • joinKey屬性和parentKey屬性:在進行childTable表資料插入時,MyCat會首先依據joinKey屬性設定的欄位拿到本次資料插入時該欄位的值,然後再根據parentKey屬性指定的父級Table的列資訊生成查詢語句,以便確定將要插入的這條資料,其父級資料在哪個分片上。

有的讀者可能就要提問了:為什麼不採用已設定的分片規則重新計算出資料存放的分片呢?這是因為分片規則可能會產生變化,即使分片規則沒有產生變化,很多規則下作為計算基準的“可用分片數量”也可能產生了變化。瞭解了ER分片表的基本工作方式,我們就可以對上一節使用者中心使用的普通分片場景進行調整,在其為使用者基本資訊修改明細配置ER分片關係,調整後的配置檔案如下所示(只列出了關鍵的變化位置,其他全域性表的設定沒有變化):

......
<!-- 原來的全域性表設定還是沒有變 -->
<table name="dictionaryD" primaryKey="Id" type="global" dataNode="dn1,dn2,dn3,dn4,dn5" />
<!-- 
這是在本示例中我們需要進行分片的使用者基本資訊資料表
-->
<table name="usertable" primaryKey="Id" dataNode="dn1,dn2,dn3,dn4,dn5" rule="mod-long">
    <!--
    使用者基本資訊修改明細
    -->
    <childTable name="usermodifyDetails" primaryKey="Id" joinKey="userid" parentKey="Id"/>
</table>
......
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

請注意,我們並沒有為childTable設定分片規則和可以使用的分片節點,這是因為childTable每一條資料儲存的位置是由它父級Table表中每一條資料的實際儲存位置決定。通過ER分片我們可以保證類似如下的join關聯語句能夠在每個分片中正確執行,並被彙總到MyCat服務上。這是MyCat服務對這些分片結果進行正確的二次整合的前提條件。

# 無論是這兩張資料表做怎樣的join關聯,都可以保證沒個分片中的查詢結果是正確的。
# 如以下這種查詢方法
select usertable.*,usermodifyDetails.fieldname,usermodifyDetails.fieldnewValue from usertable
left join usermodifyDetails on usertable.Id = usermodifyDetails.userid

# 或者這種查詢方法,又或者其它的只涉及這兩個資料表的一對多、多對一關聯查詢
select usermodifyDetails.*,usertable.Id,usertable.username from usermodifyDetails
left join usertable on usertable.Id = usermodifyDetails.userid
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

=========================================================== 

來源:http://blog.csdn.net/yinwenjie(未經允許嚴禁用於商業用途!)