一、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
运行结果: