天天看點

murmurhash3 學習筆記

背景

由于項目中有封包排重需求,是以會将封包字元串作為分布式鎖key。

考慮到封包不定長并且散列性不太好,如其作為鎖key,特别是當key值過大時,使用redis進行讀寫都會有相對的性能下降。

參考文獻裡測試對比:
長度為10:寫平均耗時0.053ms,讀0.040ms
長度為20000:寫平均耗時0.352ms,讀0.084ms           

一種簡單的方案是對封包進行一次md5,然後拿來做key。

考慮到md5的性能并不高,找到了更合适的murmurhash3非加密雜湊演算法:

murmurhash3 學習筆記

算法圖例

murmurhash3 學習筆記

(可以看到裡面有c1, c2, n 三個很特别的常量,據算法作者Austin Appleby講,都是他血汗地用大量測試資料調測出來的,為了不被其他好事之徒當頭一暴擊,他保留繼續微調的權利。)

參考文檔裡都說murmurhash3棒棒的,但是不實踐下還是不能随意拿來進獻給Boss, 我找了一個代碼,做了一個性能對比,測試使用了Google的guava裡的Hashing.murmur3_32(),對比apache的commons-lang3的md5Hex算法。

測試源碼

public class HashTest {

    public static String md5Test(String primaryKey) {
        return DigestUtils.md5Hex(primaryKey).substring(0, 4) + "_" + primaryKey;
    }

    public static String murmur3Test(String primaryKey) {
        return Hashing.murmur3_32().hashString(primaryKey, StandardCharsets.UTF_8).toString().substring(0, 4) + 
            "_" + primaryKey;
    }
}           

跑測試源碼如下(為友善直接用了Spock單測腳本):

@Unroll
class HashTestSpec extends Specification {

    def "HashTest md5 & murmurhash3"() {
        long startTime=System.currentTimeMillis()
        for (int i = 0; i < 10000; i++) {
            HashTest.md5Test(getRandomString(10))
        }
        long endTime=System.currentTimeMillis()
        print "1萬次md5算法程式運作時間: " + (endTime - startTime ) + "ms"
        print "\n"

        long startTime2=System.currentTimeMillis()
        for (int i = 0; i < 10000; i++) {
            HashTest.murmur3Test(getRandomString(10))
        }
        long endTime2=System.currentTimeMillis()
        print "1萬次murmurhash3算法程式運作時間: " + (endTime2 - startTime2 ) + "ms"
        expect:
        1==1
    }
}           

對比測試結果:

1萬次md5算法程式運作時間: 235ms

1萬次murmurhash算法程式運作時間: 73ms

有4倍的差距。

按參考文檔建議,換成murmur3_128,1萬次murmurhash算法程式運作時間: 51ms, 果然更優秀。

getRandomString的參考代碼也一并附上,友善好事之徒做複現。

public static String getRandomString(int length){
        String str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        Random random=new Random();
        StringBuffer sb=new StringBuffer();
        for(int i=0;i<length;i++){
            int number=random.nextInt(62);
            sb.append(str.charAt(number));
        }
        return sb.toString();
    }           

參考

繼續閱讀