1. 程式人生 > >資料庫中介軟體分片演算法之stringhash

資料庫中介軟體分片演算法之stringhash

前言

又是一個夜黑風高的晚上,帶上無線耳機聽一曲。突然很感慨一句話:生活就像心電圖,一帆風順就證明你掛了。 就如同我們幹運維的,覺得很簡單的事情,有時候能幹出無限可能。還是言歸正傳吧,這一次我們來說說stringhash分割槽演算法。

1.hash分割槽演算法
2.stringhash分割槽演算法
3.enum分割槽演算法
4.numberrange分割槽演算法
5.patternrange分割槽演算法
6.date分割槽演算法
7.jumpstringhash演算法

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中的

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開始索引計數

接下來我們來詳細介紹一下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 = 31*h + s.charAt(i)是等同於h = (h << 5) - h + s.charAt(i)。大家是不是還是雲裡霧裡的。你可以去看文章結尾關於這一點的詳細解釋。

這裡我們把這個公式分解一下,根據上述的公式,我們能推匯出下列算術式:
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。
今天到這兒,後續將繼續分享其他的演算法。謝謝大家支援