timg

经过同学们的反馈,今天的内容还是比较多的
在此记录下最主要的两个案例

  • 案例一-日志清洗

今晚的一个任务是使用MapReduce完成一个日志文件清洗,在此记录下清洗过程

日志数据格式:

101.226.33.222 - - [31/Mar/2015:23:59:59 +0800] "GET /uc_server/data/avatar/000/07/52/20_avatar_middle.jpg HTTP/1.1" 200 5043

实训4-1

红框所圈住的内容为要求提取出的内容
1. 分析正则表达式

提取文本中指定格式内容,利用正则表达式再好不过了。
正则表达式教程:正则表达式-教程 | 菜鸟教程

根据日志数据格式得出匹配每一行的正则表达式一般为

.* - - \[.* \+0800\] "(GET|POST) .* HTTP/1.1" \d+ .+

  • 分析过程:
    可以利用Notepad++ ,为便于利用正则表达式测试,首先复制日志一小部分内容到一个新文本文件(否则文件过大,软件处理得会很慢)
    实训4-2

    Notepad++ 中按Ctrl+F键,勾选“正则表达式”,可以通过正则查找匹配文本,我们可以利用这个功能进行正则匹配测试。
    首先大致可以看出匹配的正则表达式一般是
    .* - - \[.* \+0800\] "GET .* HTTP/1.1" \d+ \d+
    搜索框中输入上面的正则表达式,不断点击“查找下一个”,可以观察到它所匹配的每一行。
    在本文件中,因第13行最后一个字段并非“\d+”匹配的数字,所以会跳过。点击“计数”可以查看有多少行匹配。根据这些结果,不断对正则表达式进行调整,最终结果为
    .* - - \[.* \+0800\] "(GET|POST) .* HTTP/1.1" \d+ .+\

对于较小的数据量来说,实际上可以通过Notepad++ 中的替换功能就可以得到本案例所要求的结果。

2.创建MapReduce任务

下面来编写JAVA代码,为便于学习,这里我完全使用Windows。
请参考:《如何使用Windows进行MapReduce编程》
首先创建MapReduce项目(File→New→Project→如图)

实训4-3

编写代码:
// LogClean.java
package logclean;

import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
 
public class LogClean {
    public LogClean() {
    }
 
    public static void main(String[] args) throws Exception {
    	Configuration conf = new Configuration();
        String[] otherArgs = {"input","output"};
 
        Job job = Job.getInstance(conf, "log clean");
        job.setJarByClass(LogClean.class);
        job.setMapperClass(LogClean.LogMapper.class);
        job.setCombinerClass(LogClean.LogReducer.class);
        job.setReducerClass(LogClean.LogReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(Text.class);
 
        for(int i = 0; i < otherArgs.length - 1; ++i) {
            FileInputFormat.addInputPath(job, new Path(otherArgs[i]));
        }
 
        FileOutputFormat.setOutputPath(job, new Path(otherArgs[otherArgs.length - 1]));
        job.waitForCompletion(true);
    }
 
    public static class LogReducer extends Reducer<Text, Text, Text, Text> {
 
        public LogReducer() {
        }
 
        public void reduce(Text key, Iterable<Text> values, Reducer<Text, Text, Text, Text>.Context context) throws IOException, InterruptedException {
        	// 直接将每一行文本作为Key,空文本作为value输出
            context.write(key, new Text(""));
        }
    }
 
    public static class LogMapper extends Mapper<Object, Text, Text, Text> {

        private Text word = new Text();
 
        public LogMapper() {
        }
 
        public void map(Object key, Text value, Mapper<Object, Text, Text, Text>.Context context) throws IOException, InterruptedException {
            // 每一行进行正则表达式替换
        	String line = value.toString().replaceAll("(.*) - - \\[(.*) \\+0800\\] \"(GET|POST) (.*) HTTP/1.1\" (\\d+) (.+)", "$1,$2,$4,$5,$6");
            this.word.set(line);
            // 输出替换后的每一行<line,>
            context.write(this.word, new Text(""));
        }
    }
}

上面代码中replaceAll()将正则表达式匹配的内容替换为其他字符串。正则表达式中,每一个小括号代表一个捕获组,可以在第二个参数中通过$再次取用。

3.执行

将两个日志文件放在src/input目录下(input需手动创建)
log4j.properties放在src目录下
然后运行LogClean.java
在src/output下可以看到生成的两个文件

实训4-4

执行用时:88992ms

  • 案例二-查找共同好友

以下是博客的好友列表数据,冒号前是一个用户,冒号后是该用户的所有好友(数据中的好友关系是单向的)
A:B,C,D,F,E,O
B:A,C,E,K
C:F,A,D,I
D:A,E,F,L
E:B,C,D,M,L
F:A,B,C,D,E,O,M
G:A,C,D,E,F
H:A,C,D,E,O
I:A,O
J:B,O
K:A,C,D
L:D,E,F
M:E,F,G
O:A,H,I,J
求出哪些人两两之间有共同好友,及他俩的共同好友都有谁?

1.首先求出每个人都是谁的好友

程序代码如下:

// FdMR1.java
package friend;

import java.io.IOException;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class FdMR1 {

	public static class FdMapper extends Mapper<LongWritable, Text, Text, Text> {

		public void map(LongWritable ikey, Text ivalue, Context context) throws IOException, InterruptedException {
			
			// 第一个字符代表一个人
			String one = ivalue.toString().substring(0, 1);
			String[] following = ivalue.toString().substring(2).split(",");
			for(String fo:following) {
				// 输出 <Who, Who是谁的好友>
				context.write(new Text(fo), new Text(one));
			}
			
		}

	}
	
	public static class FdReducer extends Reducer<Text, Text, Text, Text> {

		public void reduce(Text _key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
			// process values
			StringBuffer sb = new StringBuffer("");
			for (Text val : values) {
				sb.append("," + val.toString());
			}
			context.write(_key, new Text(sb.toString()));
		}

	}

	
	public static void main(String[] args) throws Exception {
		Configuration conf = new Configuration();
		Job job = Job.getInstance(conf, "friend");
		job.setJarByClass(FdMR1.class);
		job.setMapperClass(FdMapper.class);

		job.setReducerClass(FdReducer.class);

		// TODO: specify output types
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(Text.class);

		// TODO: specify input and output DIRECTORIES (not files)
		FileInputFormat.setInputPaths(job, new Path("input"));
		FileOutputFormat.setOutputPath(job, new Path("out"));

		if (!job.waitForCompletion(true))
			return;
	}

}
2.然后将每两个人的共同好友输出

程序代码如下:

//FdMR2.java
package friend;

import java.io.IOException;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class FdMR2 {

	public static class FdMapper extends Mapper<LongWritable, Text, Text, Text> {

		public void map(LongWritable ikey, Text ivalue, Context context) throws IOException, InterruptedException {
			
			// 这个人是后面几个人的好友
			String one = ivalue.toString().substring(0, 1);
			System.out.println(ivalue.toString());
			// 后面几个人的好友为one
			String[] follower = ivalue.toString().substring(3).split(",");
			for(String fo:follower)
			{
				System.out.println(fo);
			}
			for(int i = 0;i < follower.length - 1;i++)
				for(int j = i + 1;j < follower.length;j++)
				{
					System.out.println(follower[i] + "-" + follower[j] + " " + one);
					if (follower[i].charAt(0) < follower[j].charAt(0)) {
						context.write(new Text(follower[i] + "-" + follower[j]), new Text(one));
					}else {
						context.write(new Text(follower[j] + "-" + follower[i]), new Text(one));
					}
				}
		}

	}
	
	public static class FdReducer extends Reducer<Text, Text, Text, Text> {

		public void reduce(Text _key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
			// process values
			StringBuffer sb = new StringBuffer("");
			for (Text val : values) {
				sb.append("," + val.toString());
			}
			context.write(_key, new Text(sb.toString()));
		}

	}
	
	public static void main(String[] args) throws Exception {
		Configuration conf = new Configuration();
		Job job = Job.getInstance(conf, "friend");
		job.setJarByClass(FdMR2.class);
		job.setMapperClass(FdMapper.class);

		job.setReducerClass(FdReducer.class);

		// TODO: specify output types
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(Text.class);

		// TODO: specify input and output DIRECTORIES (not files)
		FileInputFormat.setInputPaths(job, new Path("out"));
		FileOutputFormat.setOutputPath(job, new Path("out2"));

		if (!job.waitForCompletion(true))
			return;
	}

}