PV、UV的几种写法

11 篇文章 0 订阅

一、HQL写法

uv

count(distinct)太影响性能,以下为改进:

select
tmp.shop,count(1) as uv
from
(select user_id,shop from second_visit group by user_id,shop)tmp
group by shop;

pv

select shop,count(uid) from second_visit group by shop;

二、MR写法

2.1 pv

mapper

package pvcount;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

/**
 * @Description
 * @Author cqh <caoqingghai@1000phone.com>
 * @Version V1.0
 * @Since 1.0
 * @Date 2019/4/12 11:20
 */

/**
 * 框架在调研共我们写的map方法业务时,会将数据作为参数(一个key,一个value)传入到map方法中
 * Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
 * KEYIN:是框架(maptask)要传递给map方法的输入的参数中key的数据类型
 * VALUEIN:是框架(maptask)要传递给map方法的输入的参数中key的数据类型
 * 在默认情况下,框架传入的key是框架从待处理的数据文件(文本文件)中读取到的某一行起始偏移量
 * 所以keyin是long类型
 * 框架传入的value是从从待处理的数据文件(文本文件)中读取到的一行的数据的内容,
 * 所以valuein是String类型
 *
 * 但是,long和String是java的原生数据类型,序列化效率低
 * 所以,hadoop对其进行了改造,有一些替代品
 * long/LongWritable
 * Sring/Text
 * int/IntWritable
 *
 * map方法处理数据后之后需要写出一对key,value
 * keyout:map方法处理完成后,输出的结果中key的数据类型
 * valueout:map方法处理完成后,输出的结果中的value数据类型
 *
 */
public class PvConutMapper  extends Mapper<LongWritable, Text,Text, IntWritable> {
    /**
     * map方法是MR框架提供的,每读取一行数据就调用一次map方法
     * 也就是说读一行数据就要映射成一个key-value,然后map方法处理,处理完成后输出一个key-value
     * @param key
     * @param value
     * @param context
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        /**
         * 由于是一行处理一次,我们不能在map阶段汇总
         * 但是我们可以把数据输出成
         * 216.244.66.231    1
         * 140.205.201.39    1
         * 140.205.201.39    1
         * 220.181.108.104   1
         * 然后相同的IP的数据会被分发到同一台机器上去做reduce处理,分到一组,然后将value累加再输出
         * 实际上MR程序就是这么实现的
         */
        //value是Text类型
        String line = value.toString();
        String fields [] = line.split(" ");
        String ip = fields [0];
        //map将数据处理完成后交还给框架写出,由框架进行shuffle
        context.write(new Text(ip),new IntWritable(1));
    }
}

reducer:

package pvcount;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

/**
 * @Description
 * @Author cqh <caoqingghai@1000phone.com>
 * @Version V1.0
 * @Since 1.0
 * @Date 2019/4/12 11:49
 */

/**
 * Reducer<KEYIN,VALUEIN,KEYOUT,VALUEOUT>
 *  KEYIN与mapper  类输出的key的类型一致
 *  VALUEIN与mapper  类输出的value的类型一致
 */
public class PvCountReduce extends Reducer<Text, IntWritable,Text,IntWritable> {
    /**
     * 框架在reduce端整理好一组相同的key的数据后,一组数据调用一次reduce方法
     * @param key
     * @param values
     * @param context
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
        //140.205.201.39 :{1,1,1,1,1,1}
        //既然是一组,key相同,我们只需要把value遍历出来累加即可
        //先定义一个计数器
        int count = 0;
        for (IntWritable value:values){
            count+=value.get();
        }
       context.write(key,new IntWritable(count));
        //现在map阶段和reduce阶段已经完成,现在就需要将程序打包交给 yarn去执行
        //但是现在还不能,需要编写一个客户端程序,客户端程序要指定输入输出等,将程序提交给yarn去执行,
        //yarn拿到jar包之后,将jar包分发到其他机器
    }
}

runner

package pvcount;

/**
 * @Description
 * @Author cqh <caoqingghai@1000phone.com>
 * @Version V1.0
 * @Since 1.0
 * @Date 2019/4/12 14:10
 */

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;


/**
 * 编写yarn的客户端程序将MR程序提交给YARN去执行。yarn将jar包分发到多个nodemanager上
 * 执行是有先后顺序的,先执行map程序,再执行reduce程序
 */
public class PvCountRunner  {
    public static void main(String[] args) {
        /**
         * 程序启动需要设置一些参数,要告诉程序中的一些信息,那这些信息很零散
         * 所以我们把这些信息封装到一个对象中,这个对象就是job
         */
        try {
            Configuration conf = new Configuration();
            //获取job并携带参数
            Job job = Job.getInstance(conf,"pvCount");
            //可以用job对象封装一些信息
            //首先程序是一个jar包,就要指定jar包的位置
            //将jar包放在root目录下
            //可以将这个程序打包为pv.jar,上传到linux机器上
            //使用命令运行
            //hadoop jar /root/pv.jar pvcount.PvCountRunner /data/pvcount /out/pvcount
            job.setJar("/root/pv.jar");
            /**
             * 一个jar包中可能有多个job,所以我们要指明job使用的是哪儿一个map类
             * 哪儿一个reduce类
             */
            job.setMapperClass(PvConutMapper.class);
            job.setReducerClass(PvCountReduce.class);

            /**
             * map端输出的数据要进行序列化,所以我们要告诉框架map端输出的数据类型
             */
            job.setMapOutputKeyClass(Text.class);
            job.setMapOutputValueClass(IntWritable.class);
            /**
             * reduce端要输出,所有也要指定数据类型
             */
            job.setOutputKeyClass(Text.class);
            job.setOutputValueClass(IntWritable.class);
            /**
             * 告诉框架用什么组件去读数据,普通的文本文件,就用TextInputFormat
             * 导入长包
             */
            job.setInputFormatClass(TextInputFormat.class);
            /**
             * 告诉这个组件去哪儿读数据
             * TextInputFormat有个父类FileInputFormat
             * 用父类去指定到哪儿去读数据
             * 输入路径是一个目录,该目录下如果有子目录需要进行设置递归遍历,否则会报错
             */
            FileInputFormat.addInputPath(job,new Path(args[0]));
            FileInputFormat.setMinInputSplitSize(job,1000);
            FileInputFormat.setMaxInputSplitSize(job,1000000);
            //设置写出的组件
            job.setOutputFormatClass(TextOutputFormat.class);
            //设置写出的路径
            FileOutputFormat.setOutputPath(job,new Path(args[1]));

           // FileOutputFormat.
            job.setNumReduceTasks(5);
            //执行
            /**
             * 信息设置完成,可以调用方法向yarn去提交job
             * waitForCompletion方法就会将jar包提交给RM
             * 然后rm可以将jar包分发,其他机器就执行
             */
            //传入一个boolean类型的参数,如果是true,程序执行会返回true/flase
            //如果参数传入true,集群在运行时会有进度,这个进度会在客户端打印
            boolean res = job.waitForCompletion(true);
            /**
             * 客户端退出后会返回一个状态码,这个状态码我们可以写shell脚本的时候使用
             * 根据返回的状态码的不同区执行不同的逻辑
             */
            System.exit(res? 0:1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

 

 

三、spark写法

    val conf = new SparkConf()
    conf.setAppName("pv_uv")
    conf.setMaster("local")
    val sc = new SparkContext(conf)
    val rdd1: RDD[String] = sc.textFile("F://bigdata/data/access.log")
    val rdd2: RDD[String] = rdd1.map((_.split(" ")(0)))
    val pv1: Long = rdd2.count()
    val pv2: RDD[(String, Int)] = rdd2.map(x=>("pv",1)).reduceByKey(_+_)
    println(pv1)
    println(pv2.collect().toBuffer)

    val uv = rdd2.distinct().count() //这里的两个括号也可以去掉
    println(uv)

运行结果:
74
ArrayBuffer((pv,74))
2

运行结果:

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值