存儲-海量數據-(Mycat分庫分表)

 準備

1. 準備數據庫服務,這裏做爲學習用,咱們就用一個數據庫服務。
dhost1: localhost
2. 在dhost1服務建立三個數據庫java

CREATE DATABASE db1 CHARACTER SET 'utf8';
CREATE DATABASE db2 CHARACTER SET 'utf8';
CREATE DATABASE db3 CHARACTER SET 'utf8';

3. 配置好dataHost dataNodenode

<mycat:schema xmlns:mycat="http://io.mycat/">
 <!-- 注意:裏面的元素必定要按 schema 、dataNode 、 dataHost的順序配置 -->
 <schema name="mydb" checkSQLschema="true" sqlMaxLimit="100" dataNode="dn2">
 </schema>
 <dataNode name="dn1" dataHost="dhost1" database="db1" />
 <dataNode name="dn2" dataHost="dhost1" database="db2" />
 <dataNode name="dn3" dataHost="dhost1" database="db3" />
 <dataHost name="dhost1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" 
    dbDriver="native">
  <heartbeat>select user()</heartbeat>
  <writeHost host="hostM1" url="localhost:3306" user="xxx" password="xxx">
  </writeHost>
 </dataHost>
</mycat:schema>

1 表分類

分片表

分片表,是指那些有很大數據,須要切分到多個數據庫的表,這樣每一個分片都有一部分數據,全部分片構成了完整 的數據。mysql

<table name="t_goods" primaryKey="vid" autoIncrement="true" dataNode="dn1,dn2" rule="rule1" />

非分片表

一個數據庫中並非全部的表都很大,某些表是能夠不用進行切分的,非分片是相對分片表來講的,就是那些不需 要進行數據切分的表。linux

<table name="t_node" primaryKey="vid" autoIncrement="true" dataNode="dn1" />

示例算法

商家表,數據量500萬內。sql

CREATE TABLE t_shops(
  id bigint PRIMARY KEY AUTO_INCREMENT,
  name varchar(100) not null
);
<table name="t_shops" primaryKey="id" dataNode="dn1" />
INSERT INTO t_shops(name) values('xxx');

ER表

Mycat 中的ER 表是基於E-R 關係的數據分片策略,子表的記錄與所關聯的父表記錄存放在同一個數據分片上,保證 數據Join 不會跨庫操做。數據庫

ER分片是解決跨分片數據join 的一種很好的思路,也是數據切分規劃的一條重要規則。windows

<table name="customer" primaryKey="ID" dataNode="dn1,dn2" rule="sharding-by-intfile">
  <childTable name="orders" primaryKey="ID" joinKey="customer_id" parentKey="id">
  <childTable name="order_items" joinKey="order_id" parentKey="id" />
  </childTable>
</table>

全局表

一個真實的業務系統中,每每存在大量的相似字典表的表,這些表基本上不多變更。數組

問題:業務表每每須要和字典表Join查詢,當業務表由於規模而進行分片之後,業務表與字典表之間的關聯跨庫 了。服務器

解決:Mycat中經過表冗餘來解決這類表的join,即它的定義中指定的dataNode上都有一份該表的拷貝。(將字典 表或者符合字典表特性的表定義爲全局表。 )

<table name="company" primaryKey="ID" type="global" dataNode="dn1,dn2,dn3" />

示例:

省份表 t_province,在各數據節點所在庫上分別建立全局表:

CREATE TABLE t_province(
  id INT PRIMARY KEY,
  name varchar(100) not null
);
<table name="t_province" primaryKey="ID" type="global" dataNode="dn1,dn2,dn3" />

重啓mycat服務

插入數據看看

INSERT INTO t_province(id,name) values(1001,'浙江');
INSERT INTO t_province(id,name) values(1002,'江蘇');
INSERT INTO t_province(id,name) values(1003,'上海');
INSERT INTO t_province(id,name) values(1004,'廣東');

兩個數據節點庫上都同時寫入了該數據。

 

2 分片規則配置

分表規則定義

在conf/rule.xml中定義分片規則:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:rule SYSTEM "rule.dtd">
<mycat:rule xmlns:mycat="http://io.mycat/">
  <tableRule name="rule1">
   <rule>
     <columns>id</columns>
    <algorithm>func1</algorithm>
   </rule>
  </tableRule>
  <function name="func1" class="io.mycat.route.function.PartitionByLong">
     <property name="partitionCount">8</property>
     <property name="partitionLength">128</property>
  </function>
</mycat:rule>

tableRule標籤說明:

name 屬性指定惟一的名字,用於標識不一樣的表規則。

內嵌的rule 標籤則指定對物理表中的哪一列進行拆分和使用什麼路由算法。

   ● columns 內指定要拆分的列名字。
   ● algorithm 使用function 標籤中的name 屬性。鏈接表規則和具體路由算法。固然,多個表規則能夠鏈接到同一個路由算法上。table 標籤內使用。讓邏輯表使用這個規則進行分片。

function標籤說明:

name 指定算法的名字。
class 制定路由算法具體的類名字。
property 爲具體算法須要用到的一些屬性

 

3 分表分庫原則

分表分庫原則

分表分庫雖然能解決大表對數據庫系統的壓力,但它並非萬能的,也有一些不利之處,所以首要問題是分不分 庫,分哪些庫,什麼規則分,分多少分片。

原則一:能不分就不分,1000 萬之內的表,不建議分片,經過合適的索引,讀寫分離等方式,能夠很好的解決性能問題。
原則二:分片數量儘可能少,分片儘可能均勻分佈在多個DataHost 上,由於一個查詢SQL 跨分片越多,則整體性能越差,雖然要好於全部數據在一個分片的結果,只在必要的時候進行擴容,增長分片數量。
原則三:分片規則須要慎重選擇,分片規則的選擇,須要考慮數據的增加模式,數據的訪問模式,分片關聯性問題,以及分片擴容問題,最經常使用的分片策略爲範圍分片,枚舉分片,一致性Hash 分片,這幾種分片都有利於擴容。
原則四:儘可能不要在一個事務中的SQL 跨越多個分片,分佈式事務一直是個很差處理的問題。
原則五:查詢條件儘可能優化,儘可能避免Select * 的方式,大量數據結果集下,會消耗大量帶寬和CPU 資源,查詢儘可能避免返回大量結果集,而且儘可能爲頻繁使用的查詢語句創建索引。

這裏特別強調一下分片規則的選擇問題,若是某個表的數據有明顯的時間特徵,好比訂單、交易記錄等,則他們通 常比較合適用時間範圍分片,由於具備時效性的數據,咱們每每關注其近期的數據,查詢條件中每每帶有時間字段 進行過濾,比較好的方案是,當前活躍的數據,採用跨度比較短的時間段進行分片,而歷史性的數據,則採用比較 長的跨度存儲。

整體上來講,分片的選擇是取決於最頻繁的查詢SQL 的條件,由於不帶任何Where 語句的查詢SQL,會便利全部的 分片,性能相對最差,所以這種SQL 越多,對系統的影響越大,因此咱們要儘可能避免這種SQL 的產生。

SQL統計分析

如何準確統計和分析當前系統中最頻繁的SQL 呢?有幾個簡單作法:

    ● 採用特殊的JDBC 驅動程序,攔截全部業務SQL,並寫程序進行分析
    ● 採用Mycat 的SQL 攔截器機制,寫一個插件,攔截所欲SQL,並進行統計分析
    ● 打開MySQL 日誌,分析統計全部SQL。

找出每一個表最頻繁的SQL,分析其查詢條件,以及相互的關係,並結合ER 圖,就能比較準確的選擇每一個表的分片 策略。

庫內分表說明

對於你們常常提起的同庫內分表的問題,這裏作一些分析和說明,同庫內分表,僅僅是單純的解決了單一表數據過 大的問題,因爲沒有把表的數據分佈到不一樣的機器上,所以對於減輕MySQL 服務器的壓力來講,並無太大的做 用,你們仍是競爭同一個物理機上的IO、CPU、網絡。

此外,庫內分表的時候,要修改用戶程序發出的SQL,能夠想象一下A、B 兩個表各自分片5 個分表狀況下的Join SQL 會有多麼的反人類。這種複雜的SQL 對於DBA 調優來講,也是個很大的問題。所以,Mycat 和一些主流的數 據庫中間件,都不支持庫內分表,但因爲MySQL 自己對此有解決方案,因此能夠與Mycat 的分庫結合,作到最佳 效果,下面是MySQL 的分表方案:

    ●MySQL 分區;
    ●MERGE 表(MERGE 存儲引擎)。

通俗地講MySQL 分區是將一大表,根據條件分割成若干個小表。mysql5.1 開始支持數據表分區了。如:某用戶表 的記錄超過了600 萬條,那麼就能夠根據入庫日期將表分區,也能夠根據所在地將表分區。固然也可根據其餘的條 件分區。

MySQL 分區支持的分區規則有如下幾種:

     ●RANGE 分區:基於屬於一個給定連續區間的列值,把多行分配給分區。
     ●LIST 分區:相似於按RANGE 分區,區別在於LIST 分區是基於列值匹配一個離散值集合中的某個值來進行選擇。
     ●HASH 分區:基於用戶定義的表達式的返回值來進行選擇的分區,該表達式使用將要插入到表中的這些行的列值進行計算。這個函數能夠包含MySQL 中有效的、產生非負整數值的任何表達式。
     ●KEY 分區:相似於按HASH 分區,區別在於KEY 分區只支持計算一列或多列,且MySQL 服務器提供其自身的哈希函數。必須有一列或多列包含整數值。

在Mysql 數據庫中,Merge 表有點相似於視圖,mysql 的merge 引擎類型容許你把許多結構相同的表合併爲一個 表。以後,你能夠執行查詢,從多個表返回的結果就像從一個表返回的結果同樣。每個合併的表必須有徹底相同 表的定義和結構,可是隻支持MyISAM 引擎。

Mysql Merge 表的優勢:

    ●分離靜態的和動態的數據;
    ●利用結構接近的的數據來優化查詢;
    ●查詢時能夠訪問更少的數據;
    ●更容易維護大數據集。

在數據量、查詢量較大的狀況下,不要試圖使用Merge 表來達到相似於Oracle 的表分區的功能,會很影響性能。 個人感受是和union 幾乎等價。

Mycat 建議的方案是Mycat 分庫+MySQL 分區,此方案具備如下優點:

    ●充分結合分佈式的並行能力和MySQL 分區表的優化;
    ●能夠靈活的控制表的數據規模;
    ●能夠兩個維度對錶進行分片,MyCAT 一個維度分庫,MySQL 一個維度分區。

 

4 數據拆分原則

1. 達到必定數量級才拆分(800 萬)
2. 不到800 萬但跟大表(超800 萬的表)有關聯查詢的表也要拆分,在此稱爲大表關聯表
3. 大表關聯表如何拆:小於100 萬的使用全局表;大於100 萬小於800 萬跟大表使用一樣的拆分策略;沒法跟大表使用相同規則的,能夠考慮從java 代碼上分步驟查詢,不用關聯查詢,或者破例使用全局表。
4. 破例的全局表:如item_sku 表250 萬,跟大表關聯了,又沒法跟大表使用相同拆分策略,也作成了全局表。破例的全局表必須知足的條件:沒有太激烈的併發update,如多線程同時update 同一條id=1 的記錄。雖有多線程update,但不是操做同一行記錄的不在此列。多線程update 全局表的同一行記錄會死鎖。批量insert沒問題。
5. 拆分字段是不可修改的
6. 拆分字段只能是一個字段,若是想按照兩個字段拆分,必須新建一個冗餘字段,冗餘字段的值使用兩個字段的值拼接而成(如大區+年月拼成zone_yyyymm 字段)。
7. 拆分算法的選擇和合理性評判:按照選定的算法拆分後每一個庫中單表不得超過800 萬
8. 能不拆的就儘可能不拆。若是某個表不跟其餘表關聯查詢,數據量又少,直接不拆分,使用單庫便可。

 

5 DataNode 的分佈問題

DataNode 表明MySQL 數據庫上的一個Database,所以一個分片表的DataNode 的分佈可能有如下幾種:

    ●都在一個DataHost 上
    ●在幾個DataHost 上,但有連續性,好比dn1 到dn5 在Server1 上,dn6 到dn10 在Server2 上,依次類推
    ●在幾個DataHost 上,但均勻分佈,好比dn1,dn2,d3 分別在Server1,Server2,Server3 上,dn4 到dn5 又重複如此

通常狀況下,不建議第一種,二對於範圍分片來講,在大多數狀況下,最後一種狀況最理想,由於當一個表的數據 均勻分佈在幾個物理機上的時候,跨分片查詢或者隨機查詢,都是到不一樣的機器上去執行,並行度最高,IO 競爭 也最小,所以性能最好。

當咱們有幾十個表都分片的狀況下,怎樣設計DataNode 的分佈問題,就成了一個難題,解決此難題的最好方式是 試運行一段時間,統計觀察每一個DataNode 上的SQL 執行狀況,看是否有嚴重不均勻的現象產生,而後根據統計結 果,從新映射DataNode 到DataHost 的關係。

Mycat 1.4 增長了distribute 函數,能夠用於Table 的dataNode 屬性上,表示將這些dataNode 在該Table 的分片 規則裏的引用順序從新安排,使得他們能均勻分佈到幾個DataHost 上:

<table name="oc_call" primaryKey="ID" dataNode="distribute(dn1$0-372,dn2$0-372)" rule="latest-monthcalldate"/>

其中dn1xxx 與dn2xxxx 是分別定義在DataHost1 上與DataHost2 上的377 個分片

 

6 Mycat內置的經常使用分片規則

1 分片枚舉(列表分片)

經過在配置文件中配置可能的枚舉id,本身配置分片,本規則適用於特定的場景,好比有些業務須要按照省份或區 縣來作保存,而全國省份區縣固定的,這類業務使用本條規則,配置以下:

<tableRule name="sharding-by-intfile">
  <rule>
    <columns>user_id</columns>
    <algorithm>enum-func</algorithm>
  </rule>
</tableRule>
<function name="enum-func" class="io.mycat.route.function.PartitionByFileMap">
  <property name="mapFile">sharding-by-enum.txt</property>
  <property name="type">0</property>
  <property name="defaultNode">0</property>
</function>

function分片函數中配置說明:

●算法實現類爲:io.mycat.route.function.PartitionByFileMap
●mapFile 標識配置文件名稱;
●type 默認值爲0,0 表示Integer,非零表示String;
●defaultNode defaultNode 默認節點:小於0 表示不設置默認節點,大於等於0 表示設置默認節點爲第幾個數據節點。

默認節點的做用:枚舉分片時,若是碰到不識別的枚舉值,就讓它路由到默認節點 若是不配置默認節點
(defaultNode 值小於0 表示不配置默認節點),碰到不識別的枚舉值就會報錯。

like this:can’t find datanode for sharding column:column_name val:ffffffff

●sharding-by-enum.txt 放置在conf/下,配置內容示例:

10000=0 #字段值爲10000的放到0號數據節點
10010=1

示例

客戶表t_customer

CREATE TABLE t_customer(
  id BIGINT PRIMARY KEY,
  name varchar(100) not null,
  province int not null
);

按省份進行數據分片,表配置:

<table name="t_customer" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="sharding-by-province" />

分片規則配置 rule.xml:

<mycat:rule xmlns:mycat="http://io.mycat/">
  <tableRule name="sharding-by-province">
    <rule>
        <columns>province</columns>
        <algorithm>sharding-by-province-func</algorithm>
    </rule>
  </tableRule>
  <function name="sharding-by-province-func" class="io.mycat.route.function.PartitionByFileMap">
    <property name="mapFile">sharding-by-province.txt</property>
    <property name="type">0</property>
    <property name="defaultNode">0</property>
  </function>
</mycat:rule>

sharding-by-province.txt文件中枚舉分片

1001=0
1002=1
1003=2
1004=0

測試:插入數據

insert into t_customer(name,province) values('xxx01',1001);
insert into t_customer(name,province) values('xxx02',1002);
insert into t_customer(name,province) values('xxx03',1003);
insert into t_customer(name,province) values('xxx04',1004);
insert into t_customer(name,province) values('xxx05',1005);

2 範圍分片

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

<tableRule name="range-sharding">
  <rule>
    <columns>user_id</columns>
    <algorithm>rang-long</algorithm>
  </rule>
</tableRule>
<function name="rang-long" class="io.mycat.route.function.AutoPartitionByLong">
  <property name="mapFile">range-partition.txt</property>
  <property name="defaultNode">0</property>
</function>

配置說明:

●mapFile 表明配置文件路徑
●defaultNode 超過範圍後的默認節點。

全部的節點配置都是從0 開始,及0 表明節點1。

mapFile中的定義規則:

start <= range <= end.
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

示例

在mycat中定義分片表:

<table name="t_company" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="range-sharding-by-members-count" />
<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">company-range-partition.txt</property>
  <property name="defaultNode">0</property>
</function>

company-range-partition.txt中分片定義:

0-10=0
11-50=1
51-100=2
101-1000=0
1001-9999=1
10000-9999999=2

建立表

CREATE TABLE t_company(
  id BIGINT PRIMARY KEY,
  name varchar(100) not null,
  members int not null
);

測試:

INSERT INTO t_company(name,members) VALUES('company01',10);
INSERT INTO t_company(name,members) VALUES('company01',20);
INSERT INTO t_company(name,members) VALUES('company01',200);

3 按日期範圍分片

此規則爲按日期段進行分片。

<tableRule name="sharding-by-date">
  <rule>
    <columns>create_time</columns>
    <algorithm>sharding-by-date</algorithm>
  </rule>
</tableRule>
<function name="sharding-by-date" class="io.mycat.route.function.PartitionByDate">
  <property name="dateFormat">yyyy-MM-dd</property>
  <property name="sBeginDate">2018-01-01</property>
  <property name="sEndDate">2019-01-02</property>
  <property name="sPartionDay">10</property>
</function>

配置說明:

●columns :標識將要分片的表字段
●algorithm :分片函數
●dateFormat :日期格式
●sBeginDate :開始日期
●sEndDate:結束日期
●sPartionDay :分區天數,即默認從開始日期算起,分隔10 天一個分區

sBeginDate,sEndDate配置狀況說明:

   ● sBeginDate,sEndDate 都有指定

    此時表的dataNode 數量的>=這個時間段算出的分片數,不然啓動時會異常:

Exception in thread "main" java.lang.ExceptionInInitializerError
at io.mycat.MycatStartup.main(MycatStartup.java:53)
Caused by: io.mycat.config.util.ConfigException: Illegal table conf : table [ T_ORDER ] rule
function [ shardi
partition size : 4 > table datanode size : 3, please make sure table datanode size = function
partition size

若是配置了sEndDate 則表明數據達到了這個日期的分片後循環從開始分片插入

    ● 沒有指定 sEndDate 的狀況

    數據分片將依次存儲到dataNode上,數據分片隨時間增加,所需的dataNode數也隨之增加,當超出了爲該 表配置的dataNode數時,將獲得以下異常信息:

[SQL]
INSERT INTO t_order(order_time,customer_id,order_amount) VALUES ('2019-02-05',1001,203);
[Err] 1064 - Can't find a valid data node for specified node index :T_ORDER -> ORDER_TIME ->
2019-02-05 -> Index : 3

示例

<table name="t_order" primaryKey="order_id" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="order-sharding-by-date" />
<tableRule name="order-sharding-by-date">
  <rule>
    <columns>order_time</columns>
    <algorithm>sharding-by-date</algorithm>
  </rule>
</tableRule>
<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>
CREATE TABLE t_order (
  order_id BIGINT PRIMARY KEY,
  order_time DATETIME,
  customer_id BIGINT,
  order_amount DECIMAL(8,2)
);

測試

INSERT INTO t_order(order_time,customer_id,order_amount) VALUES ('2019-01-05',1001,201);
INSERT INTO t_order(order_time,customer_id,order_amount) VALUES ('2019-01-25',1001,202);
INSERT INTO t_order(order_time,customer_id,order_amount) VALUES ('2019-02-15',1001,203);
INSERT INTO t_order(order_time,customer_id,order_amount) VALUES ('2019-03-15',1001,203);

請去看數據的分佈!

4 天然月分片

按月份列分區,每一個天然月一個分片。

<tableRule name="sharding-by-month">
  <rule>
    <columns>create_time</columns>
    <algorithm>sharding-by-month</algorithm>
  </rule>
</tableRule>
<function name="sharding-by-month" class="io.mycat.route.function.PartitionByMonth">
  <property name="dateFormat">yyyy-MM-dd</property>
  <property name="sBeginDate">2014-01-01</property>
</function>

配置說明:

●columns: 分片字段,字符串類型
●dateFormat : 日期字符串格式,默認爲yyyy-MM-dd
●sBeginDate : 開始日期,無默認值
●sEndDate:結束日期,無默認值
●節點從0 開始分片

使用場景:

場景1:默認設置(不指定sBeginDate、sEndDate)

節點數量必須是12 個,對應1 月~12 月

    ●"2017-01-01" = 節點0
    ●"2018-01-01" = 節點0
    ●"2018-05-01" = 節點4
    ●"2019-12-01" = 節點11

場景2 :僅指定sBeginDate

sBeginDate = "2017-01-01" 該配置表示"2017-01 月"是第0 個節點,從該時間按月遞增,無最大節點

    ●"2014-01-01" = 未找到節點
    ●"2017-01-01" = 節點0
    ●"2017-12-01" = 節點11
    ●"2018-01-01" = 節點12
    ●"2018-12-01" = 節點23

場景3: 指定sBeginDate=1月、sEndDate=12月

sBeginDate = "2015-01-01" sEndDate = "2015-12-01" 該配置可當作與場景1 一致。

    ●"2014-01-01" = 節點0
    ●"2014-02-01" = 節點1
    ●"2015-02-01" = 節點1
    ●"2017-01-01" = 節點0
·   ●"2017-12-01" = 節點11
    ●"2018-12-01" = 節點11

場景4:

sBeginDate = "2015-01-01"sEndDate = "2015-03-01" 該配置表示只有3 個節點;很難與月份對應上;平均分散到 3 個節點上

5 取模

此規則爲對分片字段進行十進制運算,來分片數據。

<tableRule name="mod-sharding">
  <rule>
    <columns>user_id</columns>
    <algorithm>mod-fun</algorithm>
  </rule>
</tableRule>
<function name="mod-fun" class="io.mycat.route.function.PartitionByMod">
  <!-- how many data nodes -->
  <property name="count">3</property>
</function>

配置說明:

   ●count 指明dataNode 的數量,是求模的基數

此種在批量插入時可能存在批量插入單事務插入多數據分片,增大事務一致性難度

6 取模範圍分片

此種規則是取模運算與範圍約束的結合,主要爲了後續數據遷移作準備,便可以自主決定取模後數據的節點 分佈

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

partition-pattern.txt

1-32=0 #餘數爲1-32的放到數據節點0上
33-64=1
65-96=2
97-128=3
129-160=4
161-192=5
193-224=6
225-256=7
0-0=7

配置說明:

    ●patternValue 即求模基數
    ●defaoultNode 默認節點,若是配置了默認節點,若是id 非數據,則會分配在defaoultNode 默認節點
    ●mapFile 指定餘數範圍分片配置文件

7 二進制取模範圍分片

本條規則相似於十進制的求模範圍分片,區別在因而二進制的操做,是分片列值的二進制低10 位&1111111111。 此算法的優勢在於若是按照10 進製取模運算,在連續插入1-10 時候1-10 會被分到1-10 個分片,增大了插入的事 務控制難度,而此算法根據二進制則可能會分到連續的分片,減小插入事務控制難度。

二進制低10&1111111111 的結果是 0-1023 一共是1024個值,按範圍分紅多個連續的片(最大1024個片)

<tableRule name="rule1">
  <rule>
    <columns>user_id</columns>
    <algorithm>func1</algorithm>
  </rule>
</tableRule>
<function name="func1" class="io.mycat.route.function.PartitionByLong">
  <property name="partitionCount">2,1</property>
  <property name="partitionLength">256,512</property>
</function>

配置說明:

    ●partitionCount 分片個數列表。
    ●partitionLength 分片範圍列表

分區長度:默認爲最大2^n=1024 ,即最大支持1024 分區

約束:

    ●count,length 兩個數組的長度必須是一致的。
    ●1024 = sum((count[i] * length[i])),count 和length 兩個向量的點積恆等於1024

用法例子:

本例的分區策略:但願將數據水平分紅3 份,前兩份各佔25%,第三份佔50%。(本例非均勻分區)

// |<———————       1024         ———————————>|
// |<—-  25  6—>|<—-  256  —>|<———-512————->| 
// | partition0 | partition1 |  partition2  |

共2 份,故count[0]=2 | 共1 份,故count[1]=1 |

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

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

8 範圍取模分片

先進行範圍分片計算出分片組,組內再求模。

優勢能夠避免擴容時的數據遷移,又能夠必定程度上避免範圍分片的熱點問題。綜合了範圍分片和求模分片的優
點,分片組內使用求模能夠保證組內數據比較均勻,分片組之間是範圍分片能夠兼顧範圍查詢。

最好事先規劃好分片的數量,數據擴容時按分片組擴容,則原有分片組的數據不須要遷移。因爲分片組內數據比 較均勻,因此分片組內能夠避免熱點數據問題。

<tableRule name="auto-sharding-rang-mod">
  <rule>
    <columns>id</columns>
    <algorithm>rang-mod</algorithm>
  </rule>
</tableRule>
<function name="rang-mod" class="io.mycat.route.function.PartitionByRangeMod">
  <property name="mapFile">partition-range-mod.txt</property>
  <property name="defaultNode">21</property>
</function>

配置說明:

    ●mapFile 配置文件路徑
    ●defaultNode 超過範圍後的默認節點順序號,節點從0 開始。

partition-range-mod.txt 如下配置一個範圍表明一個分片組,=號後面的數字表明該分片組所擁有的分片的數量。

0-200M=5 //表明有5個分片節點
200M1-400M=1
400M1-600M=4
600M1-800M=4
800M1-1000M=6

9 一致性hash

一致性hash 算法有效解決了分佈式數據的擴容問題。

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

10 應用指定

此規則是在運行階段有應用自主決定路由到那個分片。

<tableRule name="sharding-by-substring">
  <rule>
    <columns>user_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><!-- zero-based -->
  <property name="size">2</property>
  <property name="partitionCount">8</property>
  <property name="defaultPartition">0</property>
</function>

配置說明:

此方法爲直接根據字符子串(必須是數字)計算分區號(由應用傳遞參數,顯式指定分區號)。 例如id=05- 100000002 在此配置中表明根據id 中從startIndex=0,開始,截取siz=2 位數字即05,05 就是獲取的分區,若是 沒傳 默認分配到defaultPartition

11 截取字符ASCII求和求模範圍分片

此種規則相似於取模範圍約束,只是計算的數值是取前幾個字符的ASCII值和,再取模,再對餘數範圍分片。

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

partition-pattern.tx

range start-end =data node index

#ASCII
#8-57=0-9 阿拉伯數字
#6四、65-90=@、A-Z
#97-122=a-z
1-4=0 # 餘數1-4的放到0號數據節點
5-8=1
9-12=2
13-16=3
17-20=4
21-24=5
25-28=6
29-32=7
0-0=7

配置說明:

    ●patternValue 即求模基數,
    ●prefixLength ASCII 截取的位數,求這幾位字符的ASCII碼值的和,再求餘patternValue
    ●mapFile 配置文件路徑,配置文件中配置餘數範圍分片規則。

 

7 主鍵值生成

在實現分庫分表的狀況下,數據庫自增主鍵已沒法保證自增主鍵的全局惟一。

CREATE TABLE t_customer(
  id BIGINT PRIMARY KEY,
  name varchar(100) not null,
  province int not null
);
<table name="t_customer" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="sharding-by-province" />

爲此,MyCat 提供了全局sequence,而且提供了包含本地配置和數據庫配置等多種實現方式。

1 本地文件方式

原理:此方式MyCAT 將sequence 配置到文件中,當使用到sequence 中的配置後,MyCAT 會更新 conf中的 sequence_conf.properties 文件中sequence 當前的值。

配置方式:

一、在sequence_conf.properties 文件中作以下配置:

GLOBAL.HISIDS=
GLOBAL.MINID=1001
GLOBAL.MAXID=1000000000
GLOBAL.CURID=1000

其中HISIDS 表示使用過的歷史分段(通常無特殊須要可不配置),MINID 表示最小ID 值,MAXID 表示最大 ID 值, CURID 表示當前ID 值。

二、server.xml 中配置:

<system><property name="sequnceHandlerType">0</property></system>

注:sequnceHandlerType 須要配置爲0,表示使用本地文件方式。

使用示例:

insert into table1(id,name) values( 10044,‘test’);

缺點:當MyCAT 從新發布後,配置文件中的sequence 會恢復到初始值。 優勢:本地加載,讀取速度較快。

爲表配置主鍵自增值的序列:

規則:在sequence_conf.properties 中配置以表名爲名的序列

T_COMPANY.CURID=501
T_COMPANY.MINID=1
T_COMPANY.MAXID=1000000000

就可使用了。

<table name="t_company" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="range-sharding-by-members-count" />
INSERT INTO t_company(name,members) VALUES('company06',200);

select * from t_company;

2 數據庫方式

原理

在數據庫中創建一張表,存放sequence 名稱(name),sequence 當前值(current_value),步長(increment int類 型,每次讀取多少個sequence)等信息;

Sequence 獲取步驟:

1. 當初次使用該sequence 時,根據傳入的sequence 名稱,從數據庫這張表中讀取current_value,和increment 到MyCat 中,並將數據庫中的current_value 設置爲原current_value 值+increment 值。
2. MyCat 將讀取到current_value+increment 做爲本次要使用的sequence 值,下次使用時,自動加1,當使用increment 次後,執行步驟1)相同的操做。

MyCat 負責維護這張表,用到哪些sequence,只須要在這張表中插入一條記錄便可。若某次讀取的 sequence 沒 有用完,系統就停掉了,則此次讀取的sequence 剩餘值不會再使用。

配置方式:

server.xml 配置:

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

注:sequnceHandlerType 須要配置爲1,表示使用數據庫方式生成sequence。

數據庫配置:

1) 建立MYCAT_SEQUENCE 表

-- 建立存放sequence 的表
DROP TABLE IF EXISTS MYCAT_SEQUENCE;
-- name sequence 名稱
-- current_value 當前value
-- increment 增加步長! 可理解爲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));
-- 插入一條sequence
INSERT INTO MYCAT_SEQUENCE(name,current_value,increment) VALUES ('GLOBAL', 100000,
100);

2) 建立相關function

-- 獲取sequence當前值(返回當前值,增量)的函數
DROP FUNCTION IF EXISTS mycat_seq_currval;

CREATE FUNCTION mycat_seq_currval(seq_name VARCHAR(50)) 
RETURNS varchar(64)
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;

-- 設置sequence 值的函數
DROP FUNCTION IF EXISTS mycat_seq_setval;

CREATE FUNCTION mycat_seq_setval(seq_name VARCHAR(50),value INTEGER) 
RETURNS varchar(64)
BEGIN
  UPDATE MYCAT_SEQUENCE
  SET current_value = value
  WHERE name = seq_name;
  RETURN mycat_seq_currval(seq_name);
END;

-- 獲取下一個sequence 值
DROP FUNCTION IF EXISTS mycat_seq_nextval;
CREATE FUNCTION mycat_seq_nextval(seq_name VARCHAR(50))
RETURNS varchar(64)
BEGIN
  UPDATE MYCAT_SEQUENCE
  SET current_value = current_value + increment
  WHERE name = seq_name;
  RETURN mycat_seq_currval(seq_name);
END;

注意:MYCAT_SEQUENCE 表和以上的3 個function,須要放在同一個節點上。function 請直接在具體節點的數據 庫上執行,若是執行的時候報: you might want to use the less safe log_bin_trust_function_creators variable

須要對數據庫作以下設置: windows 下my.ini[mysqld]加上log_bin_trust_function_creators=1 linux 下/etc/my.cnf 下my.ini[mysqld]加上log_bin_trust_function_creators=1 修改完後,便可在mysql 數據庫中執行 上面的函數。

3) sequence_db_conf.properties 相關配置,指定sequence 相關配置在哪一個節點上:

例如:

USER_SEQ=test_dn1

使用示例:

insert into table1(id,name) values( 10044,'test');

 

配置表的主鍵自增使用序列:

1 在序列定義表中增長名字爲表名的序列:

INSERT INTO MYCAT_SEQUENCE(name,current_value,increment) VALUES ('T_COMPANY', 1,100);

2 在sequence_db_conf.properties中增長表的序列配置

T_COMPANY=dn1

3 主鍵自增就可使用了

<table name="t_company" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="range-sharding-by-members-count" />
INSERT INTO t_company(name,members) VALUES('company08',200);

select * from t_company;

3 本地時間戳方式

原理:

ID= 64 位二進制:42(毫秒)+5(機器ID)+5(業務編碼)+12(重複累加)
換算成十進制爲18 位數的long 類型,每毫秒能夠併發12 位二進制的累加。

使用方式:

1 配置server.xml

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

2 在mycat 下配置:sequence_time_conf.properties

WORKID=0-31 任意整數 表示機器id(或mycat實例id)
DATAACENTERID=0-31 任意整數 業務編碼

多個mycat 節點下每一個mycat 配置的WORKID,DATAACENTERID 不一樣,組成惟一標識,總共支持32*32=1024 種 組合。

ID 示例:56763083475511 。

主鍵自增配置

<table name="t_company" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="range-sharding-by-members-count" />
INSERT INTO t_company(name,members) VALUES('company09',200);
select * from t_company;

4 分佈式ZK ID 生成器

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

配置

1 Zk 的鏈接信息統一在myid.properties 的zkURL 屬性中配置。此只需關注zkURL。

loadZk=false
zkURL=127.0.0.1:2181
clusterId=mycat-cluster-1
myid=mycat_fz_01
clusterSize=3
clusterNodes=mycat_fz_01,mycat_fz_02,mycat_fz_04
#server booster ; booster install on db same server,will reset all minCon to 2
type=server
boosterDataHosts=dataHost1

基於ZK 與本地配置的分佈式ID 生成器,ID 結構:long 64 位,ID 最大可佔63 位:

   ● |current time millis(微秒時間戳38 位,可使用17 年)|clusterId(機房或者ZKid,經過配置文件配置5位)|instanceId(實例ID,能夠經過ZK 或者配置文件獲取,5 位)|threadId(線程ID,9 位)|increment(自增,6 位)
   ● 一共63 位,能夠承受單機房單機器單線程1000*(2^6)=640000 的併發。
   ● 無悲觀鎖,無強競爭,吞吐量更高

2 配置文件:sequence_distributed_conf.properties,只要配置裏面:INSTANCEID=ZK 就是從ZK 上獲取 InstanceID。(能夠經過ZK 獲取集羣(機房)惟一InstanceID,也能夠經過配置文件配置InstanceID)

測試:

<table name="t_company" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="range-sharding-by-members-count" />
INSERT INTO t_company(name,members) VALUES('company10',200);
select * from t_company;

5 Zk 遞增方式

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

Zk 的鏈接信息統一在myid.properties 的zkURL 屬性中配置。

配置:

配置文件:sequence_conf.properties 只要配置好ZK 地址和表名的以下屬性

    ●TABLE.MINID 某線程當前區間內最小值
    ●TABLE.MAXID 某線程當前區間內最大值
    ●TABLE.CURID 某線程當前區間內當前值

文件配置的MAXID 以及MINID 決定每次取得區間,這個對於每一個線程或者進程都有效。文件中的這三個屬性配置 只對第一個進程的第一個線程有效,其餘線程和進程會動態讀取ZK

測試:

<table name="t_company" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="range-sharding-by-members-count" />
INSERT INTO t_company(name,members) VALUES('company12',200);
select * from t_company;

6 last_insert_id() 問題

咱們配置分片表主鍵自增。

<table name="t_company" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="range-sharding-by-members-count" />

如需經過 select last_insert_id() 來得到自增主鍵值,則表定義中主鍵列需是自增的AUTO_INCREMENT:

CREATE TABLE t_company(
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  name varchar(100) not null,
  members int not null
);

若是沒有指定 AUTO_INCREMENT,則select last_insert_id() 獲取不到剛插入數據的主鍵值。

CREATE TABLE t_company(
  id BIGINT PRIMARY KEY,
  name varchar(100) not null,
  members int not null
);

Mybatis 中新增記錄後獲取last_insert_id 的示例:

<insert id="insert" parameterType="com.study.user.model.User">
  insert into t_user (user_name,login_name,login_pwd,role_id)
  values(#{userName},#{loginName},#{loginPwd},#{roleId})
  <selectKey resultType="java.lang.Long" order="AFTER" keyProperty="id">
    select last_insert_id() as id
  </selectKey>
</insert>
相關文章
相關標籤/搜索