Map join
配置:
set hive.auto.convert.join = true(0.11版本后默认是true)
set hive.mapjoin.smalltable.filesize=25000000(设置小表的大小,默认就是25M)
原理:
mapjoin :主要用于小表连接大表,一般小表的大小为25M,大表没有什么具体的限制。
使用mapjoin的原因是:
在进行表的连接时,在map端处理完数据后,会把不同表的数据,形成不同的文件,reduce端进行拉取map端获得文件时,由于map端文件不在一个节点 ,且由于表的大小不一,获得的相应的文件也会大小不一,特别是针对相差较大的大小表,更会在数据拉去的时候浪费资源,拖慢job进度。为此引入了mapjoin这种处理手断。
解决:hive会自动选择小表(元数据中有记录),把小表put到各个节点的缓存(Distributed Cache)中,大表在硬盘中(这个和普通表进入map的操作一样)在map中直接与关联表连接,连表操作直接在map中进行,从内存中拉取小表,从硬盘中拉去大表,不需要走reduce,大大节约的时间。提高效率。
我们都知道。hive低层是编译成java代码,走mapreduce来执行hive语句。下面我们来看看mapjoin的java代码:
public class mapJoinDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException, URISyntaxException {
//创建配置文件
Configuration conf=new Configuration();
Job job=Job.getInstance(conf,"mapjoin");
//设置jar的位置
job.setJarByClass(mapJoinDriver.class);
//设置map和reduce的位置
job.setMapperClass(mapJoinMapper.class);
//设置map输出的key value类型
job.setMapOutputKeyClass(NullWritable.class);
job.setMapOutputValueClass(CustomerOrder.class);
//设置输入输出的路径
FileInputFormat.setInputPaths(job,new Path("file:///I:\\hadoopteam\\data\\join\\mapjoin"));
FileOutputFormat.setOutputPath(job,new Path("file:///I:\\hadoopteam\\data\\joinoutput"));
//提交小文件到内存
job.addCacheFile(new URI("file:///I:/hadoopteam/data/join/customers.csv"));
//提交程序运行
boolean result=job.waitForCompletion(true);
System.exit(result?0:1);
}
}
从代码中可以看到:
FileInputFormat.setInputPaths(job,new Path("file:///I:\\hadoopteam\\data\\join\\mapjoin"));
读取我们的大文件的路径,读到各个节点的硬盘上
job.addCacheFile(new URI("file:///I:/hadoopteam/data/join/customers.csv"));
就是把我们的小文件,put到各个节点的缓存中,job的addCacheFile函数,调用的是DistributedCache.addCacheFile()函数,DistributedCache 可将具体应用相关的、大尺寸的、只读的文件有效地分布放置。DistributedCache 是Map/Reduce框架提供的功能,能够缓存应用程序所需的文件 (包括文本,档案文件,jar文件等)。
接下来是join操作:
public class mapJoinMapper extends Mapper<LongWritable, Text, NullWritable, CustomerOrder> {
HashMap<String,String> customerMap=new HashMap<>();
CustomerOrder customerorder=new CustomerOrder();
@Override
protected void setup(Context context) throws IOException, InterruptedException {
//获取缓存文件的uri
URI[] cachefile= context.getCacheFiles();
if (null!=cachefile && cachefile.length>0){
//获取文件路径 文件名
String filename=cachefile[0].getPath().toString();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(filename), "UTF-8"));
String line;
while (StringUtils.isNotEmpty(line=bufferedReader.readLine())){
String[] split=line.split(",");
customerMap.put(split[0],split[1]);
}
bufferedReader.close();
}
}
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] fileds=value.toString().split(",");
customerorder.setCustomer_id(fileds[2]);
customerorder.setOrder_id(fileds[0]);
customerorder.setOrder_status(fileds[3]);
customerorder.setFlag("");
//从客户端hashmap中获取客户的姓名
customerorder.setCustomer_name(customerMap.get(fileds[2]));
//
context.write(NullWritable.get(),customerorder);
}
}
我们先看setup()函数,map()函数
setup函数主要做的是对我们的小表进行操作,首先获取我们的文件路径,然后获得文件名,在读取我们的小表文件,根据业务把小表文件转换成键值对的形式,保存在一个map集合中,用于后面的表的连接。
map函数就是mapreduce中的函数,每读一行执行一次map函数,这里我们输入的value是我们的大文件。执行一次map,我们都会获得相应的键值对,然后根据业务,把setup()集合中的数据与map中获得数据连接,最后在写出即可。
这就是mapjoin的过程,读取数据和数据的连接都在map中进行,挺高效率。但是其对每个节点的内存有要求,很吃节点内存,因为表的JOIN操作是在Map端且在内存进行的。
Reduce join
原理:
reduce join 又称shuffel join和commen join
他是一个完整的mapreduce过程,包括map阶段、shuffel阶段、reduce阶段,通过这三个阶段完整表的连接
map阶段:
读取源表数据,map输出的数据的key是join 中的on的条件,如果有多个,则一起作为key
map输出的数据的value为join之后所关心的列(select或者where所需的数据),同时还会有tag数据,用于标志数据属于哪个表,同时按key进行排序。
java代码如下:
public class Ordermapeer extends Mapper<LongWritable, Text,Text, CustomerOrder> {
CustomerOrder customerorder=new CustomerOrder();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//切割字段
String[] fileds=value.toString().split(",");
//4个字段是订单表 否则是用户表
if (fileds.length==4){
//对订单表中可以赋值的字段赋值
customerorder.setCustomer_id(fileds[2]);
customerorder.setOrder_id(fileds[0]);
customerorder.setOrder_status(fileds[3]);
customerorder.setCustomer_name("");
customerorder.setFlag("1");
}else{
customerorder.setCustomer_id(fileds[0]);
customerorder.setCustomer_name(fileds[1]);
customerorder.setOrder_id("");
customerorder.setOrder_status("");
customerorder.setFlag("0");
}
//写出
context.write(new Text(customerorder.getCustomer_id()),customerorder);
}
}
Shuffle阶段
根据key的值进行hash,并将key/value按照hash值推送至不同的reduce中,这样确保两个表中相同的key位于同一个reduce中
这个是封装在一些函数中了。
Reduce阶段
根据key的值完成join操作,期间通过Tag来识别不同表中的数据。
public class OrderReduce extends Reducer <Text, CustomerOrder, CustomerOrder, NullWritable>{
@Override
protected void reduce(Text key, Iterable<CustomerOrder> values, Context context) throws IOException, InterruptedException {
//准备一个订单记录集合
ArrayList<CustomerOrder> orderbeans=new ArrayList<>();
//准备1个客户的bean对象
CustomerOrder cusbean = new CustomerOrder();
//把数据放入到集合总合并bean对阿星
for (CustomerOrder bean : values) {
if ("1".equals(bean.getFlag())){//订单表
CustomerOrder orderbean= new CustomerOrder();
try {
BeanUtils.copyProperties(orderbean,bean);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
orderbeans.add(orderbean);
}else{
try {
//客户表
BeanUtils.copyProperties(cusbean,bean);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
//遍历表 进行空白拼接
for (CustomerOrder bean : orderbeans) {
bean.setCustomer_name(cusbean.getCustomer_name());
context.write(bean,NullWritable.get());
}
}
}
总结
mapjoin主要就是用于连表,现在已经默认开启,一旦hive发现大表和小表,就会走mapjoin ,如果一个小表和大表关联后,也有统计求和等操作 ,也会把数据的放到迭代器中,也要走reduce操作。
总的来说:map就做数据处理,(连表也是一种数据处理)
reduce:根据业务,对数据进行计算,获得我们想要的结果,比如统计,求和等。