結合上面兩篇博文的內容,首先提一下hadoop的安裝和配置。
Hadoop-利用java API操作HDFS文件
Hadoop-MapReduce初步應用-統計單詞個數
上面兩篇文章中提到了如何安裝和配置hadoop。以及一些視頻資料的下載。這兩篇文章包括本文的代碼示例均是僞分佈下hadoop的開發完成的。
下面開始本文中題目所說的web日誌信息的挖掘。
首先給出web日誌文件的下載:請猛戳這裏!
有了該文件之後,可以打開看看裏面的內容,內容示例如下:
222.68.172.190 - - [18/Sep/2013:06:49:57 +0000] \"GET /images/my.jpg HTTP/1.1\" 200 19939 \"http://www.angularjs.cn/A00n\" \"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36\"
這是正常情況下一條web信息日誌所記錄的信息。(關於這裏使用到的日誌信息,請參考原創作者:海量Web日誌分析 用Hadoop提取KPI統計指標 | 粉絲日誌)
首先最終原創作者的成果。非常感謝前輩!前輩的項目使用的是eclipse結合Maven開發的工程項目。
本人在原創作者的基礎上,沒有使用Maven,僅僅使用eclipse工具下java工程項目進行的。後期項目完成以後,會附上下載地址供大家下載學習交流。
在開發寫代碼之前,需要把日誌文件上傳到HDFS文件系統中,HDFS上傳命令如下:
我這裏吧文件上傳到HDFS根目錄下。
下面開發代碼,有了上面的日誌信息以後,就需要對日誌信息就行解析,比較好的方式就是使用解析類完成,代碼如下;
private String remote_addr;// 記錄客戶端的ip地址
private String remote_user;// 記錄客戶端用戶名稱,忽略屬性"-"
private String time_local;// 記錄訪問時間與時區
private String request;// 記錄請求的url與http協議
private String status;// 記錄請求狀態;成功是200
private String body_bytes_sent;// 記錄發送給客戶端文件主體內容大小
private String http_referer;// 用來記錄從那個頁面鏈接訪問過來的
private String http_user_agent;// 記錄客戶瀏覽器的相關信息
private boolean valid = true;// 判斷數據是否合法
private static KPI parser(String line) {
System.out.println(line);
KPI kpi = new KPI();
String[] arr = line.split(" ");
if (arr.length > 11) {
kpi.setRemote_addr(arr[0]);
kpi.setRemote_user(arr[1]);
kpi.setTime_local(arr[3].substring(1));
kpi.setRequest(arr[6]);
kpi.setStatus(arr[8]);
kpi.setBody_bytes_sent(arr[9]);
kpi.setHttp_referer(arr[10]);
if (arr.length > 12) {
kpi.setHttp_user_agent(arr[11] + " " + arr[12]);
} else {
kpi.setHttp_user_agent(arr[11]);
}
if (Integer.parseInt(kpi.getStatus()) >= 400) {// 大於400,HTTP錯誤
kpi.setValid(false);
}
} else {
kpi.setValid(false);
}
return kpi;
}
這裏使用parser方法進行解析。解析得到八個信息值。
有了這些信息值以後,就可以根據這些信息值就行統計。原創作者在代碼中完成了四項統計工作。
分別統計了訪問網站的獨立IP的統計,網頁訪問次數的統計,一天之內每小時的訪問次數統計,和訪問網站的瀏覽器類型的統計。
關於統計的信息說明以及如何統計,原創博文中說明的很詳細了,鏈接在此,請點擊:海量Web日誌分析 用Hadoop提取KPI統計指標 作者的博文寫的真是通俗易懂。在此我就不羅嗦了。
下面給出日誌解析工具類的完成代碼如下:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
/*
* KPI Object
* 日誌文件每一行作爲一條記錄,該類完成每一行日誌的解析
*/
public class KPI {
private String remote_addr;// 記錄客戶端的ip地址
private String remote_user;// 記錄客戶端用戶名稱,忽略屬性"-"
private String time_local;// 記錄訪問時間與時區
private String request;// 記錄請求的url與http協議
private String status;// 記錄請求狀態;成功是200
private String body_bytes_sent;// 記錄發送給客戶端文件主體內容大小
private String http_referer;// 用來記錄從那個頁面鏈接訪問過來的
private String http_user_agent;// 記錄客戶瀏覽器的相關信息
private boolean valid = true;// 判斷數據是否合法
private static KPI parser(String line) {
System.out.println(line);
KPI kpi = new KPI();
String[] arr = line.split(" ");
if (arr.length > 11) {
kpi.setRemote_addr(arr[0]);
kpi.setRemote_user(arr[1]);
kpi.setTime_local(arr[3].substring(1));
kpi.setRequest(arr[6]);
kpi.setStatus(arr[8]);
kpi.setBody_bytes_sent(arr[9]);
kpi.setHttp_referer(arr[10]);
if (arr.length > 12) {
kpi.setHttp_user_agent(arr[11] + " " + arr[12]);
} else {
kpi.setHttp_user_agent(arr[11]);
}
if (Integer.parseInt(kpi.getStatus()) >= 400) {// 大於400,HTTP錯誤
kpi.setValid(false);
}
} else {
kpi.setValid(false);
}
return kpi;
}
/**
* 按page的pv分類
*/
public static KPI filterPVs(String line) {
KPI kpi = parser(line);
Set<String> pages = new HashSet<String>();
pages.add("/about");
pages.add("/black-ip-list/");
pages.add("/cassandra-clustor/");
pages.add("/finance-rhive-repurchase/");
pages.add("/hadoop-family-roadmap/");
pages.add("/hadoop-hive-intro/");
pages.add("/hadoop-zookeeper-intro/");
pages.add("/hadoop-mahout-roadmap/");
if (!pages.contains(kpi.getRequest())) {
kpi.setValid(false);
}
return kpi;
}
/**
* 按page的獨立ip分類
*/
public static KPI filterIPs(String line) {
KPI kpi = parser(line);
Set<String> pages = new HashSet<String>();
pages.add("/about");
pages.add("/black-ip-list/");
pages.add("/cassandra-clustor/");
pages.add("/finance-rhive-repurchase/");
pages.add("/hadoop-family-roadmap/");
pages.add("/hadoop-hive-intro/");
pages.add("/hadoop-zookeeper-intro/");
pages.add("/hadoop-mahout-roadmap/");
if (!pages.contains(kpi.getRequest())) {
kpi.setValid(false);
}
return kpi;
}
/**
* PV按瀏覽器分類
*/
public static KPI filterBroswer(String line) {
return parser(line);
}
/**
* PV按小時分類
*/
public static KPI filterTime(String line) {
return parser(line);
}
/**
* PV按訪問域名分類
*/
public static KPI filterDomain(String line){
return parser(line);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("valid:" + this.valid);
sb.append("\nremote_addr:" + this.remote_addr);
sb.append("\nremote_user:" + this.remote_user);
sb.append("\ntime_local:" + this.time_local);
sb.append("\nrequest:" + this.request);
sb.append("\nstatus:" + this.status);
sb.append("\nbody_bytes_sent:" + this.body_bytes_sent);
sb.append("\nhttp_referer:" + this.http_referer);
sb.append("\nhttp_user_agent:" + this.http_user_agent);
return sb.toString();
}
public String getRemote_addr() {
return remote_addr;
}
public void setRemote_addr(String remote_addr) {
this.remote_addr = remote_addr;
}
public String getRemote_user() {
return remote_user;
}
public void setRemote_user(String remote_user) {
this.remote_user = remote_user;
}
public String getTime_local() {
return time_local;
}
public Date getTime_local_Date() throws ParseException {
SimpleDateFormat df = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss", Locale.US);
return df.parse(this.time_local);
}
public String getTime_local_Date_hour() throws ParseException{
SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHH");
return df.format(this.getTime_local_Date());
}
public void setTime_local(String time_local) {
this.time_local = time_local;
}
public String getRequest() {
return request;
}
public void setRequest(String request) {
this.request = request;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getBody_bytes_sent() {
return body_bytes_sent;
}
public void setBody_bytes_sent(String body_bytes_sent) {
this.body_bytes_sent = body_bytes_sent;
}
public String getHttp_referer() {
return http_referer;
}
public String getHttp_referer_domain(){
if(http_referer.length()<8){
return http_referer;
}
String str=this.http_referer.replace("\"", "").replace("http://", "").replace("https://", "");
return str.indexOf("/")>0?str.substring(0, str.indexOf("/")):str;
}
public void setHttp_referer(String http_referer) {
this.http_referer = http_referer;
}
public String getHttp_user_agent() {
return http_user_agent;
}
public void setHttp_user_agent(String http_user_agent) {
this.http_user_agent = http_user_agent;
}
public boolean isValid() {
return valid;
}
public void setValid(boolean valid) {
this.valid = valid;
}
public static void main(String args[]) {
String line = "222.68.172.190 - - [18/Sep/2013:06:49:57 +0000] \"GET /images/my.jpg HTTP/1.1\" 200 19939 \"http://www.angularjs.cn/A00n\" \"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36\"";
System.out.println(line);
KPI kpi = new KPI();
//每一行內容,按空格分割成數組中保存
String[] arr = line.split(" ");
//保存訪問的ip地址
kpi.setRemote_addr(arr[0]);
//保存訪問的用戶,該值在文件中基本無效,該值爲-
kpi.setRemote_user(arr[1]);
//保存訪問時間
kpi.setTime_local(arr[3].substring(1));
//保存訪問的請求地址
kpi.setRequest(arr[6]);
//保存訪問的請求狀態,正常情況,該值爲200
kpi.setStatus(arr[8]);
//保存服務器響應的字節數
kpi.setBody_bytes_sent(arr[9]);
//保存從何處訪問來
kpi.setHttp_referer(arr[10]);
//保存瀏覽器或者客戶單信息
kpi.setHttp_user_agent(arr[11] + " " + arr[12]);
System.out.println(kpi);
try {
SimpleDateFormat df = new SimpleDateFormat("yyyy.MM.dd:HH:mm:ss", Locale.US);
System.out.println(df.format(kpi.getTime_local_Date()));
System.out.println(kpi.getTime_local_Date_hour());
System.out.println(kpi.getHttp_referer_domain());
} catch (ParseException e) {
e.printStackTrace();
}
}
}
代碼中包含了main方法,可以進行測試,測試日誌的解析是否正確無誤,測試結果如下:
/**
* 222.68.172.190 - - [18/Sep/2013:06:49:57 +0000] "GET /images/my.jpg HTTP/1.1" 200 19939 "http://www.angularjs.cn/A00n" "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36"
* valid:true
* remote_addr:222.68.172.190
* remote_user:-
* time_local:18/Sep/2013:06:49:57
* request:/images/my.jpg
* status:200
* body_bytes_sent:19939
* http_referer:"http://www.angularjs.cn/A00n"
* http_user_agent:"Mozilla/5.0 (Windows
* 2013.09.18:06:49:57
* 2013091806
* www.angularjs.cn
*/
有了上面的解析工具類,下面就可以利用MapReduce完成信息的統計。
- 網站資源的訪問次數的統計
首先看網站每個頁面的訪問次數的統計,在類中,詳細給出了功能性的註釋,具體看代碼:
import java.io.IOException;
import java.util.Iterator;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.FileInputFormat;
import org.apache.hadoop.mapred.FileOutputFormat;
import org.apache.hadoop.mapred.JobClient;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapred.MapReduceBase;
import org.apache.hadoop.mapred.Mapper;
import org.apache.hadoop.mapred.OutputCollector;
import org.apache.hadoop.mapred.Reducer;
import org.apache.hadoop.mapred.Reporter;
import org.apache.hadoop.mapred.TextInputFormat;
import org.apache.hadoop.mapred.TextOutputFormat;
/**
* 頁面訪問量統計MapReduce
* 統計每個頁面的訪問次數
* 完成的功能類似於單詞統計:
* 以每個訪問網頁的資源路徑爲鍵,經過mapreduce任務,
* 最終得到每一個資源路徑的訪問次數
*/
public class KPIPV {
public static class KPIPVMapper extends MapReduceBase implements Mapper<Object, Text, Text, IntWritable> {
private IntWritable one = new IntWritable(1);
private Text word = new Text();
@Override
public void map(Object key, Text value, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException {
KPI kpi = KPI.filterPVs(value.toString());
if (kpi.isValid()) {
/**
* 以頁面的資源路徑爲鍵,每訪問一次,值爲1
* 作爲map任務的輸出
*/
word.set(kpi.getRequest());
output.collect(word, one);
}
}
}
public static class KPIPVReducer extends MapReduceBase implements Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable result = new IntWritable();
@Override
public void reduce(Text key, Iterator<IntWritable> values, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException {
int sum = 0;
while (values.hasNext()) {
sum += values.next().get();
}
result.set(sum);
output.collect(key, result);
}
}
public static void main(String[] args) throws Exception {
/**
* 對應於HDFS中文件所在的位置路徑
*/
String input = "hdfs://centos:9000/access.log.10";
/**
* 對應HDFS中的輸出文件。文件不能事先存在。否則出錯
*/
String output = "hdfs://centos:9000/out_kpipv";
JobConf conf = new JobConf(KPIPV.class);
conf.setJobName("KPIPV");
// conf.addResource("classpath:/hadoop/core-site.xml");
// conf.addResource("classpath:/hadoop/hdfs-site.xml");
// conf.addResource("classpath:/hadoop/mapred-site.xml");
//設置map輸出的鍵類型,對應於map的輸出以資源路徑字符串作爲鍵
conf.setMapOutputKeyClass(Text.class);
//設置map輸出的值類型,對應於map輸出,值爲1
conf.setMapOutputValueClass(IntWritable.class);
//設置reduce的輸出鍵類型,和map相同
conf.setOutputKeyClass(Text.class);
//設置reduce的輸出值類型,和map相同,不過是累加的值
conf.setOutputValueClass(IntWritable.class);
//設置map類
conf.setMapperClass(KPIPVMapper.class);
//設置合併函數,該合併函數和reduce完成相同的功能,提升性能,減少map和reduce之間數據傳輸量
conf.setCombinerClass(KPIPVReducer.class);
//設置reduce類
conf.setReducerClass(KPIPVReducer.class);
//設置輸入文件類型,默認TextInputFormat,該行代碼可省略
conf.setInputFormat(TextInputFormat.class);
//設置輸出文件類型,默認TextOutputFormat,該行代碼可省略
conf.setOutputFormat(TextOutputFormat.class);
//設置輸入文件路徑
FileInputFormat.setInputPaths(conf, new Path(input));
//設置輸出文件路徑。該路徑不能存在,否則出錯!!!
FileOutputFormat.setOutputPath(conf, new Path(output));
//運行啓動任務
JobClient.runJob(conf);
System.exit(0);
}
}
代碼中給出了大部分的註釋,網站網頁的訪問次數,通過統計日誌文件的每一行記錄,統計每一行日誌的訪問網站的資源路徑爲鍵,相同的資源路徑累加1,最後即可得到訪問網站網頁的訪問次數。
運行代碼,控制檯日誌信息輸出正常,無異常信息。下面看看HDFS文件的輸出情況:
out_kpipv輸出文件已經被創建,同時,該文件下還有一個_SUCCESS文件和part-00000文件。分別打開文件查看內容發現,_SUCCESS文件裏面沒有內容,part-00000文件內記錄了結果信息。
前面是資源路徑,後面的數字對應該資源路徑被訪問的次數。
- 訪問網站的獨立IP統計
代碼如下:
import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.FileInputFormat;
import org.apache.hadoop.mapred.FileOutputFormat;
import org.apache.hadoop.mapred.JobClient;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapred.MapReduceBase;
import org.apache.hadoop.mapred.Mapper;
import org.apache.hadoop.mapred.OutputCollector;
import org.apache.hadoop.mapred.Reducer;
import org.apache.hadoop.mapred.Reporter;
import org.apache.hadoop.mapred.TextInputFormat;
import org.apache.hadoop.mapred.TextOutputFormat;
/**
* 統計每個資源路徑的ip訪問MapReduce
* map以資源路徑爲鍵,以ip爲值輸出
* reduce完成以資源路徑爲鍵,ip統計值爲值寫入到結果文件中。
*
* 完成的功能類似於單詞統計!!
*/
public class KPIIP {
public static class KPIIPMapper extends MapReduceBase implements Mapper<Object, Text, Text, Text> {
private Text word = new Text();
private Text ips = new Text();
@Override
public void map(Object key, Text value, OutputCollector<Text, Text> output, Reporter reporter) throws IOException {
KPI kpi = KPI.filterIPs(value.toString());
if (kpi.isValid()) {
/**
* 以請求的資源路徑爲鍵
* 以ip爲值
*/
word.set(kpi.getRequest());
ips.set(kpi.getRemote_addr());
output.collect(word, ips);
}
}
}
public static class KPIIPReducer extends MapReduceBase implements Reducer<Text, Text, Text, Text> {
private Text result = new Text();
private Set<String> count = new HashSet<String>();
@Override
public void reduce(Text key, Iterator<Text> values, OutputCollector<Text, Text> output, Reporter reporter) throws IOException {
/**
* 以資源路徑爲鍵
* 以相同資源路徑的ip值的個數,添加入set集合中作爲值
* 寫入到結果文件中
*/
while (values.hasNext()) {
count.add(values.next().toString());
}
//結果集中存放的是獨立ip的個數
result.set(String.valueOf(count.size()));
output.collect(key, result);
}
}
public static void main(String[] args) throws Exception {
String input = "hdfs://centos:9000/access.log.10";
String output = "hdfs://centos:9000/out_kpiip";
JobConf conf = new JobConf(KPIIP.class);
conf.setJobName("KPIIP");
// conf.addResource("classpath:/hadoop/core-site.xml");
// conf.addResource("classpath:/hadoop/hdfs-site.xml");
// conf.addResource("classpath:/hadoop/mapred-site.xml");
conf.setMapOutputKeyClass(Text.class);
conf.setMapOutputValueClass(Text.class);
conf.setOutputKeyClass(Text.class);
conf.setOutputValueClass(Text.class);
conf.setMapperClass(KPIIPMapper.class);
conf.setCombinerClass(KPIIPReducer.class);
conf.setReducerClass(KPIIPReducer.class);
conf.setInputFormat(TextInputFormat.class);
conf.setOutputFormat(TextOutputFormat.class);
FileInputFormat.setInputPaths(conf, new Path(input));
FileOutputFormat.setOutputPath(conf, new Path(output));
JobClient.runJob(conf);
System.exit(0);
}
}
有了第一個mapreduce的完成,下面的三個類你會發現大同小異,所以代碼中沒有給出註釋,註釋部分請參考第一個網站資源訪問次數的統計部分。網站的獨立ip的統計,是根據日誌文件中每一條日誌信息中訪問資源的ip的個數進行統計的。以資源路徑爲鍵,不同的ip值進行累加即可得出結果。
運行代碼結果如下:
out_kpiip文件夾被創建,下面看該文件夾下的內容:
輸出結果文件中,前面對應資源路徑,後面數字對應獨立ip的個數。
- 每小時訪問網站的次數統計
代碼如下:
import java.io.IOException;
import java.text.ParseException;
import java.util.Iterator;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.FileInputFormat;
import org.apache.hadoop.mapred.FileOutputFormat;
import org.apache.hadoop.mapred.JobClient;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapred.MapReduceBase;
import org.apache.hadoop.mapred.Mapper;
import org.apache.hadoop.mapred.OutputCollector;
import org.apache.hadoop.mapred.Reducer;
import org.apache.hadoop.mapred.Reporter;
import org.apache.hadoop.mapred.TextInputFormat;
import org.apache.hadoop.mapred.TextOutputFormat;
/**
* 用戶每小時訪問量統計
*/
public class KPITime {
public static class KPITimeMapper extends MapReduceBase implements Mapper<Object, Text, Text, IntWritable> {
private IntWritable one = new IntWritable(1);
private Text word = new Text();
@Override
public void map(Object key, Text value, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException {
KPI kpi = KPI.filterBroswer(value.toString());
if (kpi.isValid()) {
try {
word.set(kpi.getTime_local_Date_hour());
output.collect(word, one);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
}
public static class KPITimeReducer extends MapReduceBase implements Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable result = new IntWritable();
@Override
public void reduce(Text key, Iterator<IntWritable> values, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException {
int sum = 0;
while (values.hasNext()) {
sum += values.next().get();
}
result.set(sum);
output.collect(key, result);
}
}
public static void main(String[] args) throws Exception {
String input = "hdfs://centos:9000/access.log.10";
String output = "hdfs://centos:9000/out_kpitime";
JobConf conf = new JobConf(KPITime.class);
conf.setJobName("KPITime");
// conf.addResource("classpath:/hadoop/core-site.xml");
// conf.addResource("classpath:/hadoop/hdfs-site.xml");
// conf.addResource("classpath:/hadoop/mapred-site.xml");
conf.setOutputKeyClass(Text.class);
conf.setOutputValueClass(IntWritable.class);
conf.setMapperClass(KPITimeMapper.class);
conf.setCombinerClass(KPITimeReducer.class);
conf.setReducerClass(KPITimeReducer.class);
conf.setInputFormat(TextInputFormat.class);
conf.setOutputFormat(TextOutputFormat.class);
FileInputFormat.setInputPaths(conf, new Path(input));
FileOutputFormat.setOutputPath(conf, new Path(output));
JobClient.runJob(conf);
System.exit(0);
}
}
每小時訪問網站的次數統計是以每小時時間爲鍵,每小時時間段內的訪問次數進行累加即可得到結果。
運行代碼如下:
out_kpitime文件夾創建。下面查看文件夾內容:
可以看到日誌文件前面一行對應2013年9月18日這一天每一小時的時間信息,後面的數字對應該一小時內網站的訪問次數。
- 訪問網站的瀏覽器類型統計
import java.io.IOException;
import java.util.Iterator;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.FileInputFormat;
import org.apache.hadoop.mapred.FileOutputFormat;
import org.apache.hadoop.mapred.JobClient;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapred.MapReduceBase;
import org.apache.hadoop.mapred.Mapper;
import org.apache.hadoop.mapred.OutputCollector;
import org.apache.hadoop.mapred.Reducer;
import org.apache.hadoop.mapred.Reporter;
import org.apache.hadoop.mapred.TextInputFormat;
import org.apache.hadoop.mapred.TextOutputFormat;
/**
* 瀏覽器統計MapReduce
*
*/
public class KPIBrowser {
public static class KPIBrowserMapper extends MapReduceBase implements Mapper<Object, Text, Text, IntWritable> {
private IntWritable one = new IntWritable(1);
private Text word = new Text();
@Override
public void map(Object key, Text value, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException {
KPI kpi = KPI.filterBroswer(value.toString());
if (kpi.isValid()) {
word.set(kpi.getHttp_user_agent());
output.collect(word, one);
}
}
}
public static class KPIBrowserReducer extends MapReduceBase implements Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable result = new IntWritable();
@Override
public void reduce(Text key, Iterator<IntWritable> values, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException {
int sum = 0;
while (values.hasNext()) {
sum += values.next().get();
}
result.set(sum);
output.collect(key, result);
}
}
public static void main(String[] args) throws Exception {
String input = "hdfs://centos:9000/access.log.10";
String output = "hdfs://centos:9000/out_kpibrowser";
JobConf conf = new JobConf(KPIBrowser.class);
conf.setJobName("KPIBrowser");
// conf.addResource("classpath:/hadoop/core-site.xml");
// conf.addResource("classpath:/hadoop/hdfs-site.xml");
// conf.addResource("classpath:/hadoop/mapred-site.xml");
conf.setOutputKeyClass(Text.class);
conf.setOutputValueClass(IntWritable.class);
conf.setMapperClass(KPIBrowserMapper.class);
conf.setCombinerClass(KPIBrowserReducer.class);
conf.setReducerClass(KPIBrowserReducer.class);
conf.setInputFormat(TextInputFormat.class);
conf.setOutputFormat(TextOutputFormat.class);
FileInputFormat.setInputPaths(conf, new Path(input));
FileOutputFormat.setOutputPath(conf, new Path(output));
//啓動任務
JobClient.runJob(conf);
System.exit(0);
}
}
直接看結果:
結果中輸出了各種客戶端對網站的訪問情況。
結束!
原作者的文章地址:http://blog.fens.me/hadoop-mapreduce-log-kpi/
原作者的代碼使用eclipse和maven結合使用的。我在此基礎上,沒有使用maven,而是使用eclipse創建java工程完成。
本項目的代碼地址:請猛戳這裏(歡迎關注我的GITHUB)
項目使用eclipse構建。方便易用,代碼註釋詳細。