數據庫中間件分片算法之stringhash

前言

又是一個夜黑風高的晚上,帶上無線耳機聽一曲。忽然很感慨一句話:生活就像心電圖,一路順風就證實你掛了。 就如同咱們幹運維的,以爲很簡單的事情,有時候能幹出無限可能。仍是言歸正傳吧,這一次咱們來講說stringhash分區算法。java

1.hash分區算法
2.stringhash分區算法
3.enum分區算法
4.numberrange分區算法
5.patternrange分區算法
6.date分區算法
7.jumpstringhash算法mysql

StringHash分區算法的配置

<tableRule name="rule_hashString">
    <rule>
        <columns>name</columns>
        <algorithm>func_hashString</algorithm>
    </rule>
</tableRule>

<function name="func_hashString" class="StringHash">
    <property name="partitionCount">3,2</property>
    <property name="partitionLength">3,4</property>
    <property name="hashSlice">0:3</property>
</function>

和以前的hash算法同樣。須要在rule.xml中配置tableRule和function。算法

  • tableRule標籤,name對應的是規則的名字,而rule標籤中的columns則對應的分片字段,這個字段必須和表中的字段一致。algorithm則表明了執行分片函數的名字。
  • function標籤,name表明分片算法的名字,算法的名字要和上面的tableRule中的<algorithm>標籤相對應。class:指定分片算法實現類。property指定了對應分片算法的參數。不一樣的算法參數不一樣。

1.partitionCount:指定分區的區間數,具體爲 C1 +C2 + ... + Cn
2.partitionLength:指定各區間長度,具體區間劃分爲 [0, L1), [L1, 2L1), ..., [(C1-1)L1, C1L1), [C1L1, C1L1+L2), [C1L1+L2, C1L1+2L2), ... 其中,每個區間對應一個數據節點。
3.hashSlice:指定參與hash值計算的key的子串。字符串從0開始索引計數sql

接下來咱們來詳細介紹一下StringHash的工做原理。咱們以上面的配置爲例。數據庫

1.在啓動的時候,兩個數組點乘作運算,獲得取模數。

2.兩個數組進行叉乘,得出物理分區表。

3.根據hashSlice二維數組,把分片字段的字符串進行截取。

字符串截取的範圍是hashSlice[0]到hashSlice[1]。好比我這裏設置0,3。‘buddy'這個字符串就會截取出bud,相似數據庫中的substring函數。數組

4.將截取出來的字符串作hash,這個hash的計算方法我研究了一下dble的源代碼。源代碼以下:

/**
  * String hash:s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] <br>
  * h = 31*h + s.charAt(i); => h = (h << 5) - h + s.charAt(i); <br>
  *
  * @param start hash for s.substring(start, end)
  * @param end   hash for s.substring(start, end)
  */
 public static long hash(String s, int start, int end) { 
     if (start < 0) {
         start = 0;
     }
     if (end > s.length()) {
         end = s.length();
     }
     long h = 0;
     for (int i = start; i < end; ++i) {
         h = (h << 5) - h + s.charAt(i);
     }
     return h;
 }

這段源代碼的意思其實上面有解釋。算法是s[0]*31^(n-1) + s[1]31^(n-2) + ... + s[n-1]。而後接下來它說明h = 31h + s.charAt(i)是等同於h = (h << 5) - h + s.charAt(i)。你們是否是仍是雲裏霧裏的。你能夠去看文章結尾關於這一點的詳細解釋。less

這裏咱們把這個公式分解一下,根據上述的公式,咱們能推導出下列算術式:
i=0 -> h = 31 * 0 + s.charAt(0)
i=1 -> h = 31 * (31 * 0 + s.charAt(0)) + s.charAt(1)
i=2 -> h = 31 * (31 * (31 * 0 + s.charAt(0)) + s.charAt(1)) + s.charAt(2)
i=3 -> h = 31 * (31 * (31 * (31 * 0 + s.charAt(0)) + s.charAt(1)) + s.charAt(2)) + s.charAt(3)
.......以此內推運維

假設咱們的字符串是"buddy",咱們截取0-3字符串,咱們來算一下。根據上面的函數來寫段java代碼編譯運行。函數

public class test {
        public static void main(String args[]) {
                String Str = new String("buddy");
                System.out.println(hash(Str,0,3));
        }

public static long hash(String s, int start, int end) {
     if (start < 0) {
         start = 0;
     }
     if (end > s.length()) {
         end = s.length();
     }
     long h = 0;
     for (int i = start; i < end; ++i) {
         h = (h << 5) - h + s.charAt(i);
     }
     return h;
   }
}

[root@mysql5 java]# javac test.java 
[root@mysql5 java]# java test
97905

經過運行程序截取字符串buddy,0-3獲得的結果是97905。那麼這個結果是怎麼算出來的。首先截取0,3,最終截取的是三個字符串bud。索引從0開始計數對應的就是i=2。根據i=2的公式:
i=2 -> h = 31 * (31 * (31 * 0 + s.charAt(0)) + s.charAt(1)) + s.charAt(2)
咱們能夠查詢ascii表
s.charAt(0),是算"b"這個字母的ASCII值,十進制數字爲98
s.charAt(1),是算"u"這個字母的ASCII值,十進制數字爲117
s.charAt(1),是算"d"這個字母的ASCII值,十進制數字爲100
性能

把上述三個值帶入到公式得出 31 * (31 * (31 * 0 + 98) + 117) + 100 = 97905。正好和咱們程序計算的值同樣。

5.對計算出來的值取模,而後落在指定的分區中。

97905 mod 17 =2 根據取模的值,落在了dn1分區,dn1分區是存放(0,3)的。

6.讓咱們建表來測試一下,是否是落在第1個分區。

如圖所示,當咱們執行插入name='buddy',而後再一次查詢的name='buddy'的時候,直接路由到了第一個分區。和咱們以前計算的結果一致。

注意事項

  1. 該分區算法和hash分區算法有一樣的限制(注意事項3除外)
  2. 分區字段爲字符串類型

後記

今天介紹的stringhash和hash分區算法大體相同,只不過對於字符串需先計算出hash值。該算法有個經典的數字叫31。這個數字大有來頭。《Effective Java》中的一段話說明了爲何要用31,由於31是一個奇質數,若是選擇一個偶數的話,乘法溢出信息將丟失。由於乘2等於移位運算。使用質數的優點不太明顯,但這是一個傳統。31的一個很好的特性是乘法能夠用移位和減法來代替以得到更好的性能:31*i==(i<<5)-i。現代的 Java 虛擬機能夠自動的完成這個優化。

The value 31 was chosen because it is an odd prime. If it were even and the multiplication overflowed, information would be lost, as multiplication by 2 is equivalent to shifting. The advantage of using a prime is less clear, but it is traditional. A nice property of 31 is that the multiplication can be replaced by a shift and a subtraction for better performance: 31 * i == (i << 5) - i. Modern VMs do this sort of optimization automatically.

若是你前面沒看懂前面那段java代碼,如今應該明白(h << 5) - h的結果其實就等於31*h。 今天到這兒,後續將繼續分享其餘的算法。謝謝你們支持!

相關文章
相關標籤/搜索