先说一下场景,日志文件中有大约三千万行数据,大小为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);
}
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();
}
}
}