MapReduce实现统计不同UserID的QQ用户数量

前言

        对于MapReduce实现WordCount而言,最重要的就是Map输出的<key,value>键值对,其会传至Reduce中,相同key的所有Value将可以被Reduce函数迭代遍历。

        此时,假设有一个log文件,其中包含了如下数据:

5540d95881d349b090e67d62f2992da3.png

        不同的时间点为一行数据,其中每一行数据中的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包存放的地址一定要在映射到虚拟机的文件夹下)

7378e8eb435f43468c6606031266aa24.png

        保存完名字后再点击两次Next进入最后一个确认界面 点击Browse 选择类

39511c15d53c4f899968491147a3dc46.png

        至此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)

五、涉及代码

b22e3ad361fa493a9fcd3c9945748074.png


此文只是自己做完了一份作业后有感而记录,怕未来某一天要用到而忘记- -若有代码或言语不当之处欢迎沟通交流QAQ

(需要jar包的java全部代码的话欢迎私信! 虽然我觉得做的不大好)

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值