背景
由于項目中有封包排重需求,是以會将封包字元串作為分布式鎖key。
考慮到封包不定長并且散列性不太好,如其作為鎖key,特别是當key值過大時,使用redis進行讀寫都會有相對的性能下降。
參考文獻裡測試對比:
長度為10:寫平均耗時0.053ms,讀0.040ms
長度為20000:寫平均耗時0.352ms,讀0.084ms
一種簡單的方案是對封包進行一次md5,然後拿來做key。
考慮到md5的性能并不高,找到了更合适的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();
}