MR案例:Map Join

适用场景:一张表十分小【key不可重复】、一张表非常大。
用法:在Job提交时,首先将小表加载到 DistributedCache 分布式缓存中,然后从DistributeCache中读取小表解析成 key/value 保存到内存中(可以放在Hash Map等容器中)。然后扫描大表中的每条记录的 key 是否能在内存中找到相同 join key 的记录,如果有则直接输出结果。

代码实现:

package join.map;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

/**
 * map-join中小表的数据如下:
 * 
 *    1    Beijing
 *    2    Guangzhou
 *    3    Shenzhen
 *    4    Xian
 * 
 * 大表的数据如下:
 * 
 *    Beijing Red Star              1
 *    Shenzhen Thunder              3
 *    Guangzhou Honda               2
 *    Beijing Rising                1
 *    Guangzhou Development Bank    2
 *    Tencent                       3
 *    Back of Beijing               1
 */
public class MapJoin {    

    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);
        job.setJarByClass(MapJoin2.class);

        //此方法已过时,被job.addCacheFile()所取代
        //DistributedCache.addCacheFile(new URI("hdfs://10.16.17.182:9000/test/in/address.txt"), conf);

        //加载小表到 分布式缓存DistributedCache
     job.addCacheFile(new Path(args[0]).toUri());

        job.setMapperClass(MJMapper.class);
        job.setNumReduceTasks(0);

        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(Text.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(Text.class);

        FileInputFormat.addInputPath(job, new Path(args[1]));
        FileOutputFormat.setOutputPath(job, new Path(args[2]));

        System.exit(job.waitForCompletion(true)? 0:1);
    }

    public static class MJMapper extends Mapper<LongWritable, Text, Text, Text>{

        /**
         * 此map是存放小表数据用的
         * 注意小表的key是不能重复的,类似与数据库的外键表
         * 在这里的小表,就相当于一个外键表
         * **/
        private HashMap<String, String> map=new HashMap<String, String>();

        @Override
        protected void setup(Context context) throws IOException, InterruptedException {            

            BufferedReader br=null;        // 读取文件流
            String line;

            // 获取DistributedCached里面的共享文件,相当于每个节点的本地缓存
            //此方法已过时暂无找到替代的方法
        Path[] paths = context.getLocalCacheFiles();

            for(Path path : paths){                    
                if(path.getName().indexOf("address") >= 0){        //如果是 address文件            
            br=new BufferedReader(new FileReader(path.toString()));                    

                    while((line=br.readLine()) != null){    //读取文件中的每一行
               String[] splited = line.split("\t");

                        map.put(splited[0], splited[1]);    //将小表解析成 key/value 存放进map
                    }
                }
            }
        }

        /**
         * map阶段读取并处理大表中的数据
         * 小表中的数据是加载到HashMap中的,无需从hdfs读取
         */
        @Override
        protected void map(LongWritable key, Text value, Context context)
                throws IOException, InterruptedException {

            if(value==null || ("").equals(value.toString())){    //跳过空值
                return;
            }

            String[] splited = value.toString().split("\t");

            if(map.get(splited[1]) != null){    //map中大表的 key 对应的 value 不为空

                Text keyOut = new Text(splited[0]);        //key=大表的第一列
                Text valueOut = new Text(map.get(splited[1]));    //value=小表的第二列

                context.write(keyOut, valueOut);     
            }
        }
    }
}

总结:

在hadoop中,共享全局变量或全局文件的几种方法:

1、使用Configuration的set()方法,只适合数据内容比较小的场景
2、将缓存文件放在HDFS上,每次都去读取,效率比较低
3、将缓存文件放在DistributedCache里,在setup()初始化一次后,即可多次使用,缺点是不支持修改操作,仅能读取

DistributedCache是Hadoop提供的文件缓存机制,使得一个job的所有map或reduce可以访问同一份文件。在任务提交后,hadoop自动将指定的文件分发到各个节点,并缓存到本地目录,供用户程序读取使用。

具有以下特点:

1、缓存的文件是只读的,修改这些文件内容没有意义
2、用户可以调整文件可见范围(比如只能用户自己使用,所有用户都可以使用等),进而防止重复拷贝现象
3、按需拷贝,文件是通过HDFS作为共享数据中心分发到各节点的,且只发给任务被调度到的节点

命令行使用方式:

-files:将指定的 本地/hdfs 文件分发到各个Task的工作目录下,不对文件进行任何处理
-archives:将指定文件分发到各个Task的工作目录下,并对名称后缀为“.jar”、“.zip”,“.tar.gz”、“.tgz”的文件自动解压。默认情况下,解压后的内容存放到工作目录下名称为解压前文件名的目录中,比如压缩包为dict.zip,则解压后内容存放到目录dict.zip中。为此,你可以给文件起个别名/软链接,比如dict.zip#dict,这样,压缩包会被解压到目录dict中。
-libjars:指定待分发的jar包,Hadoop将这些jar包分发到各个节点上后,会将其自动添加到任务的CLASSPATH环境变量中。

若缓存文件在Linux本地目录,则Job提交之后,首先将缓存文件上传到HDFS的某一目录下,再分发到各个节点上的,因此,HDFS是缓存文件的必经之路。

API使用方法:

在hdfs上准备好要共享的数据(text/jar/archive),这是因为缓存文件的默认访问协议为(hdfs://)
通过 job.addCacheFile(new Path(args[0]).toUri()); 方法加载缓存文件。
在Mapper类的setup()方法中对缓存文件进行初始化
在map()和reduce()方法中,可以使用处理后的缓存文件

需要注意的地方:

使用DistributedCache的Job要打包成jar包在集群上运行,Local模式会报错!!!
setup()方法的解析,很重要。

猜你喜欢

转载自blog.csdn.net/u010010664/article/details/79800867