前言
对于MapReduce实现WordCount而言,最重要的就是Map输出的<key,value>键值对,其会传至Reduce中,相同key的所有Value将可以被Reduce函数迭代遍历。
此时,假设有一个log文件,其中包含了如下数据:
不同的时间点为一行数据,其中每一行数据中的userID可能存在相同的情况,即在不同时间登录某个spName。要求即为统计出QQ的总用户数量(不同的userID,相同userID只算做一个用户)。
此文介绍了三种方案去实现该任务目标。
一、方案1
要统计QQ的总用户数量,即可以在Map函数阶段先对spName==qq进行提取
几处代码解释(小白):
①Map传入的四个参数LongWritable Text Text NullWritable ,LongWritable Text代表输入的文件格式(一般不加以改动),Text NullWritable代表输出的<key,value>键值对的格式(此处key为Text,value为NullWritable)
②String line = value.toString() —— 读取文件中一行的字符 赋予字符形式的变量line
③String [] itr = line.split("\t") —— 将line一行以Tab分隔符进行分割,赋予数组itr
public static class Map extends Mapper<LongWritable, Text, Text, NullWritable>{
public void map(LongWritable key, Text value, Context context
) throws IOException, InterruptedException {
String line = value.toString(); // 读取一行字符
String[] itr = line.split("\t"); // 将每行字符采用TAB制表位进行分割
String target = "qq"; // 确定目标为qq这一列
// 遍历每行的所有元素 查看是否有元素与qq相同 采用equals函数
for(int i = 0;i<itr.length;i++) {
if(itr[i].equals(target))
try {
String userID = itr[1]; //若该行元素包含‘qq’这一字符串 则保存该行的userID
context.write(new Text(userID),NullWritable.get()); // 将该行传入Reduce中
}catch(Exception e) {
}
}
}
}
Map工作结束后即进行Reduce工作,在Reduce中,会收到同一个key对应的所有value值。对于实现该任务而言,Map阶段已经筛选出了所有包含QQ的userID行,即传入Reduce的为(userID,null),此时只需要Reduce任务进行一次就执行某个++,最后利用CleanUp统计该变量的值即可。
几处代码解释(小白):
①Reduce传入参数与Map传入参数相同意思
②cleanup方法的执行是在Reduce任务全部执行完后进行
public static class Reduce extends Reducer<Text,NullWritable,Text,NullWritable> {
private int QQnumber = 0;
public void reduce(Text key, Iterable<NullWritable> values, Context context
) throws IOException, InterruptedException {
QQnumber++;
}
protected void cleanup(Context context) throws IOException, InterruptedException {
context.write(new Text("不同userID对应的QQ用户总数量为:"), NullWritable.get());
context.write(new Text(String.valueOf(QQnumber)), NullWritable.get());
}
}
二、方案2
在Map阶段也可以进行常规处理(此处Map代码的理解同上 比上面的Map代码更简单)
public static class Map extends Mapper<LongWritable, Text, Text, Text>{
public void map(LongWritable key, Text value, Context context
) throws IOException, InterruptedException {
String line = value.toString();
String[] items = line.split("\t");
for(int i = 0;i<items.length;i++) {
String USERID = items[1];
String SpNAME = items[4];
context.write(new Text(USERID),new Text(SpNAME));
{
}
}
}
}
对于Reduce阶段:
public static class Reduce extends Reducer<Text,Text,Text,Text> {
private int USER_n = 0;
public void reduce(Text key, Iterable<Text> values, Context context
) throws IOException, InterruptedException {
String qq = "qq";
// 遍历key对应的所有value 只要有一个value为QQ则跳出该key的循环
// 即完成了一个userID的统计
for (Text val : values) {
String val1 = val.toString();
if(val1.equals(qq))
{
USER_n++;
break;
}
}
}
protected void cleanup(Context context) throws IOException, InterruptedException {
context.write(new Text("结果:"), new Text(String.valueOf(USER_n)));
}
}
三、方案3
大致思路与方案1相同,但在Map阶段采用了StringTokenizer方法而替换掉了split方法(据说效率更高 但能力有效没有到验证效率的水平 - -)
public static class QQMap extends Mapper<LongWritable, Text, Text, NullWritable>{
public void map(LongWritable key, Text value, Context context
) throws IOException, InterruptedException {
String target = "qq";
int i = 0;
// 得到一整行字符串
StringTokenizer itr = new StringTokenizer(value.toString(),"\t");
// 获取分割后的字符串长度(如I love u 这里的nTokens即为3)
int nTokens = itr.countTokens();
// 创建数组保存每个字符串
String []a=new String [nTokens];
// 遍历字符串保存至数组之中(如果您看过WordCount的处理方法 此处与WordCount方法不同)
while (itr.hasMoreTokens()) {
a[i]=(String)itr.nextToken();
i++;
}
// 对应log文件中第五列(索引为4)为spName判断是否为qq
if(a[4].equals(target)){
context.write(new Text(a[1]), NullWritable.get()); // 写入Reduce
}
}
}
在Reduce中,进行与方案1相同的步骤:
public static class QQReduce extends Reducer<Text,NullWritable,Text,Text> {
private int QQnumber = 0;
public void reduce(Text key, Iterable<NullWritable> values, Context context
) throws IOException, InterruptedException {
QQnumber++;
}
protected void cleanup(Context context) throws IOException, InterruptedException {
context.write(new Text("QQ用户数量为:"), new Text(String.valueOf(QQnumber)));
}
}
上述方案取其一即可实现该任务
下面讲述如何使用docker对该任务进行处理(前提:已经下载好docker——安装教程:Windows Docker 安装 | 菜鸟教程)
一、创建docker镜像
docker run -p 50070:50070 -p 8088:8088 -p 8080:8080 -v 本地地址 suhothayan/hadoop-spark-pig-hive:2.9.2 bash
在cmd中输入上述指令即可,本地地址为你需要映射到虚拟机中的本机文件夹地址
镜像来源:GitHub - suhothayan/hadoop-spark-pig-hive: Docker with hadoop spark pig hive
二、导出上述方案jar包
右键class 有个export 选择jar file 点击Next 输入想保存的名字(名字可以自己取,该jar包存放的地址一定要在映射到虚拟机的文件夹下)
保存完名字后再点击两次Next进入最后一个确认界面 点击Browse 选择类
至此Jar包导出完成
三、将需要进行处理的文件上传到HDFS中
如该任务中文件名为access.log,需要将其先保存至本地映射到虚拟机的文件夹下,后进入docker(在cmd中输入如下命令进入)
docker ps -a // 查看虚拟机端口号
docker restart 端口
docker attach 端口
输入完成后,即进入了虚拟机环境之中 输入ls即可查看当前目录
ls
将access.log上传至HDFS中
docker fs -put /home/test/access.log /user/Job1
此时accss.log即保存到了HDFS中user文件夹的Job1文件夹下
四、执行jar包
hadoop jar jar包名字 /user/Job1/access.log /user/test/test1
此时jar包结果可以在user/test/test1中查看到
hadoop fs -ls/user/test/test1 // 查看该目录下文件
hadoop fs -cat /user/test/test1/part-r-00000 // 查看文件内容 (cat)
五、涉及代码
此文只是自己做完了一份作业后有感而记录,怕未来某一天要用到而忘记- -若有代码或言语不当之处欢迎沟通交流QAQ
(需要jar包的java全部代码的话欢迎私信! 虽然我觉得做的不大好)