1. 程式人生 > >大資料查重去重方案及效能優化

大資料查重去重方案及效能優化

最近做針對百萬級別的資料的去重工作,現抽空寫下筆記。

做這個去重,是基於前同事的基礎上做改造,原來是用的simHash演算法做文字相似計算,上網查了下,simHash演算法是相對來說,在大資料領域比較受歡迎的查重演算法,話不多說,來一步步說下我的設計之路。

一、先簡單介紹下simHash.

傳統的Hash演算法只負責將原始內容儘量均勻隨機地對映為一個簽名值,原理上僅相當於偽隨機數產生演算法。傳統的hash演算法產生的兩個簽名,如果原始內容在一定概率下是相等的;如果不相等,除了說明原始內容不相等外,不再提供任何資訊,因為即使原始內容只相差一個位元組,所產生的簽名也很可能差別很大。所以傳統的Hash是無法在簽名的維度上來衡量原內容的相似度,而SimHash本身屬於一種區域性敏感雜湊演算法,它產生的hash簽名在一定程度上可以表徵原內容的相似度。

“你媽媽喊你回家吃飯,回家羅回家羅” 和 “你媽媽叫你回家吃飯,回家羅回家羅”。

通過simhash計算結果為:

  1000010010101101111111100000101011010001001111100001001011001011

  1000010010101101011111100000101011010001001111100001101010001011

通過傳統hash計算為:

0001000001100110100111011011110

1010010001111111110010110011101

二、具體的應用

2.1 改造前

好了,基本要用的演算法有了,接下來就是如何好好利用這個演算法,設計一個合理的功能。

一開始我採用的設計是,分批次遍歷所有的資料,使用simHash演算法,將資源中的文字計算為simHash簽名,並將簽名分成4段Long,用這4個long片段去資源指紋分析表中用查詢比對資料

,將所有4個片段中有一個相似的指紋全部取出做海明距離的計算,海明距離小於3的再對文字做相似度計算,得出相似度最大的資源所屬的堆號,標記當前資源與其堆號相同,入庫。

這樣最後分出來的結果就是,相似的資料堆號相同,也就是把所有的資料分成一批一批的,同批的是長得差不多的。

光文字有點難懂,下面上邏輯流程圖。

根據上面這個思路,進行開發,完成後實際測試中發現,這樣設計存在嚴重的效能問題。因為資料量大,要做多次的資料庫讀取,資料庫插入,資料庫查詢,所消耗的效能是非常巨大的。經過初步計算,400w條資料,可能就需要跑25小時以上。所以,這個方案,就直接否掉了。

2.2 改造後

問題

那麼,在大資料的時代,效能是非常重要的,怎麼解決,也是很頭疼,下面我再講講我具體的解決方案,不敢說是最好的,但至少相對來說,把效能提高到一條資料大概2ms

左右吧。

基於原有的分堆思路,也就是最後的目的是把相似的資料標記上一個相同的堆號。

那麼首先,考慮到的一個問題就是,資料的simHash指紋,如何維護的問題,當對資料做修改的時候相應的simHash應該重新計算。

其次,考慮到效能問題,像做simHash簽名和重複分析,其實是兩件事,是可以分開來做的,也就是,至少這樣,可以把效能壓力分散開來。

再來,就是多次讀取資料庫的問題。

一,要多次讀取目標資料;

二、要多次查詢分析表,來與當前目標資料做對比,以確定重複物件。

對應的解決方案

針對問題一,分量一次讀取,首先就是讀一次很慢,其次就是資料量大,佔記憶體多。通過蒐羅整個百度,發現Mongo有一個神技能可以同時解決速度跟記憶體問題。那就是使用DBCursor游標。DBCursor游標有點類似指標,使用.next()一個個按順序讀取資料庫資料,使用完用close()進行關閉連線。

針對問題二,考慮到要多次查詢,光來回的資料庫連線互動就非常耗時,更何況還要做查詢,即使加了索引做查詢,資料量大的時候也是非常慢的。怎麼解決?

這個時候,就得在記憶體跟效能之間權衡。一個simHash存成String型別,加上4個Long,400w條資料其實也就幾百M的事,所以當然是選擇放在記憶體裡面等候做對比。

但是,使用for+if來做對比?那就真的是浪費記憶體還耗效能了。

於是我考慮到用hashMap的鍵值查詢,hashMap的鍵值查詢,很好的結合了陣列和連結串列的優勢,可以說查詢速度是相當相當快的了。

用4個simHash片段作為key,對應的完整的simHash+堆號作為value。在對比的時候進行map.get("key1"),這樣就可以在記憶體中,快速的完成大資料之間的對比。比雙for迴圈這種蠢方法不知道快了多少個等級。

這裡我為什麼是把simHash切成4個片段而不是3個、5個、6個?

這個要根據自己伺服器的能力決定了,片段約多準確率越高、但是相對的就越佔記憶體跟耗效能。

通過DBCursor游標和hashMap的使用,其實已經在很大程度上調整了效能問題。那還有一個入庫問題,我採用的是當積累到一定量的時候,再做批量入庫操作,這樣可以省了很多的資料庫連線時間。

以上純屬我個人的一次資料查重經驗,僅做參考,歡迎各位有更好的方案共享。