您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關MyCat的分庫分表策略是什么,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
在講解MyCat
分庫分表策略之前,我們首先介紹一下其配置文件的格式。在MyCat
中,配置文件主要有兩個:schema.xml
和rule.xml
。顧名思義,這兩個配置文件分別指定了MyCat
所代理的數據庫集群的配置和分庫分表的相關策略。schema.xml
中的典型配置如下:
<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <!-- 指定了對外所展示的數據庫名稱,也就是說,客戶端連接MyCat數據庫時,制定的database為mydb 而當前數據庫中的表的配置就是根據下面的配置而來的 --> <schema name="mydb" checkSQLschema="true" sqlMaxLimit="100"> <!-- 定義了一個t_goods表,該表的主鍵是id,該字段是自增長的,并且該表的數據會被分配到dn1,dn2和 dn3上,這三個指的是當前MyCat數據庫所代理的真實數據庫的節點名,每個節點的具體配置在下面的 配置中。這里rule屬性指定了t_goods表中的數據分配到dn1,dn2和dn3上的策略,mod-long指的是 按照長整型取余的方式分配,也就是按照id對節點數目進行取余 --> <table name="t_goods" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="mod-long"/> </schema> <!-- 分別指定了dn1,dn2和dn3三個節點與對應的數據庫的關系,dataHost對應的就是下面的數據庫節點配置 --> <dataNode name="dn1" dataHost="dhost1" database="db1"/> <dataNode name="dn2" dataHost="dhost2" database="db2"/> <dataNode name="dn3" dataHost="dhost3" database="db3"/> <!-- 這里分別指定了各個數據庫節點的配置 --> <dataHost name="dhost1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="localhost:3306" user="root" password="password"/> </dataHost> <dataHost name="dhost2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native"> <heartbeat>select user()</heartbeat> <writeHost host="hostM2" url="localhost:3306" user="root" password="password"/> </dataHost> <dataHost name="dhost3" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native"> <heartbeat>select user()</heartbeat> <writeHost host="hostM3" url="localhost:3306" user="root" password="password"/> </dataHost> </mycat:schema>
可以看到,schema.xml
指定的是各個數據庫節點與MyCat
中虛擬數據庫和表的關聯關系,并且指定了當前表的分表策略,比如這里的mod-long
。在rule.xml
中則指定了具體的分表策略及其所使用的算法實現類,如下是一個典型的rule.xml
的配置:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mycat:rule SYSTEM "rule.dtd"> <mycat:rule xmlns:mycat="http://io.mycat/"> <!-- 這里的mod-long對應的就是上面schema.xml的表配置中rule屬性所使用的規則名稱,其columns節點 指定了當前規則所對應的字段名,也就是id,algorithm節點則指定了當前規則所使用的算法,具體的 算法對應于下面的function節點所指定的實現類--> <tableRule name="mod-long"> <rule> <columns>id</columns> <algorithm>mod-long</algorithm> </rule> </tableRule> <!-- 這里指定的是mod-long這個算法所使用的具體實現類,實現類需要使用全限定路徑,具體的代碼讀者朋友 可以閱讀MyCat源碼,并且讀者也可以查看MyCat默認為我們提供了哪些分表策略實現 --> <function name="mod-long" class="io.mycat.route.function.PartitionByMod"> <!-- 指定了當前所使用的數據庫節點數 --> <property name="count">3</property> </function> </mycat:rule>
結合schema.xml
和rule.xml
兩個配置文件的配置,我們可以看出,MyCat
首先通過schema.xml
指定了當前服務器中所虛擬的數據庫,以及該數據庫中所對應的表的配置,比如這里的mydb
和t_goods
,實際上,我們在通過數據庫連接工具連接到MyCat
數據庫時,看到的表定義都是通過該配置文件得來的,其本身并沒有通過讀取真實的數據庫節點來獲得這些配置。在指定了虛擬數據庫和虛擬表之后,在schema.xml
中,通過表級別的配置,又分別指定了當前表所關聯的數據節點配置,以及該表是如何進行分庫分表的。而具體的分庫分表實現類則在rule.xml
中進行了配置。另外,通過上面的配置,我們也可以看出,MyCat
是不支持通過客戶端連接工具來創建表的,其所有的額表必須提前在配置文件中進行定義。
關于取余的策略,這種方式上面已經進行了詳細的介紹,主要的策略就是根據指定的字段對數據庫節點數進行取余,從而將其插入到對應的數據庫中,這里不再贅述。
按照范圍分片,顧名思義,就是首先對整體數據進行范圍劃分,然后將各個范圍區間分配到對應的數據庫節點上,當用戶插入數據時,根據指定字段的值,判斷其屬于哪個范圍,然后將數據插入到該范圍對應的數據庫節點上。需要注意的是,這里會配置一個默認的范圍,當用戶插入的數據不再任何指定的范圍內時,該數據將會被插入到默認節點上。如下是按范圍分片的配置:
<!-- schema.xml --> <table name="t_company" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="range-sharding-by-members-count"/>
<!-- rule.xml --> <tableRule name="range-sharding-by-members-count"> <rule> <!-- 指定了分片字段 --> <columns>members</columns> <algorithm>range-members-count</algorithm> </rule> </tableRule> <function name="range-members-count" class="io.mycat.route.function.AutoPartitionByLong"> <!-- 指定了范圍分片的”范圍-節點“的對應策略 --> <property name="mapFile">files/company-range-partition.txt</property> <!-- 指定了超出范圍的數據將會被分配的數據節點 --> <property name="defaultNode">0</property> </function>
<!-- 上面mapFile屬性指定的company-range-partition.txt文件內容,這里指定了具體的范圍與數據節點的對應關系 --> 0-10=0 11-50=1 51-100=2 101-1000=0 1001-9999=1 10000-9999999=2
按照日期分片,這種方式相對來說理解稍微復雜一點,我們這里直接展示一個配置示例:
<!-- schema.xml --> <table name="t_order" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="order-sharding-by-date"/>
<!-- rule.xml --> <tableRule name="order-sharding-by-date"> <rule> <!-- 指定分區字段為order_time --> <columns>order_time</columns> <!-- 指定分區算法為sharding-by-date --> <algorithm>sharding-by-date</algorithm> </rule> </tableRule> <!-- 指定分區算法使用的實現類是io.mycat.route.function.PartitionByDate,這里需要傳如四個屬性: dateFormat表示下面sBeginDate、sEndDate以及分區字段的數據值所使用的日期格式化方式; sBeginDate指定了分區范圍的開始時間; sEndDate指定了分區范圍的結束時間; sPartitionDay指定了每個分區間隔的時間范圍長度--> <function name="sharding-by-date" class="io.mycat.route.function.PartitionByDate"> <property name="dateFormat">yyyy-MM-dd</property> <property name="sBeginDate">2019-01-01</property> <property name="sEndDate">2019-02-02</property> <property name="sPartionDay">20</property> </function>
上面的配置中,比較好理解的是sBeginDate
和sEndDate
,這兩個參數指定了所有分區將會劃分的總的分區時間段范圍;而sPartitionDay
則指定了每個分區所占用的時間段范圍,比如這里sPartitionDay
為20,sBeginDate
為2019-01-01
,sEndDate
為2019-02-02
,也就是說根據20天一段來分,整個時間段將會被分為2019-01-01~2019-01-21
和2019-01-21~2019-02-02
。關于這里劃分的分區數,這里需要說明兩點:
這里我們將整個時間段切割之后,只能得到兩個分區,但我們的數據庫節點配置了三個,此時,只有第一個節點和第二個節點將會被使用,第三個將始終不會用到;
如果我們提供的數據庫節點數比切割的分區數要小的話,那么就會有一部分分區沒有與其指定的數據庫節點,此時就會拋出異常;
另外,我們需要著重強調的一點是,在正常使用的過程中,如果配置了結束時間,那么始終會有一天我們的時間會超出結束時間,但如果我們將結束時間配置得非常大,那么就會出現一個問題就是所需要的分區數會比我們的數據庫節點數要多,此時就會拋出異常。在這一點上,MyCat
允許我們的分區字段時間比結束時間要大,也就是說,插入的字段值可以是2019-02-02
以后的日期。此時目標日期所在的分區計算方式如下:
int targetPartition = ((endTimeMills - sBeginDateMills) / (partitionDurationMills)) % nPartitons;
endTimeMills
表示目標要計算的時間戳;
sBeginDateMills
表示配置的開始時間戳;
partitionDurationMills
表示每個分區時間段的時間戳時長,比如這里就是20 * 24 * 60 * 60 * 1000
;
nPartitons
表示當前在sBeginDate
和sEndDate
之間劃分的分區數量,根據前面我們的演示知道其為2;
上面的公式,從整體上來理解,其實比較簡單,本質上就是將目標時間與開始時間之間的差值除以分區長度,從而計算得出目標時間與開始時間之間的分區數,然后將該分區數與當前劃分的分區數進行取模,從而得出其所在的分區。下面我們以四條數據為例,講解其將會落入的目標數據節點:
insert into t_order(`id`, `order_time`) values (1, '2019-01-05'); # 分區0,db1 insert into t_order(`id`, `order_time`) values (1, '2019-01-25'); # 分區1,db2 insert into t_order(`id`, `order_time`) values (1, '2019-02-05'); # 分區1,db2 insert into t_order(`id`, `order_time`) values (1, '2019-02-15'); # 分區2,db1
在上述配置中,我們是配置了分區的結束時間的,實際上,在這種分區策略下,我們也可以不配置結束時間,如果不配置結束時間,那么需要注意的一點是,目標時間所在的分區計算公式如下:
int targetPartition = (endTimeMills - sBeginDateMills) / (partitionDurationMills);
相信讀者朋友已經看出來了,這就是計算目標時間與開始時間中間間隔了多少個分區,然后將該值作為目標分區,也就是數據會落到目標數據庫節點上,此時,隨著時間的持續增長,如果數據庫節點的數目比當前計算得到的分區數要小,那么就會拋出異常。
按照月份進行分片,顧名思義,就是以月為單位,判斷目標時間在哪個月內,然后就將數據分配到這個月對應的數據節點上。如下是按照月份進行分片的配置示例:
<!-- schema.xml --> <table name="t_bank" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2,dn3,dn4,dn5,dn6,dn7,dn8,dn9,dn10,dn11,dn12" rule="sharding-by-month"/>
<!-- rule.xml --> <tableRule name="sharding-by-month"> <rule> <columns>create_time</columns> <algorithm>partbymonth</algorithm> </rule> </tableRule> <function name="partbymonth" class="io.mycat.route.function.PartitionByMonth"> <property name="dateFormat">yyyy-MM-dd</property> </function>
根據上面的配置,我們需要說明如下幾點:
如果沒有配置開始時間和結束時間,那么數據庫的節點數必須大于等于12,因為一年有12個月,數據將會根據其所在的月份分配到對應的數據節點上;
如果只配置了開始時間(字段名為sBeginDate
),此時需要注意兩點:
如果目標時間小于開始時間,則會拋出異常,因為目標時間與開始時間之間的分區數值為負數;
如果目標時間大于開始時間,則會計算目標時間與開始時間之間的月份數,此時該月份數就會作為目標數據節點(其并不會對12取模之后存放),因而如果計算得到的數值比我們的數據庫節點數要大,就會拋出異常;
如果配置了開始時間,也配置了結束時間(字段名為sEndDate
),需要注意的是,此時并不會按照目標時間所在的月份而將其放到對應的數據庫節點上,而是首先會計算開始時間和結束時間之間相隔的月份數nPartitions
,然后計算目標時間與開始時間之間的月份數targetPartitions
,然后將兩者取模,即targetPartitions % nPartitions
,從而得到其所在的數據庫節點。另外,需要注意的是,如果目標時間小于開始時間,那么targetPartitions
就是一個負數,此時其所在的分區計算公式就為nPartitions - targetPartitions % nPartitions
,也就是說,目標分區會循環的從最大的分區值往下倒數。
按照枚舉值分片比較適合于某個字段只有固定的幾個值的情況,比如省份。通過配置文件將每個枚舉值對應的數據庫節點進行映射,這樣對于指定類型的數據,就會被分配到同一個數據庫實例中。如下是按照枚舉值分片的一個示例:
<!-- schema.xml --> <table name="t_customer" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="sharding-by-province"/>
<tableRule name="sharding-by-province"> <rule> <!-- 指定了分區字段 --> <columns>province</columns> <!-- 指定分區算法 --> <algorithm>sharding-by-province-func</algorithm> </rule> </tableRule> <!-- 按照枚舉值分片的算法,這里mapFile中保存了分區字段中各個枚舉值與目標數據庫實例的對應關系; type字段表示了分區字段的key的類型,0表示數字,非0表示字符串;defaultNode指定了沒有配置映射 關系的數據其存儲的數據庫節點 --> <function name="sharding-by-province-func" class="io.mycat.route.function.PartitionByFileMap"> <property name="mapFile">files/sharding-by-province.txt</property> <property name="type">0</property> <property name="defaultNode">0</property> </function>
<!-- sharding-by-province.txt --> 1001=0 1002=1 1003=2 1004=0
范圍取模分片的優點在于,既擁有范圍分片的固定范圍數據不做遷移的優點,也擁有了取模分片對于熱點數據均勻分布的優點。首先我們還是以一個示例進行講解:
<!-- schema.xml --> <table name="t_car" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="auto-sharding-rang-mod"/>
<!-- rule.xml --> <tableRule name="auto-sharding-rang-mod"> <rule> <!-- 指定id字段為范圍取模分片的字段 --> <columns>id</columns> <algorithm>rang-mod</algorithm> </rule> </tableRule> <!-- 這里defaultNode指的是,如果目標字段的值不在范圍內,則將其放置到默認節點上;mapFile指定了 范圍與分片數的一個對應關系 --> <function name="rang-mod" class="io.mycat.route.function.PartitionByRangeMod"> <property name="defaultNode">0</property> <property name="mapFile">files/partition-range-mod.txt</property> </function>
<!-- partition-range-mod.txt --> 0-5=1 6-10=2 11-15=1
關于范圍取模分片,這里需要著重說明一下其概念:
在最后的partition-range-mod.txt
文件中,我們可以看到,其每一行在等號前指定了一個不想交的范圍,這個范圍表示的就是目標分區字段的值將會落在哪個范圍內;
等號后面有一個數字,需要注意的是,這個數字并不是指數據庫節點id,而是當前范圍將會占用的數據庫節點數目,比如這里的范圍0-5
內的數據將會被分配到1個數據庫節點上,而范圍6-10
內的數據將會被分配到2個數據庫節點上;
等號后面指定了當前范圍所需要使用的分片數,而該范圍的數據在這幾個數據庫節點的分布方式是通過取模的方式來實現的,也就是說,在大的方向上,整體數據被切分為多個范圍,然后在每個范圍內,數據根據取模的方式分配到不同的數據節點上。
這也就是范圍取模分片的概念的由來,這種分片方式的優點在于,在進行擴容和數據遷移的時候,不相關的范圍內的數據是不需要移動的。比如假設我們0-5范圍內的數據非常多,1個數據庫實例無法承受,此時就可以增加一個數據庫實例,然后將配置改為0-5=2
,接著將之前該范圍內的數據庫的數據導出,然后由重新導入,以平均分配到這兩個數據庫節點上。可以看出,這種方式擴容,對于其余兩個范圍內的數據庫實例是沒有影響的。最后,需要著重強調的一點是,既然等號后面表示所需要的數據庫實例數量,那么等號后面的數字加起來的和一定要小于我們所提供的真實數據庫實例的數量。
二進制取模分片的方式與范圍取模非常相似,但也有不同,其分片方式主要是根據目標分片字段的低10位的值來判斷其屬于哪個分片。我們首先還是以一個示例進行講解:
<!-- schema.xml --> <table name="t_bike" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="rule1"/>
<!-- rule.xml --> <tableRule name="rule1"> <rule> <!-- 指定了分片字段 --> <columns>id</columns> <algorithm>func1</algorithm> </rule> </tableRule> <!-- 這里分片算法中需要傳如兩個參數:partitionCount和partitionLength。這兩個字段的含義分別為分區數量 和分區長度,但是需要注意的是,這里的分區數量和分區長度相乘之后加起來必須為1024,比如這里的 2 * 256 + 1 * 512 = 1024。至于為什么必須為1024,這主要是因為二進制取模分片是取目標分區字段的 低10位數據作為其所在的槽,由于低10位最大為1024,因而這里配置的加和必須為1024。 --> <function name="func1" class="io.mycat.route.function.PartitionByLong"> <property name="partitionCount">2,1</property> <property name="partitionLength">256,512</property> </function>
關于上面的分片方式的分片效果,其總共有1+2 = 3個分片,而每個分片中所分配的范圍分別為0-255
,256-511
和512-1023
。圖示如下:
|----------------------------------1024----------------------------------| |-------256-------|-------256--------|-----------------512---------------| |---partition0----|----partition1----|-------------partition2------------|
一致性hash分片方式上面的二進制取模方式非常相似,不過一致性hash的虛擬槽的概念更強,并且一致性hash分片的虛擬槽的數量是可配置的。如下是一個典型的一致性hash分片的配置方式:
<!-- schema.xml --> <table name="t_house" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="sharding-by-murmur"/>
<!-- rule.xml --> <tableRule name="sharding-by-murmur"> <rule> <columns>id</columns> <algorithm>murmur</algorithm> </rule> </tableRule> <!-- 下面的屬性中,count指定了要分片的數據庫節點數量,必須指定,否則沒法分片;virtualBucketTimes指的是 一個實際的數據庫節點被映射為這么多虛擬節點,默認是160倍,也就是虛擬節點數是物理節點數的160倍; weightMapFile指定了節點的權重,沒有指定權重的節點默認是1。以properties文件的格式填寫, 以從0開始到count-1的整數值也就是節點索引為key,以節點權重值為值。所有權重值必須是正整數, 否則以1代替;bucketMapPath用于測試時觀察各物理節點與虛擬節點的分布情況,如果指定了這個屬性, 會把虛擬節點的murmur hash值與物理節點的映射按行輸出到這個文件,沒有默認值,如果不指定, 就不會輸出任何東西--> <function name="murmur" class="io.mycat.route.function.PartitionByMurmurHash"> <property name="seed">0</property><!-- 默認是0 --> <property name="count">3</property> <property name="virtualBucketTimes">160</property><!-- --> <!-- <property name="weightMapFile">weightMapFile</property> --> <property name="bucketMapPath"> /Users/zhangxufeng/xufeng.zhang/mycat/bucketMap.txt</property> </function>
按照目標字段前綴進行分片,這種方式就比較好理解,其會獲取到指定分區字段的前綴值,然后將其轉換為十進制數字,將其作為分區值,如果該數字超過了分區數量,則會將當前數據放在默認分區。如下是按照字符串前綴方式分區的配置示例:
<!-- schema.xml --> <table name="t_community" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="sharding-by-substring"/>
<!-- rule.xml --> <tableRule name="sharding-by-substring"> <rule> <!-- 指定按照id字段進行分區 --> <columns>id</columns> <algorithm>sharding-by-substring</algorithm> </rule> </tableRule> <function name="sharding-by-substring" class="io.mycat.route.function.PartitionDirectBySubString"> <!-- 指定前綴的開始位置 --> <property name="startIndex">0</property> <!-- 指定前綴的大小 --> <property name="size">2</property> <!-- 指定了分區數量 --> <property name="partitionCount">3</property> <!-- 指定了默認分區 --> <property name="defaultPartition">0</property> </function>
按照前綴ASCII碼和值進行取模,顧名思義,就是取了前綴之后,將其轉換為ASCII碼值,然后對取模基數進行取模,最后將求得的余數按照配置文件中的范圍分配到對應的數據庫節點上。如下是該分區方式的配置示例:
<!-- schema.xml --> <table name="t_phone" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="sharding-by-prefixpattern"/>
<!-- rule.xml --> <tableRule name="sharding-by-prefixpattern"> <rule> <!-- 指定id為分區字段 --> <columns>id</columns> <algorithm>sharding-by-prefixpattern</algorithm> </rule> </tableRule> <!-- 這里的patternValue指定的是取模基數;prefixLength表示對指定字段的前多少位進行截取計算ASCII碼; mapFile中指定了取模的余數范圍與目標數據庫節點的對應關系 --> <function name="sharding-by-prefixpattern" class="io.mycat.route.function.PartitionByPrefixPattern"> <property name="patternValue">256</property> <property name="prefixLength">5</property> <property name="mapFile">files/partition-pattern.txt</property> </function>
<!-- partition-pattern.txt --> 0-100=0 101-200=1 201-256=2
以上就是MyCat的分庫分表策略是什么,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。