天天看點

Mycat(三)——幾種分片規則 分庫 一緻性hash的原理及使用

文章目錄

    • Mycat分片規則
      • 取模
    • 分庫
      • schema.xml
    • 分片枚舉
      • schema.xml
      • 測試
        • 問題:
    • 固定hash分片
      • 優點
    • 範圍約定分片
    • 按日期分區
      • 按天分
    • 一緻性HASH
      • 解決什麼問題?
      • 原理
      • 增加節點
      • 某個節點當機
      • 資料傾斜
      • 總結
    • Mycat使用一緻性Hash
      • 跳增一緻性哈希分片

Mycat分片規則

取模

在前面示範分表的時候,使用了取模的方式實作。

取模的話是根據節點個數進行,會有一些弊端,如:

  1. hash不均勻,生成的分布式id未必是連續的id,是以大機率可能會有很多id被hash到同一個節點;
  2. 擴容需要rehash。假如有3個節點,一個id被id%3 hash到了第一個節點,如果進行擴容,增加一個執行個體,那麼再對這個id進行hash,id%4,可能就到了另外一個節點,這樣的話就無法查詢到這個id的資料資訊。

分庫

這裡使用兩組主從執行個體。

master-01——slave01

master02——slave02

【搭建主從見文尾巴】

在兩個master中建立一個mycat庫,和如下表:

CREATE TABLE `t_order` (
  `orderId` bigint(20) NOT NULL,
  `orderName` varchar(255) NOT NULL,
  `orderType` varchar(255) CHARACTER SET utf8 NOT NULL,
  `createTime` datetime DEFAULT NULL,
  PRIMARY KEY (`orderId`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf-8 ROW_FORMAT=DYNAMIC;
           

schema.xml

<schema name="mycatDB" checkSQLschema="true" dataNode="ali3307">
    	<!--指定兩個dataNode,對應兩個master主機-->
		<table name="t_order" dataNode="ali3307,tx3306"  primaryKey="orderId" rule="mod-long">
		</table>
</schema>

	<dataNode name="ali3307" dataHost="HOSTali3307" database="mycat" />
	<dataNode name="tx3306" dataHost="HOSTtx3306" database="consult" />
	
	<!--第一個master-->
	<dataHost name="HOSTali3307" maxCon="1000" minCon="10" balance="0"
			  writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
		<!--heartbeat标簽
		發送心跳,會檢測下面的兩個主機是否為主從
		-->
		<heartbeat>select user()</heartbeat>
		<connectionInitSql></connectionInitSql>
		<!--
		如果writeHost指定的後端資料庫當機,那麼這個writeHost綁定的所有readHost都将不可用。另一方面,由于這個writeHost當機系統會自動的檢測到,并切換到備用的writeHost上去
		-->
        <!--writeHost,指定我們的master,用于寫-->
		<writeHost host="aliWrite" url="xxxx:3307" user="root"
				   password="root">
            <!--readHost指定為slave,用于讀-->
			<readHost host="aliRead" url="xxxx:3316" password="root" user="root"/>
		</writeHost>
	</dataHost>
<!--第二個master-->
	<dataHost name="HOSTtx3306" maxCon="1000" minCon="10" balance="0"
			  writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
		<heartbeat>select user()</heartbeat>
		<connectionInitSql></connectionInitSql>
		<writeHost host="txWrite" url="xxxx:3306" user="root"
				   password="123456">
			<readHost host="txRead" url="xxx:3307" password="123456" user="root"/>
		</writeHost>
	</dataHost>
           

說明:

  • 在虛拟表table中指定兩個master執行個體的dataNode。
  • 兩個dataNode分别指定自己的dataHost,在dataHost中,writeHost指定為master,用于寫,readHost用于指定slave,用于讀。
  • mycat會自動檢測到主從關系。
  • 規則rule這裡還是使用取模 mod-long,在rule.xml中配置的:
    <tableRule name="mod-long">
    		<rule>
                <!--指定id列名-->
    			<columns>orderId</columns>
    			<algorithm>mod-long</algorithm>
    		</rule>
    	</tableRule>
    <function name="mod-long" class="io.mycat.route.function.PartitionByMod">
        <!--兩個master執行個體,配置2-->
    		<property name="count">2</property>
    	</function>
               

測試添加兩條資料:

insert into t_order(orderId,orderName,orderType,createTime) values(741624363904667648,'na','DD','2020-02-02')
insert into t_order(orderId,orderName,orderType,createTime) values(741624363904667649,'na','DD','2020-02-02')
           
Mycat(三)——幾種分片規則 分庫 一緻性hash的原理及使用

分别插入到了兩個庫中的t_order表。

分片枚舉

假如我們要根據某個字段進行分區,如根據不同的省份分到不同的庫中。

準備一張表:

CREATE TABLE `t_order_province` (
  `orderId` bigint(20) NOT NULL,
  `orderName` varchar(255) NOT NULL,
  `createTime` datetime DEFAULT NULL,
  `province` varchar(255) NOT NULL,
  PRIMARY KEY (`orderId`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
           

第4個字段為所屬省份資訊,我們就根據該字段進行分區。

schema.xml

<schema name="mycatDB" checkSQLschema="true" dataNode="ali3307">
        <table name="t_order_province" dataNode="ali3307,tx3306" primaryKey="orderId" rule="sharding-by-intfile">
        </table>
    </schema>
           

在這裡表名換成上面的表,然後指定分區規則。其他的和之前的一樣。

在rule.xml中配置規則:

<tableRule name="sharding-by-intfile">
		<rule>
			<columns>province</columns>
			<algorithm>hash-int</algorithm>
		</rule>
	</tableRule>
<function name="hash-int"
			  class="io.mycat.route.function.PartitionByFileMap">
		<property name="mapFile">partition-hash-int.txt</property>
		<!--type預設值為0,0表示Integer,非零表示String-->
		<property name="type">1</property>
		<property name="defaultNode">0</property>
	</function>
           

columns指定分區的列。

在該function中,mapFile指定分區的配置檔案。

如下:

BJ=0
SJ=0
GZ=0
HZ=0
JS=1
SX=1
           

假設将這幾個省份分别分到兩個區中(注意有幾個mysql執行個體,就隻能分幾個區,我們有兩個master執行個體,是以最多隻能分倆區)

是以,這種方式隻能針對這種知道固定值的場景,對範圍内可能出現的值做固定分區。

defaultNode:

表示預設節點:小于0表示不設定預設節點,大于等于0表示設定預設節點。

對于不能找到分區的值就存到預設節點中。

測試

連接配接mycat的邏輯庫插入資料:

INSERT INTO t_order_province(orderId,orderName,createTime,province) values(2133,'嘻哈哈','2020-08-03','BJ');
INSERT INTO t_order_province(orderId,orderName,createTime,province) values(2134,'噜噜噜','2020-08-03','HZ');

INSERT INTO t_order_province(orderId,orderName,createTime,province) values(2133,'滴滴','2020-08-03','JS');
INSERT INTO t_order_province(orderId,orderName,createTime,province) values(2134,'啦啦','2020-08-03','SX');
           

結果:

Mycat(三)——幾種分片規則 分庫 一緻性hash的原理及使用

可以看到,4組資料根據省份名,按照配置的分區進入到了兩個庫中。

這樣就可以根據各個省份實際的業務量,對其資料進行分區到不同的庫中。

問題:

對于這樣一種分片方式,像上海北京這樣的地區的資料量非常大,這樣的話,時間久了資料量就會傾斜到此類地區的分區中,而像新疆、西藏的分區就會很少,當資料量飽和時,就需要再增加節點,如使用三台機器儲存北京上海的資料。這時可以在中間再加一層mycat,通過一緻性hash進行分片:

Mycat(三)——幾種分片規則 分庫 一緻性hash的原理及使用

一緻性hash我們後面再講。

固定hash分片

該分片規則,就取id的二進制低10位,然後和1111111111相與得到一個結果。

優點

相對于十進制取模,當連續插入1-10時,可能會被分到10根分片,而基于二進制,可能會分到連續的分片,能夠減少插入事務,避免使用XA帶來的性能問題。

配置:

其他都一樣,分片規則改成固定hash。

rule.xml中配置如下:

<tableRule name="gd-hash">
		<rule>
			<columns>orderId</columns>
			<algorithm>gd-hash-func</algorithm>
		</rule>
	</tableRule>
	<function name="gd-hash-func" class="io.mycat.route.function.PartitionByLong">
		<property name="partitionCount">1,1</property>
		<property name="partitionLength">300,724</property>
	</function>
           

這裡有兩個參數:

  • partitionCount

    分區的數量,值為逗号隔開的相加。上面就是2個分區,如果為2,1那就是3個分區。

  • partitionLength

    每個分區的長度。總長為1024。如上面的,第一個分區就是0-299,第二個分區就是300-1023;

    如果是如下配置:

    <function name="gd-hash-func" class="io.mycat.route.function.PartitionByLong">
    		<property name="partitionCount">2,1</property>
    		<property name="partitionLength">300,424</property>
    	</function>
               
    則共3個分區。第一個分區範圍為:0-299;第二個分區為:300-599;第三個分區為600-1023;

配置完成後插入兩條資料:

INSERT INTO t_order_province(orderId,orderName,createTime,province) values(1111,'aa','2020-02-02','BJ');
INSERT INTO t_order_province(orderId,orderName,createTime,province) values(8888,'aa','2020-02-02','BJ');

           
  • 1111

    轉為二進制為10001010111

    低10位為:0001010111。

    和1111111111相與後為:1010111=87,是以進入第一個分區

  • 8888

    二進制:10001010111000

    低10位:1010111000

    相與後:1010111000=696進入第二個分區。

結果:

Mycat(三)——幾種分片規則 分庫 一緻性hash的原理及使用

範圍約定分片

用處不大,就是指定固定的範圍進行分片。

rule.xml:

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

指定在

autopartition-long.txt

檔案中配置範圍規則:

0-1000=0
1001-2000=1
           

0-1000的id在0分區,1001-2000的分區在1分區。

如果不在範圍内,則不可插入。

很簡單,就不示範了。

按日期分區

按天分

其他都一樣,指定分區規則即可:

<tableRule name="sharding-by-date">
		<rule>
			<columns>createTime</columns>
			<algorithm>partbyday</algorithm>
		</rule>
	</tableRule>
           

columns指定日期列。

<function name="partbyday"
			  class="io.mycat.route.function.PartitionByDate">
		<property name="dateFormat">yyyy-MM-dd</property>
    <!--開始日期-->
		<property name="sNaturalDay">0</property>
		<property name="sBeginDate">2020-08-01</property>
     <!--結束日期-->
		<property name="sEndDate">2020-08-08</property>
		<property name="sPartionDay">4</property>
	</function>
           
  • sPartionDay

    分區大小。指定分區的天數。即從開始日期開始,每n天分在一個節點中。

因為目前隻有兩個資料庫執行個體,是以調成每4天一個分區,範圍從08-01開始,到08-08,正好可以分2個區。

如果範圍内可分的分區數大于配置的dataNode個數,啟動就會報錯。

如果想啟動不報錯,就不指定結束日期,這樣就可以啟動了。但是肯定也會按從開始日期開始指定天數進行分區的,是以如果插入的資料的日期除以範圍個數的出來的分區位置沒有對應的dataNode的話,還是會插入失敗。

仍然用之前按省分的那個表:

INSERT INTO t_order_province(orderId,orderName,createTime,province) values(1111,'aa','2020-08-03','BJ');
INSERT INTO t_order_province(orderId,orderName,createTime,province) values(222,'aa','2020-08-04','BJ');
INSERT INTO t_order_province(orderId,orderName,createTime,province) values(333,'aa','2020-08-06','BJ');
INSERT INTO t_order_province(orderId,orderName,createTime,province) values(4444,'aa','2020-08-08','BJ');
           

這樣4條資料,前兩個應該在第一個dataNode中,後兩個應該在第二個dataNode中:

Mycat(三)——幾種分片規則 分庫 一緻性hash的原理及使用

按月分區道理一樣的:

<function name="sharding-by-month"
			  class="io.mycat.route.function.PartitionByMonth">
		<property name="dateFormat">yyyy-MM-dd</property>
		<property name="sBeginDate">2020-01-01</property>
		<property name="nPartition">3</property>
	</function>
           

按需配置即可。相關配置進入PartitionByMonth可以看。

一緻性HASH

解決什麼問題?

前面在分片枚舉的部分說到,對于上海北京這樣的會出現大業務量的分區,一定會出現資料傾斜現象,這時我們需要對原本的分區節點進行擴容,而擴容一下不要緊,原本hash到之前節點的資料,擴容後,節點個數加1,再進行hash就肯定無法定位到之前的節點,相當于這些資料通過簡單的按節點個數取模的hash方式都找不到了。

假如是Redis的架構,那麼相當于原理的某個機器的所有的key都失效了,這樣當通路這些key的時候就都會打到資料庫,會出現緩存雪崩。

原理

Mycat(三)——幾種分片規則 分庫 一緻性hash的原理及使用

如上,一緻性hash算法會對2^32次方進行取模,所有的hash後的值組成了一個hash環。

每個主機和過來的id,都會通過一個hash算法獲得其在hash環上的一個位置。

上圖中,假設有三個節點 H1、H2和H3,其通過hash會到了環中的某個位置;

現在又id1~id5五個id,分别進行hash後也到了環上的幾個位置;

這些id如何對應到某個節點呢?

這裡,其一緻性hash按照環的順時針方向,對某個id,将其放在順時針方向距離其最近的一個節點上。

上圖中,配置設定到某個節點的id和目前節點用了同一個顔色标注。

那麼假設以下情況:

增加節點

此時像分片枚舉中那樣,單個節點不夠用了,OK,我們加一個節點:

Mycat(三)——幾種分片規則 分庫 一緻性hash的原理及使用

此時增加了一個H4節點,那麼現在,就不會像之前的取模hash一樣,所有的id都會失效,現在隻會有從H4到H2之間的id,即id1會失效,此時id1交給H4處理。

容錯性比原本的方式要好很多。

某個節點當機

Mycat(三)——幾種分片規則 分庫 一緻性hash的原理及使用

假設此時H3當機了,那麼隻有H3和H1之間的id資料會收到影響,即id4,此時id4交給H2處理。

資料傾斜

節點較多時,其hash分布肯定會較均勻,但假如節點比較少,就會出現資料傾斜的情況:

Mycat(三)——幾種分片規則 分庫 一緻性hash的原理及使用

如上,id1、id2、id3和id5都配置設定給了H1,而H3隻配置設定到了id4,這就會出現資料傾斜。

而一緻性hash針對這種情況,會采用一種虛拟節點的方式解決:

Mycat(三)——幾種分片規則 分庫 一緻性hash的原理及使用

如上,H3映射出一個虛拟的H3-1,H1映射出一個虛拟的H1-1;

這樣id2、id5由H1-1處理;

id1由H3-1處理;

但是實際上,id2和id5逗遊實際的H1處理(圖中箭頭所示),id1由實際的H3處理。

是以,其内部需要維護一個虛拟節點到真實節點的映射關系。

在查詢時,比如查id1,通過hash找到對應的環的位置,然後找到最近的H3-1。再根據映射關系找到真實的H3,然後從H3中找到id1的資料。

這就是一緻性hash的原理。

總結

  • 通過虛拟節點解決資料傾斜問題
  • 動态擴容或節點當機時,需要遷移的資料少

Mycat使用一緻性Hash

rule.xml

<tableRule name="sharding-by-murmur">
		<rule>
			<columns>orderId</columns>
			<algorithm>murmur</algorithm>
		</rule>
	</tableRule>

	<function name="murmur"
			  class="io.mycat.route.function.PartitionByMurmurHash">
		<property name="seed">0</property><!-- 預設是0 -->
		<property name="count">2</property><!-- 要分片的資料庫節點數量 -->
		<property name="virtualBucketTimes">160</property><!-- 一個實際的資料庫節點被映射的虛拟節點個數,預設就是160 -->
		<!-- <property name="weightMapFile">weightMapFile</property> 節點的權重,沒有指定權重的節點預設是1。以properties檔案的格式填寫,以從0開始到count-1的整數值也就是節點索引為key,以節點權重值為值。所有權重值必須是正整數,否則以1代替 -->
		<property name="bucketMapPath">D:\Documentation\JAVA\mycat\Mycat-Server-Mycat-server-1675-release\src\main\resources</property>
		<!-- 用于測試時觀察各實體節點與虛拟節點的分布情況,如果指定了這個屬性,會把虛拟節點的murmur hash值與實體節點的映射按行輸出到這個檔案,沒有預設值,如果不指定,就不會輸出任何東西 -->
	</function>
           

這裡還是使用之前的分片枚舉用的表,插入1000條資料:

@Test
    public void test() {
        for (int i = 0; i < 1000; i++) {
            Order order = new Order();
            order.setOrderId(SnowflakeUtil.nextId());
            order.setOrderName("RR");
            order.setProvince("XX");
            orderMapper.add(order);
        }
    }
@Insert("insert into t_order_province(orderId,orderName,createTime,province) values(#{orderId},#{orderName},now(),#{province})")
    int add(Order order);

           

兩個資料庫的資料:

Mycat(三)——幾種分片規則 分庫 一緻性hash的原理及使用

一個449,一個551,較均勻。

跳增一緻性哈希分片

mycat源碼說明:思想源自Google公開論文,比傳統一緻性哈希更省資源速度更快資料遷移量更少

rule.xml

<tableRule name="jch">
		<rule>
			<columns>orderId</columns>
			<algorithm>jump-consistent-hash</algorithm>
		</rule>
	</tableRule>
<function name="jump-consistent-hash" class="io.mycat.route.function.PartitionByJumpConsistentHash">
		<property name="totalBuckets">2</property>
	</function>
           

totalBuckets:指定節點個數。

插入1000條資料:

@Test
    public void test() {
        for (int i = 0; i < 1000; i++) {
            Order order = new Order();
            order.setOrderId(SnowflakeUtil.nextId());
            order.setOrderName("RR");
            order.setProvince("XX");
            orderMapper.add(order);
        }
    }
@Insert("insert into t_order_province(orderId,orderName,createTime,province) values(#{orderId},#{orderName},now(),#{province})")
    int add(Order order);

           
Mycat(三)——幾種分片規則 分庫 一緻性hash的原理及使用

兩個master執行個體的資料均勻了很多。

繼續閱讀