MyCat是基於阿里開源的Cobar產品而研發,Cobar的穩定性、可靠性、優秀的架構和性能以及衆多成熟的使用案例使得MYCAT一開始就擁有一個很好的起點,站在巨人的肩膀上,咱們能看到更遠。業界優秀的開源項目和創新思路被普遍融入到MYCAT的基因中,使得MYCAT在不少方面都領先於目前其餘一些同類的開源項目,甚至超越某些商業產品。html
MYCAT背後有一支強大的技術團隊,其參與者都是5年以上資深軟件工程師、架構師、DBA等,優秀的技術團隊保證了MYCAT的產品質量。myCAT開源項目維護很頻繁,目前最新版已經到了1.5 Release(23天前剛維護過)。所以它的維護性、穩定性是獲得了保證的。同時myCat的文檔極其豐富,參於開發的人員又不少,因此它能夠應付不少在以往的Amoeba以及Corba上未能解決的問題,它是一個能夠真正被應用在生產環境上的數據庫中間件。在下面的章節咱們將使用myCat來實現mySQL的讀寫分離和垂直水平折分的具體案例。前端
什麼是MyCAT?簡單的說,MyCAT就是:java
<?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>
<?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>
<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 start
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;
<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 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 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"/>
<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"/>
<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>
通常來講,咱們數據庫中的全部表很難經過某一個(或少數幾個)字段所有關聯起來,因此很難簡單的僅僅經過數據的水平切分來解決全部問題。而垂直切分也只能解決部分問題,對於那些負載很是高的系統,即便僅僅只是單個表都沒法經過單臺數據庫主機來承擔其負載。咱們必須結合「垂直」和「水平」兩種切分方式同時使用
每個應用系統的負載都是一步一步增加上來的,在開始遇到性能瓶頸的時候,大多數架構師和DBA都會選擇先進行數據的垂直拆分,由於這樣的成本最早,最符合這個時期所追求的最大投入產出比。然而,隨着業務的不斷擴張,系統負載的持續增加,在系統穩定一段時期以後,通過了垂直拆分以後的數據庫集羣可能又再一次不堪重負,遇到了性能瓶頸。
若是咱們再一次像最開始那樣繼續細分模塊,進行數據的垂直切分,那咱們可能在不久的未來,又會遇到如今所面對的一樣的問題。並且隨着模塊的不斷的細化,應用系統的架構也會愈來愈複雜,整個系統極可能會出現失控的局面。
這時候咱們就必需要經過數據的水平切分的優點,來解決這裏所遇到的問題。並且,咱們徹底沒必要要在使用數據水平切分的時候,推倒以前進行數據垂直切分的成果,而是在其基礎上利用水平切分的優點來避開垂直切分的弊端,解決系統複雜性不斷擴大的問題。而水平拆分的弊端(規則難以統一)也已經被以前的垂直切分解決掉了,讓水平拆分能夠進行的駕輕就熟。
node
在數據切分處理中,特別是水平切分中,中間件最終要的兩個處理過程就是數據的切分、數據的聚合。
選擇合適的切分規則,相當重要,由於它決定了後續數據聚合的難易程度,甚至能夠避免跨庫的數據聚合
處理。
myCAT數據分片拆分原則:
mysql
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;),因而,規則開始起效。
Server A | Server B |
1 | 1 |
2 | 2 |
3 | 3 |
咱們拿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=' ';
別忘了把「log-slave-update」也註釋掉。
重啓後登陸192.168.0.104,輸入如下命令
stop slave; change master to master_host=' ';
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;
配置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>
<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分片規則大全。
經常使用的根據主鍵或非主鍵的分片規則配置:
經過在配置文件中配置可能的枚舉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
/** * defaultNode 默認節點:小於0表示不設置默認節點,大於等於0表示設置默認節點 * 默認節點的做用:枚舉分片時,若是碰到不識別的枚舉值,就讓它路由到默認節點 * 若是不配置默認節點(defaultNode值小於0表示不配置默認節點),碰到 * 不識別的枚舉值就會報錯, * like this:can't find datanode for sharding column:column_name val:ffffffff */
<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);
<function name="func1" class="org.opencloudb.route.function.PartitionByLong"> <property name="partitionCount">4</property> <property name="partitionLength">256</property> </function>
<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
<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>
<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>配置說明:
<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>
String idVal = "0";
Assert.assertEquals(true, 7 == autoPartition.calculate(idVal));
idVal = "45a";
Assert.assertEquals(true, 2 == autoPartition.calculate(idVal));
<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));
<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
<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));
<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
以上全部規則每種都有特定使用場景,能夠選擇性使用!