先說一下情境,記錄檔中有大約三千萬行資料,大小為1.2G,格式為IP,TIME,現在要針對IP欄位進行數量統計重複的次數,以便制定規則來控制使用者的惡意註冊。
關於如何讀取,並統計這三千萬的資料,有三個思路:
第一個:使用JAVA的BufferedReader,分批讀取資料,然後再利用map統計結果。
第二個:使用JAVA的BufferedReader,分批讀取資料,然後把結果直接存入到redis的sorted set中。
第三個:多線程環境下,結合第二個思路。
第一個思路,執行不到三分鐘,記憶體已經溢出。
第二個思路,一秒鐘大約能存入到redis中1667條資料,速度還不錯,一小時能統計600萬左右,但要統計完成,大約需要5個小時,考慮到資料越多,讀寫Redis的速度就越慢,估計要6個小時左右。
第三個思路,由於redis是單線程工作,此次又全部是寫入的工作,所以多線程對效能提高的能力有限,如果是有讀有寫,可利用Redis的主從模式,寫主庫,讀從庫,多線程也能提高效率。
三個思路都行不通,最終向一個剛來加班的大牛請教,結果利用linux命令列,15分鐘統計完成。於是,我就有了時間寫下這篇文章,記錄一下。下面附上第二個思路的JAVA實現,以及請教大牛後的linux命令列實現。
shell版【15分鐘統計完成】
date
cat regIp.txt | awk -F'\t' '{ print $1}' | sort | uniq -c | sort -rn | head -n100
date
JAVA版本【6小時統計完成】:
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Date;
import redis.clients.jedis.Jedis;
public class ReadLogFile {
private static Jedis jedis;
static {
jedis = new Jedis("192.168.1.100");
jedis.connect();
jedis.auth("redisPwd#");
jedis.select(10);
}
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
readFile();
}
private static void readFile() throws IOException {
long start = System.currentTimeMillis();
System.out.println(new Date());
String fileName = "log.txt";
File file = new File(fileName);
BufferedInputStream fis = new BufferedInputStream(new FileInputStream(file));
BufferedReader reader = new BufferedReader(new InputStreamReader(fis,"utf-8"),100*1024*1024);// 用100M的緩衝讀取文字檔
String keyTotal = "ipInfo";
String line = "";
while((line = reader.readLine()) != null){
if(line.indexOf("state") >=0){
continue;
}
String[] arrs = line.split("\t");
if(arrs == null || arrs.length != 2){
continue;
}
jedis.zincrby(keyTotal, 1, arrs[0]);
}
long cost = (System.currentTimeMillis() - start);
System.out.println("cost = " + cost + "ms, means " + (cost/1000.0) + " s");
if(reader != null){
reader.close();
}
}
}