Java使用極小的內存完成對超大數據的去重計數,用於實時計算中統計UV


 

Java使用極小的內存完成對超大數據的去重計數,用於實時計算中統計UV – lxw的大數據田地 http://lxw1234.com/archives/2015/09/516.htm

Java使用極小的內存完成對超大數據的去重計數,用於實時計算中統計UV

 編程語言  lxw1234@qq.com  3年前 (2015-09-25)  15132℃  1評論

關鍵字:streamlib、基數估計、實時計算uv、大數據、去重計數

一直在想如何在實時計算中完成對海量數據去重計數的功能,即SELECT COUNT(DISTINCT) 的功能。比如:從每天零點開始,實時計算全站累計用戶數(UV),以及某些組合維度上的用戶數,這里的用戶假設以Cookieid來計。

想想一般的解決辦法,在內存中使用HaspMap、HashSet?或者是在Redis中以Cookieid為key?感覺都不合適,在數以億計用戶的業務場景下,內存顯然也成了瓶頸。

如果說,實時計算的業務場景中,對UV的計算精度並不要求100%(比如:實時的監測某一網站的PV和UV),那么可以考慮采用基數估計算法來統計。這里有一個Java的實現版本 stream-lib:https://github.com/addthis/stream-lib

采用基數估計算法目的就是為了使用很小的內存,即可完成超大數據的去重計數。號稱是只使用幾KB的內存,就可以完成對數以條數據的去重計數。但基數估計算法都不是100%精確的,誤差在0~2%之間,一般是1%左右。

本文使用stream-lib來嘗試對兩個數據集進行去重計數。相關的文檔和下載見文章最后。

測試數據集1:

  • 文件名:small_cookies.txt
  • 文件內容:每個cookieid一行
  • 文件總記錄數:14892708
  • 去重記錄數:3896911
  • 文件總大小:350153062(約334M)
  1. [liuxiaowen@dev site_raw_log]$ head -5 small_cookies.txt
  2. 7EDCF13A03D387548FB2B8
  3. da5f0196-56036078075b9f-14892137
  4. 1D0A83B604ADD4558970EE
  5. 3DF76E7100025F553B1980
  6. 72C756700C3CAA56035EE0
  7. [liuxiaowen@dev site_raw_log]$ wc -l small_cookies.txt
  8. 14892708 small_cookies.txt
  9. [liuxiaowen@dev site_raw_log]$ awk '!a[$0]++{print $0}' small_cookies.txt | wc -l
  10. 3896911
  11. [liuxiaowen@dev site_raw_log]$ ll small_cookies.txt
  12. -rw-rw-r--. 1 liuxiaowen liuxiaowen 350153062 Sep 25 10:50 small_cookies.txt

測試數據集2:

  • 文件名:big_cookies.txt
  • 文件內容:每個cookieid一行
  • 文件總記錄數:547631464
  • 去重記錄數:190264959
  • 文件總大小:12610638153(約11.8GB)
  1. --總記錄數
  2. spark-sql> select count(1) from big_cookies;
  3. 547631464
  4. Time taken: 7.311 seconds, Fetched 1 row(s)
  5. --去重記錄數
  6. spark-sql> select count(1) from (select cookieid from big_cookies group by cookieid) x;
  7. 190264959
  8. Time taken: 80.516 seconds, Fetched 1 row(s)
  9.  
  10. hadoop fs -getmerge /hivedata/warehouse/liuxiaowen.db/big_cookies/* big_cookies.txt
  11.  
  12. [liuxiaowen@dev site_raw_log]$ wc -l big_cookies.txt
  13. 547631464 cookies.txt
  14. //總大小
  15. [liuxiaowen@dev site_raw_log]$ ll big_cookies.txt
  16. -rw-r--r--. 1 liuxiaowen liuxiaowen 12610638153 Sep 25 13:25 big_cookies.txt
  17.  

普通方法測試

所謂普通方法,就是遍歷文件,將所有cookieid放到內存的HashSet中,而HashSet的size就是去重記錄數。

代碼如下:

  1. package com.lxw1234.streamlib;
  2.  
  3. import java.io.BufferedReader;
  4. import java.io.File;
  5. import java.io.FileReader;
  6. import java.io.IOException;
  7. import java.util.HashSet;
  8. import java.util.Set;
  9.  
  10. public class Test {
  11. public static void main(String[] args) {
  12. Runtime rt = Runtime.getRuntime();
  13. Set set = new HashSet();
  14. File file = new File(args[0]);
  15. BufferedReader reader = null;
  16. long count = 0l;
  17. try {
  18. reader = new BufferedReader(new FileReader(file));
  19. String tempString = null;
  20. while ((tempString = reader.readLine()) != null) {
  21. count++;
  22. set.add(tempString);
  23. if(set.size() % 5000 == 0) {
  24. System.out.println("Total count:[" + count + "] Unique count:[" + set.size() + "] FreeMemory:[" + rt.freeMemory() + "] ..");
  25. }
  26. }
  27. reader.close();
  28. } catch (Exception e) {
  29. e.printStackTrace();
  30. } finally {
  31. if (reader != null) {
  32. try {
  33. reader.close();
  34. } catch (IOException e1) {}
  35. }
  36. }
  37. System.out.println("Total count:[" + count + "] Unique count:[" + set.size() + "] FreeMemory:[" + rt.freeMemory() + "] ..");
  38. }
  39. }
  40.  

指定使用10M的內存運行,命令為:

  1. java -cp /tmp/teststreamlib.jar -Xms10M -Xmx10M -XX:PermSize=10M -XX:MaxPermSize=10M \
  2. com.lxw1234.streamlib.Test /home/liuxiaowen/site_raw_log/small_cookies.txt

運行結果如下:

streamlib

10M的內存,僅僅夠存65000左右的cookieid,之后就報錯,內存不夠了。大數據集更不用說。

基數估計方法測試

采用streamlib中的基數估計算法實現ICardinality,對兩個結果集的總記錄數和去重記錄數進行統計,代碼如下:

  1. package com.lxw1234.streamlib;
  2.  
  3. import java.io.BufferedReader;
  4. import java.io.File;
  5. import java.io.FileReader;
  6. import java.io.IOException;
  7.  
  8. import com.clearspring.analytics.stream.cardinality.AdaptiveCounting;
  9. import com.clearspring.analytics.stream.cardinality.ICardinality;
  10.  
  11. public class TestCardinality {
  12.  
  13. public static void main(String[] args) {
  14. Runtime rt = Runtime.getRuntime();
  15. long start = System.currentTimeMillis();
  16. long updateRate = 1000000l;
  17. long count = 0l;
  18. ICardinality card = AdaptiveCounting.Builder.obyCount(Integer.MAX_VALUE).build();
  19. File file = new File(args[0]);
  20. BufferedReader reader = null;
  21. try {
  22. reader = new BufferedReader(new FileReader(file));
  23. String tempString = null;
  24. while ((tempString = reader.readLine()) != null) {
  25. card.offer(tempString);
  26. count++;
  27. if (updateRate > 0 && count % updateRate == 0) {
  28. System.out.println("Total count:[" + count + "] Unique count:[" + card.cardinality() + "] FreeMemory:[" + rt.freeMemory() + "] ..");
  29. }
  30. }
  31. reader.close();
  32. } catch (Exception e) {
  33. e.printStackTrace();
  34. } finally {
  35. if (reader != null) {
  36. try {
  37. reader.close();
  38. } catch (IOException e1) {}
  39. }
  40. }
  41. long end = System.currentTimeMillis();
  42. System.out.println("Total count:[" + count + "] Unique count:[" + card.cardinality() + "] FreeMemory:[" + rt.freeMemory() + "] ..");
  43. System.out.println("Total cost:[" + (end - start) + "] ms ..");
  44. }
  45. }
  46.  
  • 測試數據集1

指定使用10M的內存運行,測試數據集1命令為:

  1. java -cp /tmp/stream-2.9.1-SNAPSHOT.jar:/tmp/teststreamlib.jar -Xms10M -Xmx10M -XX:PermSize=10M -XX:MaxPermSize=10M \
  2. com.lxw1234.streamlib.TestCardinality /home/liuxiaowen/site_raw_log/small_cookies.txt

運行結果如下:

streamlib

  • 測試數據集2

同樣指定使用10M的內存運行,測試數據集2命令為:

  1. java -cp /tmp/stream-2.9.1-SNAPSHOT.jar:/tmp/teststreamlib.jar -Xms10M -Xmx10M -XX:PermSize=10M -XX:MaxPermSize=10M \
  2. com.lxw1234.streamlib.TestCardinality /home/liuxiaowen/site_raw_log/big_cookies.txt

運行結果為:

streamlib

測試結果

streamlib

 

測試結果來看,基數估計算法統計的結果中誤差的確是0~2%,如果可以接受這個誤差,那么這個方案完全可以用於實時計算中的不同維度UV統計中。

從運行結果圖上可以看到,雖然指定了10M內存,但空閑內存(FreeMemory)一直在差不多7M以上,也就是說,5.4億的數據去重計數,也僅僅使用了3M左右的內存。

相關下載

以上程序需要依賴stream-2.9.1-SNAPSHOT.jar,我編譯好了一份,

點擊下載stream-2.9.1-SNAPSHOT.jar

你也可以從官網中下載源碼,編譯。

相關文章:

http://blog.csdn.net/hguisu/article/details/8433731

http://m.oschina.net/blog/315457

您可以關注 lxw的大數據田地 ,或者 加入郵件列表 ,隨時接收博客更新的通知郵件。

 

如果覺得本博客對您有幫助,請 贊助作者 。

轉載請注明:lxw的大數據田地 » Java使用極小的內存完成對超大數據的去重計數,用於實時計算中統計UV

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM