MapReduce表连接操作之Map端join

本文介绍Map端Join技术,包括其实现原理与步骤,并通过具体示例展示了如何利用DistributedCache在MapReduce中实现表连接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一:背景

MapReduce提供了表连接操作其中包括Map端joinReduce端join还有半连接,现在我们要讨论的是Map端join,Map端join是指数据到达map处理函数之前进行合并的,效率要远远高于Reduce端join,因为Reduce端join是把所有的数据都经过Shuffle,非常消耗资源。


二:技术实现

基本思路:

(1):需要join的两个文件,一个存储在HDFS中,一个使用DistributedCache.addCacheFile()将需要join的另外一个文件加入到所有Map缓存中。

(2):在Map函数里读取该文件,进行join

(3):将结果输出到reduce

(4):DistributedCache.addCacheFile()需要在作业提交前设置。


什么是DistributedCache?

DistributedCache是为了方便用户进行应用程序开发而设计的文件分发工具。它能够将只读的外部文件进行自动分发到各个节点上进行本地缓存,以便task运行时加载。


DistributedCache的使用步骤

(1):在HDFS中上传文件(文本文件、压缩文件、jar包等)

(2):调用相关API添加文件信息

(3):task运行前直接调用文件读写API获取文件。

常见API:

DistributedCache.addCacheFile();

DistributedCache.addCacheArchive();


下面我们通过一个示例来深入体会Map端join。

表一:tb_a数据如下

  1. name sex age depNo
  2. zhang male 20 1
  3. li female 25 2
  4. wang female 30 3
  5. zhou male 35 2

表二:tb_b数据如下

  1. depNo depName
  2. 1 sales
  3. 2 Dev
  4. 3 Mgt

#需求就是连接上面两张表


注意:在Map端join操作中,我们往往将较小的表添加到内存中,因为内存的资源是很宝贵的,这也说明了另外一个问题,那就是如果表的数据量都非常大则不适合使用Map端join。


代码如下:

  1. public class MyMapJoin {
  2. // 定义输入路径
  3. private static String INPUT_PATH1 = "";
  4. //加载到内存的表的路径
  5. private static String INPUT_PATH2 = "";
  6. // 定义输出路径
  7. private static String OUT_PATH = "";
  8. public static void main(String[] args) {
  9. try {
  10. // 创建配置信息
  11. Configuration conf = new Configuration();
  12. // 获取命令行的参数
  13. String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
  14. // 当参数违法时,中断程序
  15. if (otherArgs.length != 3) {
  16. System.err.println("Usage:MyMapJoin<in1> <in2> <out>");
  17. System.exit(1);
  18. }
  19. // 给路径赋值
  20. INPUT_PATH1 = otherArgs[0];
  21. INPUT_PATH2 = otherArgs[1];
  22. OUT_PATH = otherArgs[2];
  23. // 创建文件系统
  24. FileSystem fileSystem = FileSystem.get(new URI(OUT_PATH), conf);
  25. // 如果输出目录存在,我们就删除
  26. if (fileSystem.exists(new Path(OUT_PATH))) {
  27. fileSystem.delete(new Path(OUT_PATH), true);
  28. }
  29. // 添加到内存中的文件(随便添加多少个文件)
  30. DistributedCache.addCacheFile(new Path(INPUT_PATH2).toUri(), conf);
  31. // 创建任务
  32. Job job = new Job(conf, MyMapJoin.class.getName());
  33. // 打成jar包运行,这句话是关键
  34. job.setJarByClass(MyMapJoin.class);
  35. //1.1 设置输入目录和设置输入数据格式化的类
  36. FileInputFormat.setInputPaths(job, INPUT_PATH1);
  37. job.setInputFormatClass(TextInputFormat.class);
  38. //1.2 设置自定义Mapper类和设置map函数输出数据的key和value的类型
  39. job.setMapperClass(MapJoinMapper.class);
  40. job.setMapOutputKeyClass(NullWritable.class);
  41. job.setMapOutputValueClass(Emp_Dep.class);
  42. //1.3 设置分区和reduce数量
  43. job.setPartitionerClass(HashPartitioner.class);
  44. job.setNumReduceTasks(0);
  45. FileOutputFormat.setOutputPath(job, new Path(OUT_PATH));
  46. // 提交作业 退出
  47. System.exit(job.waitForCompletion(true) ? 0 : 1);
  48. } catch (Exception e) {
  49. e.printStackTrace();
  50. }
  51. }
  52. public static class MapJoinMapper extends Mapper<LongWritable, Text, NullWritable, Emp_Dep> {
  53. private Map<Integer, String> joinData = new HashMap<Integer, String>();
  54. @Override
  55. protected void setup(Mapper<LongWritable, Text, NullWritable, Emp_Dep>.Context context) throws IOException, InterruptedException {
  56. // 预处理把要关联的文件加载到缓存中
  57. Path[] paths = DistributedCache.getLocalCacheFiles(context.getConfiguration());
  58. // 我们这里只缓存了一个文件,所以取第一个即可,创建BufferReader去读取
  59. BufferedReader reader = new BufferedReader(new FileReader(paths[0].toString()));
  60. String str = null;
  61. try {
  62. // 一行一行读取
  63. while ((str = reader.readLine()) != null) {
  64. // 对缓存中的表进行分割
  65. String[] splits = str.split("\t");
  66. // 把字符数组中有用的数据存在一个Map中
  67. joinData.put(Integer.parseInt(splits[0]), splits[1]);
  68. }
  69. } catch (Exception e) {
  70. e.printStackTrace();
  71. } finally{
  72. reader.close();
  73. }
  74. }
  75. @Override
  76. protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, NullWritable, Emp_Dep>.Context context) throws IOException,
  77. InterruptedException {
  78. // 获取从HDFS中加载的表
  79. String[] values = value.toString().split("\t");
  80. // 创建Emp_Dep对象
  81. Emp_Dep emp_Dep = new Emp_Dep();
  82. // 设置属性
  83. emp_Dep.setName(values[0]);
  84. emp_Dep.setSex(values[1]);
  85. emp_Dep.setAge(Integer.parseInt(values[2]));
  86. // 获取关联字段depNo,这个字段是关键
  87. int depNo = Integer.parseInt(values[3]);
  88. // 根据depNo从内存中的关联表中获取要关联的属性depName
  89. String depName = joinData.get(depNo);
  90. // 设置depNo
  91. emp_Dep.setDepNo(depNo);
  92. // 设置depName
  93. emp_Dep.setDepName(depName);
  94. // 写出去
  95. context.write(NullWritable.get(), emp_Dep);
  96. }
  97. }
  98. }
程序运行的结果:


注:这个程序不知道为什么要打成jar包的方式才能运行,直接通过Eclipse链接服务器运行会失败(如果有知道的朋友,请指教!)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.youkuaiyun.com/lzm1340458776/article/details/42971075
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值