需求分析
通过web服务器的日志文件分析出每天各个省份UV数,UV数的指标有很多种,我这里选用不同的userID作为指标。得到这个UV数方便前端展示和用作数据分析。
逻辑实现
- 在map端读取日志文件,然后以 date_province \t userID的形式组合为一个key,把value值设置为空,最后输出。(其中date为年月日)
- 经过shuffle阶段分组合并后,会把某一个人某天内多次访问某个省份合并为一次。
- 在reduce端进行省份UV数统计。
测试日志文件格式
71508281814590031 http://s.yhd.com/?tc=0.0.12.2704_13852075_5.13&tp=1.1.16.0.5.KlBK557-10-6z7Ct http://star.pclady.com.cn/135/1351735_3.html 3 B8SUYA48VDXS8KAE9Y1SVBC3HVBS3JRV9VZX 1.1.16.0.5.KlBK557-10-6z7Ct 0.0.12.2704_13852075_5.13 P8BZ4AU4EJBZY3BBUQ3GHBACPBYWRF8Q 39.180.50.118 2015-08-28 19:14:59 http://star.pclady.com.cn/135/1351735_3.html 1 1000 Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0) Win32 北京市 2 2015-08-28 19:14:59 北京市 58 HSl7CKz-00-EmKCv 0 1024*768 1 1 1199119844687
代码实现
- map端关键代码。mapreduce编程框架中
setup方法在执行map或reduce之前执行,且执行一次,一般用来初始化某些资源。
clearup方法在执行完map或reduce之后执行,且执行一次,一边用来释放某些资源。在这里用来统计完UV后打印。
private static class ModuelMapper extends Mapper<LongWritable, Text, Text, NullWritable> {
private Text outputKey = new Text();
private NullWritable outputValue = NullWritable.get();
public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String valueString = value.toString();
// 按制表符分割日志文件每一行
String[] values = valueString.split("\t");
// 过滤脏数据
if(values.length < 30) {
return;
}
// 取出日期时间,判断
String dateTime = values[17];
if(StringUtils.isEmpty(dateTime)) {
return;
}
// 从日期时间中取出年月日
String dateValue = dateTime.substring(0, 10);
// 取出省份ID,判断
String provinceID = values[23];
if(StringUtils.isEmpty(provinceID)) {
return;
}
// 过滤不是数字的省份ID
try {
Integer.valueOf(provinceID);
} catch (Exception e) {
return;
}
// 取出userID,判断
String userID = values[5];
if(StringUtils.isEmpty(userID)) {
return;
}
// 设置key
outputKey.set(dateValue + "_" + provinceID + "\t" + userID);
// 输出
context.write(outputKey, outputValue);
}
}
- reduce端关键代码
private static class ModuelReducer extends Reducer<Text, NullWritable, Text, IntWritable> {
private Text outputKey = new Text();
private IntWritable outputValue = new IntWritable();
private Map<String, Integer> count = new HashMap<String, Integer>();
public void reduce(Text key, Iterable<NullWritable> values, Context context)
throws IOException, InterruptedException {
String keyString = key.toString();
// 去除userID,获取daet_provinceID
String provinceDate = keyString.split("\t")[0];
if(count.containsKey(provinceDate)) {
// 获取之前的UV数
Integer scount = count.get(provinceDate);
// UV数加一
scount = scount + 1;
// 更新这个provinceDate中的value值。(HashMap中不会出现相同的键)
count.put(provinceDate, scount);
} else { // 第一次出现这个provinceDate的情况
count.put(provinceDate, 1);
}
}
protected void cleanup(Reducer<Text, NullWritable, Text, IntWritable>.Context context)
throws IOException, InterruptedException {
// 获取key集合
Set<String> keyDate = count.keySet();
// 设置key和value并输出
for(String value : keyDate) {
outputKey.set(value);
outputValue.set(count.get(value));
context.write(outputKey, outputValue);
}
}
}
- 测试结果
2015-08-28_26 268
2015-08-28_25 27
2015-08-28_24 128
2015-08-28_23 152
2015-08-28_29 117
2015-08-28_28 29
2015-08-28_27 91
2015-08-28_9 260
2015-08-28_7 199
2015-08-28_8 118
2015-08-28_5 1759
2015-08-28_6 1086
2015-08-28_3 943
2015-08-28_4 505
2015-08-28_1 7684
2015-08-28_11 217
2015-08-28_10 157
2015-08-28_2 3026
2015-08-28_32 274
2015-08-28_0 10
2015-08-28_30 37
2015-08-28_15 208
2015-08-28_14 513
2015-08-28_13 440
2015-08-28_12 511
2015-08-28_19 356
2015-08-28_18 561
2015-08-28_17 475
2015-08-28_16 605
2015-08-28_22 68
2015-08-28_21 152
2015-08-28_20 5029