1. 程式人生 > >海量資料處理-Topk引發的思考

海量資料處理-Topk引發的思考

海量資料處理–TopK引發的思考

三問海量資料處理:

  1. 什麼是海量資料處理,為什麼出現這種需求?
  2. 如何進行海量資料處理,常用的方法和技術有什麼?
  3. 如今分散式框架已經很成熟了,為什麼還用學習海量資料處理的技術?

什麼是海量資料處理,為什麼出現這種需求?

如今網際網路產生的資料量已經達到PB級別,如何在資料量不斷增大的情況下,依然保證快速的檢索或者更新資料,是我們面臨的問題。所謂海量資料處理,是指基於海量資料的儲存、處理和操作等。因為資料量太大無法在短時間迅速解決,或者不能一次性讀入記憶體中。

從何解決我們上面提到的兩個問題:時間問題和空間問題

時間角度,我們需要設計巧妙的演算法配合合適的資料結構來解決;例如常用到的資料結構:雜湊、點陣圖、堆、資料庫(B樹),紅黑樹、倒排索引、Trie樹等。空間角度:我們只需要分而治之即可,兩大步操作:分治、合併。MapReduce就是基於這個原理。

如今分散式框架已經很成熟了,為什麼還用學習海量資料處理的技術?

這個問題,就相當於為什麼要學習演算法,因為大部分人在工作中都很少用到這些演算法和高階的資料機構。武俠講究內外兼修才是集大成著。演算法就是內功,可以不用,不可以沒有。演算法更多的是理解和推到的過程,這就是為什麼很多學數學和物理專業的學生,可以在計算機行業做的很好,因為他們的數學好,可以很好的理解各種推到過程和演算法原理。最後一句話,知其然而後知其所以然,技術更好的成長。

這篇文章,我採用總分的結構進行寫作,我們每次都會丟擲一個問題,這個問題對應的海量資料處理的一個方面,我們從下面幾個角度分析:

  1. 對應海量資料處理的那個技術,以及是時間角度和空間角度
  2. 分析這個問題,如何解決
  3. 提出解決方案,進行分析
  4. 詳細講解這處理這個問題時,用到的技術,例如什麼是堆,hash等

丟擲問題:尋找熱門查詢

任何的搜尋引擎(百度、Google等)都會將使用者的查詢記錄到日誌檔案。對於百度這種公司,我們知道每天有很多Query查詢,假設有100G的日誌檔案,只有一臺4G記憶體的電腦,現在讓你統計某一天熱門查詢的Top 100. (Top 100的統計是很有用意義的,可以提供給使用者,知道目前什麼是最熱門的,關注熱點,例如金庸老先生的去世,現在應該就是熱門。)

分析問題 根據題目的描述,我們有100G日誌檔案,只有一臺4G的電腦,這是空間不足的問題,我們需要分而治之。

  1. 劃分檔案,將100G日誌檔案劃分成K的小檔案。K >= 100G / 4G = 25。這裡我們去K=50(為什麼不取25呢?),將所有的Query劃分到50個小檔案中,然後統計每一個小檔案中的Query的頻率,之後合併結果,得到最後的Top 100的Query。

  2. 需要我們處理的兩個點:劃分和合並。 劃分:保證相同的Query劃分到同一個小檔案中。 統計:統計每個小檔案中Query的頻率 合併:如何快速的合併得到結果。 劃分的時候我們需要用到一個叫做hash的技術,兩步走,

  3. hash(string) = key_index,

  4. File_index = Key_index % K(50) 劃分:

設計一個hash函式,需要保證的正string -> index的轉換,這裡不需要考慮衝突,只有保證相同的string對應一個key_index即可,一個好的hash函式會將均勻分佈的資料,均勻分佈到不同的檔案中去。常用的Hash函式和原理

C++程式碼:

const unsigned int BIG_MOD = 1000003;
inline unsigned int hash_code(std::string&& key) {
    unsigned int value = 0;
    for(int i = 0; i < key.size(); i++) {
        value = value * 31 + (unsigned int) key[i];
        value %= BIG_MOD;
    }
    return value;
}

統計:

這裡給出兩種方法, hash_map和Trie數。每一個小檔案有一個輸出結果,這裡我們只需要輸出前Top 100頻率進行。 Hash_map這個原理跟前面的提到的hash函式基本一樣,只是需要解決衝突問題,之後會在別的文章中專門提出。C++的結構map,或者Java中Hashmap或者Python中的dict基本使用方式一樣。Map[query] += 1. HashMap的不足在於我們空間使用多,對於查詢這種Query,很多的查詢都是一樣的,我們可以使用Trie樹來解救,這是一個字首樹的結果,例如 Querys = {“我愛你”,“愛你們”,“我”,“我”,“我愛你“,”金庸“},構造的樹的格式如需:

資料結構:

const int MAXN = 10000;
struct TreeNode {
    string key;
    int cnt;
    int flag;
    struct TreeNode *next[MAXN];
};

具體的Trie樹細節,這裡就不在繼續展開,可以搜尋相關文章,後續可能會繼續退出Trie樹的相關文章。 基於上述兩種方法,我們都可以直接得到Top 100的結果。輸出檔案格式如下: Query1 Count1 。 。 。 Queryi Counti

合併:

讀取所有的結果在記憶體中,根據Count排序取前100,時間複雜度是O(nlogn),n是所有個數n=50*100. 但是注意到我們只要前k個最大的,我可以使用堆排序,使用一個叫做最小堆的問題。維護k(100)大小的最小堆,每次插入新的元素,去掉最小的元素,時間複雜度O(k + (n-k)logk),比排序小很多。 這裡同樣可以使用Trie樹,和上述的方式一樣,注意這可以轉化一個取第k個大小的問題,我們也可以使用快速排序中劃分函式,進行找到第k個,前面的就是我們需要的目標。

結束: 到這裡我們的問題已經可以結束了,但是卻有幾個問題需要提出來。

  1. 這真的是熱門Query統計嗎?百度等公司是這麼做的嗎?相似的Query怎麼處理?如何實時的更新熱門榜單呢?等等的問題,需要我們思考。
  2. Hash劃分的時候,劃分檔案不平衡怎麼辦?如何保證平均劃分?

注:這裡提到的hash,堆和Trie和快排的劃分每一個技術,都可以拿出來單獨一篇文章,讀者可以先查詢相關資料,之後我們也會推出相關的文章。