MapReduce多个作业协调处理

一:背景

当数据来源不同的时候,比如用户表在MYSQL数据库中,而销售表在HDFS中,我们可以启动多个作业来依次处理这些数据源。


二:技术实现

#需求

#用户表user在MYSQL数据库中,数据如下:

  1. 1 liaozhongmin
  2. 2 lavimer
  3. 3 liaozemin

#销售表user_data在HDFS中,数据如下:

  1. 1 12
  2. 2 28
  3. 2 36
  4. 3 88

#我们现在的需求是要统计每个用户的销售情况,结果应该如下显示:

  1. 1 liaozhongmin 12
  2. 2 lavimer 64
  3. 3 liaozemin 88


代码实现:

MultiJob1.java从数据库中读取数据并进行处理:

  1. public class MultiJob1 {
  2. public static class Step1Mapper extends Mapper<LongWritable, User, Text, Text>{
  3. //创建输出的key
  4. private Text outKey = new Text();
  5. private Text outValue = new Text();
  6. protected void map(LongWritable key, User value, Mapper<LongWritable, User, Text, Text>.Context context) throws IOException, InterruptedException {
  7. //设置key
  8. outKey.set(String.valueOf(value.getId()));
  9. //设置写出去的value
  10. outValue.set(value.getName());
  11. //把结果写出去
  12. context.write(outKey, outValue);
  13. }
  14. }
  15. public static class Step1Reducer extends Reducer<Text, Text, Text, Text>{
  16. @Override
  17. protected void reduce(Text key, Iterable<Text> values, Reducer<Text, Text, Text, Text>.Context context) throws IOException, InterruptedException {
  18. for (Text val : values){
  19. context.write(key, val);
  20. }
  21. }
  22. }
  23. /**
  24. * 运行job的方法
  25. * @param path
  26. */
  27. public static void run(Map<String, String> path){
  28. try {
  29. //创建配置信息
  30. Configuration conf = new Configuration();
  31. //通过conf创建数据库配置信息
  32. DBConfiguration.configureDB(conf, "com.mysql.jdbc.Driver", "jdbc:mysql://liaozhongmin:3306/myDB", "root", "134045");
  33. //从map集合中取出输出路径
  34. String step1OutPath = path.get( "step1Output");
  35. //创建文件系统
  36. FileSystem fileSystem = FileSystem.get( new URI(step1OutPath), conf);
  37. //如果输出目录存在就删除
  38. if (fileSystem.exists( new Path(step1OutPath))){
  39. fileSystem.delete( new Path(step1OutPath), true);
  40. }
  41. //创建任务
  42. Job job = new Job(conf,MultiJob1.class.getName());
  43. //1.1 设置输入数据格式化的类和设置数据来源
  44. job.setInputFormatClass(DBInputFormat.class);
  45. DBInputFormat.setInput(job, User.class, "user", null, null, new String[]{ "id", "name"});
  46. //1.2 设置自定义的Mapper类和Mapper输出的key和value的类型
  47. job.setMapperClass(Step1Mapper.class);
  48. job.setMapOutputKeyClass(Text.class);
  49. job.setMapOutputValueClass(Text.class);
  50. //1.3 设置分区和reduce数量(reduce的数量和分区的数量对应,因为分区只有一个,所以reduce的个数也设置为一个)
  51. job.setPartitionerClass(HashPartitioner.class);
  52. job.setNumReduceTasks( 1);
  53. //1.4 排序
  54. //1.5 归约
  55. //2.1 Shuffle把数据从Map端拷贝到Reduce端
  56. //job.setCombinerClass(Step1Reducer.class);
  57. //2.2 指定Reducer类和输出key和value的类型
  58. job.setReducerClass(Step1Reducer.class);
  59. job.setOutputKeyClass(Text.class);
  60. job.setOutputValueClass(Text.class);
  61. //2.3 指定输出的路径和设置输出的格式化类
  62. FileOutputFormat.setOutputPath(job, new Path(step1OutPath));
  63. job.setOutputFormatClass(TextOutputFormat.class);
  64. //提交作业 然后关闭虚拟机正常退出
  65. job.waitForCompletion( true);
  66. } catch (Exception e) {
  67. e.printStackTrace();
  68. }
  69. }
  70. }

MultiJob2.java从HDFS中读取数据并且和第一个Job处理后的结果进行合并:

  1. public class MultiJob2 {
  2. // 定义一个输入路径用于判断当前处理的是来自哪里的文件
  3. private static String FILE_PATH = "";
  4. public static class Step2Mapper extends Mapper<LongWritable, Text, Text, Text> {
  5. protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, Text>.Context context) throws IOException, InterruptedException {
  6. // 获取文件的输入路径
  7. FileSplit fileSplit = (FileSplit) context.getInputSplit();
  8. FILE_PATH = fileSplit.getPath().toString();
  9. // 获取输入行记录
  10. String line = value.toString();
  11. // 抛弃无效记录(这里最好使用计数器统计一下无效记录)
  12. if (line == null || line.equals( "")) {
  13. return;
  14. }
  15. // 处理来自数据库中的中间结果
  16. if (FILE_PATH.contains( "part")) {
  17. // 按制表符进行切割
  18. String[] values = line.split( "\t");
  19. // 当数组长度小于2的时候,视为无效记录
  20. if (values.length < 2) {
  21. return;
  22. }
  23. // 获取id和name
  24. String id = values[ 0];
  25. String name = values[ 1];
  26. // 把结果写出去
  27. context.write( new Text(id), new Text(name));
  28. } else if (FILE_PATH.contains( "user_data")) {
  29. // 按制表符进行切割
  30. String[] values = line.split( "\t");
  31. // 当数组长度小于2的时候,视为无效记录
  32. if (values.length < 2) {
  33. return;
  34. }
  35. // 获取id和grade
  36. String id = values[ 0];
  37. String score = values[ 1];
  38. // 把结果写出去
  39. context.write( new Text(id), new Text(score));
  40. }
  41. }
  42. }
  43. public static class Step2Reducer extends Reducer<Text, Text, Text, Text> {
  44. @Override
  45. protected void reduce(Text key, Iterable<Text> values, Reducer<Text, Text, Text, Text>.Context context) throws IOException, InterruptedException {
  46. // 用来存放来自数据库的中间结果
  47. Vector<String> vectorDB = new Vector<String>();
  48. // 用来存放来自HDFS中处理后的结果
  49. Vector<String> vectorHDFS = new Vector<String>();
  50. // 迭代数据键对应的数据添加到相应Vector中
  51. for (Text val : values) {
  52. if (val.toString().startsWith( "db#")) {
  53. vectorDB.add(val.toString().substring( 3));
  54. } else if (val.toString().startsWith( "hdfs#")) {
  55. vectorHDFS.add(val.toString().substring( 5));
  56. }
  57. }
  58. // 获取两个Vector集合的长度
  59. int sizeA = vectorDB.size();
  60. int sizeB = vectorHDFS.size();
  61. // 做笛卡尔积
  62. for ( int i = 0; i < sizeA; i++) {
  63. for ( int j = 0; j < sizeB; j++) {
  64. context.write( new Text(key), new Text(vectorDB.get(i) + "\t" + vectorHDFS.get(j)));
  65. }
  66. }
  67. }
  68. }
  69. /**
  70. * 自定义Combiner
  71. *
  72. * @author 廖钟民 time : 2015年1月25日下午1:39:51
  73. * @version
  74. */
  75. public static class Step2Combiner extends Reducer<Text, Text, Text, Text> {
  76. @Override
  77. protected void reduce(Text key, Iterable<Text> values, Reducer<Text, Text, Text, Text>.Context context) throws IOException, InterruptedException {
  78. int sum = 0;
  79. //处理来自数据库中的数据
  80. if (FILE_PATH.contains( "part")) {
  81. for (Text val : values) {
  82. context.write(key, new Text( "db#" + val.toString()));
  83. }
  84. } else { //处理来自HDFS中的数据
  85. for (Text val : values) {
  86. sum += Integer.parseInt(val.toString());
  87. }
  88. context.write(key, new Text( "hdfs#" + String.valueOf(sum)));
  89. }
  90. }
  91. }
  92. public static void run(Map<String, String> paths) {
  93. try {
  94. // 创建配置信息
  95. Configuration conf = new Configuration();
  96. // 从Map集合中获取输入输出路径
  97. String step2Input1 = paths.get( "step2Input1");
  98. String step2Input2 = paths.get( "step2Input2");
  99. String step2Output = paths.get( "step2Output");
  100. // 创建文件系统
  101. FileSystem fileSystem = FileSystem.get( new URI(step2Output), conf);
  102. // 如果输出目录存在,我们就删除
  103. if (fileSystem.exists( new Path(step2Output))) {
  104. fileSystem.delete( new Path(step2Output), true);
  105. }
  106. // 创建任务
  107. Job job = new Job(conf, MultiJob2.class.getName());
  108. // 1.1 设置输入目录和设置输入数据格式化的类
  109. FileInputFormat.addInputPath(job, new Path(step2Input1));
  110. FileInputFormat.addInputPath(job, new Path(step2Input2));
  111. job.setInputFormatClass(TextInputFormat.class);
  112. //1.2 设置自定义Mapper类和设置map函数输出数据的key和value的类型
  113. job.setMapperClass(Step2Mapper.class);
  114. job.setMapOutputKeyClass(Text.class);
  115. job.setMapOutputValueClass(Text.class);
  116. // 1.3 设置分区和reduce数量(reduce的数量,和分区的数量对应,因为分区为一个,所以reduce的数量也是一个)
  117. job.setPartitionerClass(HashPartitioner.class);
  118. job.setNumReduceTasks( 1);
  119. // 1.4 排序
  120. // 1.5 归约
  121. job.setCombinerClass(Step2Combiner.class);
  122. // 2.1 Shuffle把数据从Map端拷贝到Reduce端。
  123. // 2.2 指定Reducer类和输出key和value的类型
  124. job.setReducerClass(Step2Reducer.class);
  125. job.setOutputKeyClass(Text.class);
  126. job.setOutputValueClass(Text.class);
  127. // 2.3 指定输出的路径和设置输出的格式化类
  128. FileOutputFormat.setOutputPath(job, new Path(step2Output));
  129. job.setOutputFormatClass(TextOutputFormat.class);
  130. // 提交作业 退出
  131. job.waitForCompletion( true);
  132. } catch (Exception e) {
  133. e.printStackTrace();
  134. }
  135. }
  136. }

MultiJobTest.java作业调度控制类:

  1. public class MultiJobTest {
  2. //定义HDFS的路径
  3. public static final String HDFS = "hdfs://liaozhongmin:9000";
  4. public static void main(String[] args) {
  5. //定义一个map集合用于存储操作参数
  6. Map<String, String> paths = new HashMap<String, String>();
  7. //存储第一步的输出路径(第一步是从数据库中去取数据,没有输入路径)
  8. paths.put( "step1Output", HDFS + "/step1_Out");
  9. //存储第二部的输入路径(第二个参数是多参数输入的)
  10. paths.put( "step2Input1", HDFS + "/step2_inpath/user_data");
  11. paths.put( "step2Input2", HDFS + "/step1_Out/part-*");
  12. paths.put( "step2Output", HDFS + "/step2_out");
  13. //依次运行job
  14. MultiJob1.run(paths);
  15. MultiJob2.run(paths);
  16. System.exit( 0);
  17. }
  18. public static JobConf config(){
  19. //创建配置
  20. JobConf conf = new JobConf(MultiJobTest.class);
  21. conf.setJobName( "MultiJobTest");
  22. //设置配置文件
  23. /*conf.addResource("classpath:/hadoop/core-site.xml");
  24. conf.addResource("classpath:/hadoop/hdfs-site.xml");
  25. conf.addResource("classpath:/hadoop/mapred-site.xml");*/
  26. conf.set( "io.sort.mb", "1024");
  27. return conf;
  28. }
  29. }

程序运行的结果如下:


版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lzm1340458776/article/details/43114611
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值