MapReduce使用Map-side Join实现连表查询

原理:在map task执行的时候,会先小表的数据读入memory中,每次在map函数遍历大表的时候,就会查找memory中对应相同join key的记录集,然后和大表中的数据连接,其实Map-side Join连表和sql语句中的join是一样的。你可以这样理解:在map处理大文件后,读取小文件的时候,他就会做连接。

适用场景:Map-side Join适用场景是一个大表和一个小表的连接操作,其中,“小表”是指文件足够小,可以加载到内存中。该算法可以将join算子执行在Map端,无需经历shuffle和reduce等阶段,因此效率非常高。

废话不多说,直接用例子说明:

两个txt文件:(数据太多,需要数据可以私聊我)

movies.txt:电影ID:电影名称:电影类别

ratings.txt:用户ID:电影ID:用户评分:评分时间

 

每个用户会对一个或者多个电影进行评分

问题:找出评论时间最晚的10条评论,输出电影名和评论时间

输出样例:

电影名称 评分时间

 

很明显,这两个表是通过电影ID来连接,我们在map阶段就实现两个表的连接,输出<评论时间,电影名称>,因为在map到reduce之间它会将key(评论时间)升序排列,在reduce阶段使用让Sort阶段的Key降序排列的比较器,使key从升序变为降序,然后使用for语句输出前十个value,这里调用了一个DateUtil工具类,把时间戳转换成年月日显示。

重点看重写map方法的代码

        protected void map(Object key, Text value, Context context) throws IOException, InterruptedException {
            //这里获取的是ratings.txt,ratings.txt是大文件
            //获取到txt文件中每一行的内容
            String row = value.toString();
            //将每一行的内容按:进行切分
            String[] values = row.split(":");
            //从values数组中得到电影id
            String movies_id = values[1];//电影id
            //从values数组中得到评分时间
            int time = Integer.parseInt(values[3]);//评分时间
            //moviesMap里面是存放着movies.txt的内容
            //获取movies.txt中每一行的信息,即电影ID:电影名称:电影类别
            //从这里开始大表就开始变遍历小表中的内容,找到相同的关键字做连接,当你读取小表movies.txt文件中的内容的时候,连接就开始了
            String movies_infor = moviesMap.getOrDefault(movies_id, "");//获取所有电影信息
            //获取电影名
            String movies_name =movies_infor.split(":")[1];
            k.set(movies_name);
            //这里输出(评论时间:电影名)
            context.write(new IntWritable(time),k);
//            System.out.println(time);
//            System.out.println(k);
        }

 下面给出问题的完整代码:

package cn.neu.connection.first;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.filecache.DistributedCache;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.*;
import java.lang.reflect.Array;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;

import static cn.neu.connection.first.DateUtil.timeStamp2Date;

public class MapSideJoin {

    public static class MyMapper extends Mapper<Object, Text, IntWritable,Text> {
        public Text k = new Text();
        //读取缓存文件,并按照文件名称读取到map或者别的数据结构中
        //定义一个存储movies数据缓存的数据结构
        Map<String, String> moviesMap = new HashMap<>();

        @Override
        protected void setup(Context context) throws IOException, InterruptedException {
            //获取文件系统的操作对象,通过这个对象来获取小表的数据
            FileSystem fs = FileSystem.get(context.getConfiguration());
            Path path = new Path("movies.txt");
            FSDataInputStream open = fs.open(path);
            BufferedReader br = new BufferedReader(new InputStreamReader(open));
            String line;
            while((line=br.readLine())!=null){
                //把小表数据读取到内存,也就是放入HashMap中
                //因为HashMap被创建后停留在内存中,然后随着程序的结束而消失
                String[] movies_name=line.split(":");
                moviesMap.put(movies_name[0],line);
            }
        }


        //读取login.txt进行处理
        @Override
        protected void map(Object key, Text value, Context context) throws IOException, InterruptedException {
            //这里获取的是ratings.txt
            String row = value.toString();

            String[] values = row.split(":");
            String movies_id = values[1];//电影id
            int time = Integer.parseInt(values[3]);//评论时间
            String movies_infor = moviesMap.getOrDefault(movies_id, "");//获取所有电影信息
            //1097:E.T. the Extra-Terrestrial (1982):Children's|Drama|Fantasy|Sci-Fi
            String movies_name =movies_infor.split(":")[1];

//            System.out.println(movies_infor);
            k.set(movies_name);
            //这里输出(评论时间:电影名)
            context.write(new IntWritable(time),k);
            System.out.println(time);
            System.out.println(k);
        }
    }

    public static class MyReducer extends Reducer<IntWritable, Text, Text,Text> {
        int i = 1;
        @Override
        protected void reduce(IntWritable key, Iterable<Text> values, Context context) throws IOException, InterruptedException {

            //因为在map到reduce之间它会将key(评论时间)升序排列,我们使用使Sort阶段的Key降序排列的比较器后,
            // 在reduce得到的key就是降序排列的,我们直接输出前十个数据就好了
            if (i < 10) {
                for (Text value : values) {
                    String date = timeStamp2Date(String.valueOf(key), "yyyy-MM-dd HH:mm:ss");
                    context.write(value, new Text(date));
                }
                i++;
            }
        }

        //使Sort阶段的Key降序排列的比较器
        public static class IntWritableDecreasingComparator extends
                IntWritable.Comparator {
            public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {
                return -super.compare(b1, s1, l1, b2, s2, l2);
            }
        }

        public static void main(String[] args) throws IOException {
            try {
                //Configuration conf = HdfsUtils.getConf();
                Job job = Job.getInstance(new Configuration());
                job.setJarByClass(MapSideJoin.class);
                job.setMapperClass(MyMapper.class);
                job.setMapOutputKeyClass(IntWritable.class);
                job.setMapOutputValueClass(Text.class);

                job.setReducerClass(MyReducer.class);
                job.setOutputKeyClass(Text.class);
                job.setOutputValueClass(IntWritable.class);
                //设置Sort阶段使用比较器
                job.setSortComparatorClass(IntWritableDecreasingComparator.class);
                //设置缓存
                job.addCacheFile(new URI("movies.txt"));

                FileInputFormat.addInputPath(job, new Path("ratings.txt"));
                FileOutputFormat.setOutputPath(job, new Path("output"));
                FileSystem fileSystem = FileSystem.get( new Configuration());
                if (fileSystem.exists(new Path("output"))) {//判断输出文件是否存在
                    fileSystem.delete(new Path("output"), true); //存在则删除文件,否则会出错
                }

                int success = job.waitForCompletion(true) ? 0 : 1;
                System.exit(success);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (URISyntaxException e) {
                e.printStackTrace();
            }
        }
    }
}

上面内容都是个人见解,有不对的地方望指出!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MapReduce实现join操作通常有两种方式:Reduce-side joinMap-side join。 1. Reduce-side join: Reduce-side join是最常用的实现方式。它的基本思想是将两个需要join的表分别映射为(key, value)的形式,其中key为需要join的字段,value则包含该字段以及其他需要输出的字段。然后将两个表的数据都输入到Map函数中,在Map函数中对两个表的数据进行标记,并将需要join的字段作为输出的key。在Reduce函数中,对相同的key进行合并,得到最终的输出结果。 下面是一个示例的Reduce-side join实现Map函数: ``` public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line = value.toString(); String[] fields = line.split(","); String joinKey = fields[0]; String table = fields[1]; // 表名 String data = fields[2]; // 数据 Text outputKey = new Text(joinKey); Text outputValue = new Text(table + ":" + data); context.write(outputKey, outputValue); } ``` Reduce函数: ``` public void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException { List<String> table1Data = new ArrayList<String>(); List<String> table2Data = new ArrayList<String>(); for (Text value : values) { String[] fields = value.toString().split(":"); if (fields[0].equals("table1")) { table1Data.add(fields[1]); } else if (fields[0].equals("table2")) { table2Data.add(fields[1]); } } for (String data1 : table1Data) { for (String data2 : table2Data) { context.write(key, new Text(data1 + "," + data2)); } } } ``` 2. Map-side joinMap-side join是一种更加高效的实现方式,它的基本思想是将一个表的数据缓存到内存中,然后在Map函数中将另一个表的数据与缓存的数据进行join。需要注意的是,Map-side join只适用于小表与大表之间的join操作,因为需要将小表的数据全部缓存到内存中。 下面是一个示例的Map-side join实现Map函数: ``` public void setup(Context context) throws IOException, InterruptedException { // 读取小表的数据并缓存到内存中 BufferedReader br = new BufferedReader(new FileReader("table1.csv")); String line; while ((line = br.readLine()) != null) { String[] fields = line.split(","); String joinKey = fields[0]; String data = fields[1] + "," + fields[2]; table1Data.put(joinKey, data); } br.close(); } public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line = value.toString(); String[] fields = line.split(","); String joinKey = fields[0]; String data = fields[1] + "," + fields[2]; if (table1Data.containsKey(joinKey)) { String table1Data = table1Data.get(joinKey); context.write(new Text(joinKey), new Text(table1Data + "," + data)); } } ``` 需要注意的是,Map-side join需要提前将小表的数据缓存到内存中,因此需要在Map函数之前执行setup函数。同时,为了提高效率,通常使用HashMap等数据结构来缓存小表的数据。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值