海量資料相似度計算之simhash短文字查詢

發表於2013-09-10

在前一篇文章 《海量資料相似度計算之simhash和海明距離》 介紹了simhash的原理,大家應該感覺到了演算法的魅力。但是隨著業務的增長 simhash的資料也會暴增,如果一天100w,10天就1000w了。我們如果插入一條資料就要去比較1000w次的simhash,計算量還是蠻大,普通PC 比較1000w次海明距離需要 300ms ,和5000w資料比較需要1.8 s。看起來相似度計算不是很慢,還在秒級別。給大家算一筆賬就知道了:

隨著業務增長需要一個小時處理100w次,一個小時為3600 *1000 = 360w毫秒,計算一下一次相似度比較最多隻能消耗 360w / 100w = 3.6毫秒。300ms慢嗎,慢!1.8S慢嗎,太慢了!很多情況大家想的就是升級、增加機器,但有些時候光是增加機器已經解決不了問題了,就算增加機器也不是短時間能夠解決的,需要考慮分散式、客戶預算、問題解決的容忍時間?頭大時候要相信人類的智慧是無窮的,泡杯茶,聽下輕音樂:)暢想下宇宙有多大,宇宙外面還有什麼東西,程式設計師有什麼問題能夠難倒呢?

加上客戶還提出的幾個,彙總一下技術問題:

  • 1、一個小時需要比較100w次,也就是每條資料和simhash庫裡的資料比較需要做到3.6毫秒。
  • 2、兩條同一時刻發出的文字如果重複也只能保留一條。
  • 3、希望保留2天的資料進行比較去重,按照目前的量級和未來的增長,2天大概在2000w — 5000w 中間。
  • 4、短文字和長文字都要去重,經過測試長文字使用simhash效果很好,短文字使用simhash 準備度不高。

目前我們估算一下儲存空間的大小,就以JAVA 來說,儲存一個simhash 需要一個原生態 lang 型別是64位 = 8 byte,如果是 Object 物件還需要額外的 8 byte,所以我們儘量節約空間使用原生態的lang型別。假設增長到最大的5000w資料, 5000w * 8byte = 400000000byte = 400000000/( 1024 * 1024) = 382 Mb,所以按照這個大小普通PC伺服器就可以支援,這樣第三個問題就解決了。

比較5000w次怎麼減少時間呢?其實這也是一個查詢的過程,我們想想以前學過的查詢演算法: 順序查詢、二分查詢、二叉排序樹查詢、索引查詢、雜湊查詢。不過我們這個不是比較數字是否相同,而是比較海明距離,以前的演算法並不怎麼通用,不過解決問題的過程都是通用的。還是和以前一樣,不使用數學公式,使用程式猿大家都理解的方式。還記得JAVA裡有個HashMap嗎?我們要查詢一個key值時,通過傳入一個key就可以很快的返回一個value,這個號稱查詢速度最快的資料結構是如何實現的呢?看下hashmap的內部結構:

simhash4

如果我們需要得到key對應的value,需要經過這些計算,傳入key,計算key的hashcode,得到7的位置;發現7位置對應的value還有好幾個,就通過連結串列查詢,直到找到v72。其實通過這麼分析,如果我們的hashcode設定的不夠好,hashmap的效率也不見得高。借鑑這個演算法,來設計我們的simhash查詢。通過順序查詢肯定是不行的,能否像hashmap一樣先通過鍵值對的方式減少順序比較的次數。看下圖:

simhash3

儲存
1、將一個64位的simhash code拆分成4個16位的二進位制碼。(圖上紅色的16位)
2、分別拿著4個16位二進位制碼查詢當前對應位置上是否有元素。(放大後的16位)
3、對應位置沒有元素,直接追加到連結串列上;對應位置有則直接追加到連結串列尾端。(圖上的 S1 — SN)

查詢
1、將需要比較的simhash code拆分成4個16位的二進位制碼。
2、分別拿著4個16位二進位制碼每一個去查詢simhash集合對應位置上是否有元素。
2、如果有元素,則把連結串列拿出來順序查詢比較,直到simhash小於一定大小的值,整個過程完成。

原理
借鑑hashmap演算法找出可以hash的key值,因為我們使用的simhash是區域性敏感雜湊,這個演算法的特點是隻要相似的字串只有個別的位數是有差別變化。那這樣我們可以推斷兩個相似的文字,至少有16位的simhash是一樣的。具體選擇16位、8位、4位,大家根據自己的資料測試選擇,雖然比較的位數越小越精準,但是空間會變大。分為4個16位段的儲存空間是單獨simhash儲存空間的4倍。之前算出5000w資料是 382 Mb,擴大4倍1.5G左右,還可以接受:)

通過這樣計算,我們的simhash查詢過程全部降到了1毫秒以下。就加了一個hash效果這麼厲害?我們可以算一下,原來是5000w次順序比較,現在是少了2的16次方比較,前面16位變成了hash查詢。後面的順序比較的個數是多少? 2^16 = 65536, 5000w/65536 = 763 次。。。。實際最後連結串列比較的資料也才 763次!所以效率大大提高!

到目前第一點降到3.6毫秒、支援5000w資料相似度比較做完了。還有第二點同一時刻發出的文字如果重複也只能保留一條和短文字相識度比較怎麼解決。其實上面的問題解決了,這兩個就不是什麼問題了。

  • 之前的評估一直都是按照線性計算來估計的,就算有多執行緒提交相似度計算比較,我們提供相似度計算伺服器也需要線性計算。比如同時客戶端傳送過來兩條需要比較相似度的請求,在伺服器這邊都進行了一個排隊處理,一個接著一個,第一個處理完了在處理第二個,等到第一個處理完了也就加入了simhash庫。所以只要服務端加了佇列,就不存在同時請求不能判斷的情況。
  • simhash如何處理短文字?換一種思路,simhash可以作為區域性敏感雜湊第一次計算縮小整個比較的範圍,等到我們只有比較700多次比較時,就算使用我們之前精準度高計算很慢的編輯距離也可以搞定。當然如果覺得慢了,也可以使用餘弦夾角等效率稍微高點的相似度演算法。

相關文章