一:背景
当数据来源不同的时候,比如用户表在MYSQL数据库中,而销售表在HDFS中,我们可以启动多个作业来依次处理这些数据源。
二:技术实现
#需求
#用户表user在MYSQL数据库中,数据如下:
-
1 liaozhongmin
-
2 lavimer
-
3 liaozemin
#销售表user_data在HDFS中,数据如下:
-
1 12
-
2 28
-
2 36
-
3 88
#我们现在的需求是要统计每个用户的销售情况,结果应该如下显示:
-
1 liaozhongmin 12
-
2 lavimer 64
-
3 liaozemin 88
代码实现:
MultiJob1.java从数据库中读取数据并进行处理:
-
public class MultiJob1 {
-
-
public static class Step1Mapper extends Mapper<LongWritable, User, Text, Text>{
-
//创建输出的key
-
private Text outKey = new Text();
-
private Text outValue = new Text();
-
protected void map(LongWritable key, User value, Mapper<LongWritable, User, Text, Text>.Context context) throws IOException, InterruptedException {
-
//设置key
-
outKey.set(String.valueOf(value.getId()));
-
//设置写出去的value
-
outValue.set(value.getName());
-
-
//把结果写出去
-
context.write(outKey, outValue);
-
}
-
}
-
-
public static class Step1Reducer extends Reducer<Text, Text, Text, Text>{
-
-
protected void reduce(Text key, Iterable<Text> values, Reducer<Text, Text, Text, Text>.Context context) throws IOException, InterruptedException {
-
for (Text val : values){
-
context.write(key, val);
-
}
-
}
-
}
-
-
/**
-
* 运行job的方法
-
* @param path
-
*/
-
public static void run(Map<String, String> path){
-
try {
-
//创建配置信息
-
Configuration conf = new Configuration();
-
-
//通过conf创建数据库配置信息
-
DBConfiguration.configureDB(conf, "com.mysql.jdbc.Driver", "jdbc:mysql://liaozhongmin:3306/myDB", "root", "134045");
-
-
//从map集合中取出输出路径
-
String step1OutPath = path.get( "step1Output");
-
-
//创建文件系统
-
FileSystem fileSystem = FileSystem.get( new URI(step1OutPath), conf);
-
-
//如果输出目录存在就删除
-
if (fileSystem.exists( new Path(step1OutPath))){
-
fileSystem.delete( new Path(step1OutPath), true);
-
}
-
-
//创建任务
-
Job job = new Job(conf,MultiJob1.class.getName());
-
-
//1.1 设置输入数据格式化的类和设置数据来源
-
job.setInputFormatClass(DBInputFormat.class);
-
DBInputFormat.setInput(job, User.class, "user", null, null, new String[]{ "id", "name"});
-
-
//1.2 设置自定义的Mapper类和Mapper输出的key和value的类型
-
job.setMapperClass(Step1Mapper.class);
-
job.setMapOutputKeyClass(Text.class);
-
job.setMapOutputValueClass(Text.class);
-
-
//1.3 设置分区和reduce数量(reduce的数量和分区的数量对应,因为分区只有一个,所以reduce的个数也设置为一个)
-
job.setPartitionerClass(HashPartitioner.class);
-
job.setNumReduceTasks( 1);
-
-
//1.4 排序
-
//1.5 归约
-
//2.1 Shuffle把数据从Map端拷贝到Reduce端
-
//job.setCombinerClass(Step1Reducer.class);
-
//2.2 指定Reducer类和输出key和value的类型
-
job.setReducerClass(Step1Reducer.class);
-
job.setOutputKeyClass(Text.class);
-
job.setOutputValueClass(Text.class);
-
-
//2.3 指定输出的路径和设置输出的格式化类
-
FileOutputFormat.setOutputPath(job, new Path(step1OutPath));
-
job.setOutputFormatClass(TextOutputFormat.class);
-
-
//提交作业 然后关闭虚拟机正常退出
-
job.waitForCompletion( true);
-
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
}
-
}
MultiJob2.java从HDFS中读取数据并且和第一个Job处理后的结果进行合并:
-
public class MultiJob2 {
-
-
// 定义一个输入路径用于判断当前处理的是来自哪里的文件
-
private static String FILE_PATH = "";
-
-
public static class Step2Mapper extends Mapper<LongWritable, Text, Text, Text> {
-
-
protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, Text>.Context context) throws IOException, InterruptedException {
-
-
// 获取文件的输入路径
-
FileSplit fileSplit = (FileSplit) context.getInputSplit();
-
FILE_PATH = fileSplit.getPath().toString();
-
-
// 获取输入行记录
-
String line = value.toString();
-
// 抛弃无效记录(这里最好使用计数器统计一下无效记录)
-
if (line == null || line.equals( "")) {
-
return;
-
}
-
// 处理来自数据库中的中间结果
-
if (FILE_PATH.contains( "part")) {
-
// 按制表符进行切割
-
String[] values = line.split( "\t");
-
// 当数组长度小于2的时候,视为无效记录
-
if (values.length < 2) {
-
return;
-
}
-
-
// 获取id和name
-
String id = values[ 0];
-
String name = values[ 1];
-
// 把结果写出去
-
context.write( new Text(id), new Text(name));
-
} else if (FILE_PATH.contains( "user_data")) {
-
// 按制表符进行切割
-
String[] values = line.split( "\t");
-
// 当数组长度小于2的时候,视为无效记录
-
if (values.length < 2) {
-
return;
-
}
-
-
// 获取id和grade
-
String id = values[ 0];
-
String score = values[ 1];
-
// 把结果写出去
-
context.write( new Text(id), new Text(score));
-
}
-
-
}
-
}
-
-
public static class Step2Reducer extends Reducer<Text, Text, Text, Text> {
-
-
protected void reduce(Text key, Iterable<Text> values, Reducer<Text, Text, Text, Text>.Context context) throws IOException, InterruptedException {
-
-
-
// 用来存放来自数据库的中间结果
-
Vector<String> vectorDB = new Vector<String>();
-
// 用来存放来自HDFS中处理后的结果
-
Vector<String> vectorHDFS = new Vector<String>();
-
// 迭代数据键对应的数据添加到相应Vector中
-
for (Text val : values) {
-
if (val.toString().startsWith( "db#")) {
-
vectorDB.add(val.toString().substring( 3));
-
} else if (val.toString().startsWith( "hdfs#")) {
-
vectorHDFS.add(val.toString().substring( 5));
-
}
-
}
-
-
// 获取两个Vector集合的长度
-
int sizeA = vectorDB.size();
-
int sizeB = vectorHDFS.size();
-
// 做笛卡尔积
-
for ( int i = 0; i < sizeA; i++) {
-
for ( int j = 0; j < sizeB; j++) {
-
context.write( new Text(key), new Text(vectorDB.get(i) + "\t" + vectorHDFS.get(j)));
-
}
-
}
-
}
-
}
-
-
/**
-
* 自定义Combiner
-
*
-
* @author 廖钟民 time : 2015年1月25日下午1:39:51
-
* @version
-
*/
-
public static class Step2Combiner extends Reducer<Text, Text, Text, Text> {
-
-
protected void reduce(Text key, Iterable<Text> values, Reducer<Text, Text, Text, Text>.Context context) throws IOException, InterruptedException {
-
-
int sum = 0;
-
//处理来自数据库中的数据
-
if (FILE_PATH.contains( "part")) {
-
for (Text val : values) {
-
context.write(key, new Text( "db#" + val.toString()));
-
}
-
-
} else { //处理来自HDFS中的数据
-
for (Text val : values) {
-
sum += Integer.parseInt(val.toString());
-
}
-
context.write(key, new Text( "hdfs#" + String.valueOf(sum)));
-
}
-
-
}
-
}
-
-
public static void run(Map<String, String> paths) {
-
try {
-
// 创建配置信息
-
Configuration conf = new Configuration();
-
-
// 从Map集合中获取输入输出路径
-
String step2Input1 = paths.get( "step2Input1");
-
String step2Input2 = paths.get( "step2Input2");
-
String step2Output = paths.get( "step2Output");
-
// 创建文件系统
-
FileSystem fileSystem = FileSystem.get( new URI(step2Output), conf);
-
// 如果输出目录存在,我们就删除
-
if (fileSystem.exists( new Path(step2Output))) {
-
fileSystem.delete( new Path(step2Output), true);
-
}
-
-
// 创建任务
-
Job job = new Job(conf, MultiJob2.class.getName());
-
-
// 1.1 设置输入目录和设置输入数据格式化的类
-
FileInputFormat.addInputPath(job, new Path(step2Input1));
-
FileInputFormat.addInputPath(job, new Path(step2Input2));
-
job.setInputFormatClass(TextInputFormat.class);
-
-
//1.2 设置自定义Mapper类和设置map函数输出数据的key和value的类型
-
job.setMapperClass(Step2Mapper.class);
-
job.setMapOutputKeyClass(Text.class);
-
job.setMapOutputValueClass(Text.class);
-
-
// 1.3 设置分区和reduce数量(reduce的数量,和分区的数量对应,因为分区为一个,所以reduce的数量也是一个)
-
job.setPartitionerClass(HashPartitioner.class);
-
job.setNumReduceTasks( 1);
-
-
// 1.4 排序
-
// 1.5 归约
-
job.setCombinerClass(Step2Combiner.class);
-
-
// 2.1 Shuffle把数据从Map端拷贝到Reduce端。
-
// 2.2 指定Reducer类和输出key和value的类型
-
job.setReducerClass(Step2Reducer.class);
-
job.setOutputKeyClass(Text.class);
-
job.setOutputValueClass(Text.class);
-
-
// 2.3 指定输出的路径和设置输出的格式化类
-
FileOutputFormat.setOutputPath(job, new Path(step2Output));
-
job.setOutputFormatClass(TextOutputFormat.class);
-
-
// 提交作业 退出
-
job.waitForCompletion( true);
-
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
}
-
}
MultiJobTest.java作业调度控制类:
-
public class MultiJobTest {
-
-
//定义HDFS的路径
-
public static final String HDFS = "hdfs://liaozhongmin:9000";
-
-
-
public static void main(String[] args) {
-
//定义一个map集合用于存储操作参数
-
Map<String, String> paths = new HashMap<String, String>();
-
-
//存储第一步的输出路径(第一步是从数据库中去取数据,没有输入路径)
-
paths.put( "step1Output", HDFS + "/step1_Out");
-
-
//存储第二部的输入路径(第二个参数是多参数输入的)
-
paths.put( "step2Input1", HDFS + "/step2_inpath/user_data");
-
paths.put( "step2Input2", HDFS + "/step1_Out/part-*");
-
paths.put( "step2Output", HDFS + "/step2_out");
-
-
//依次运行job
-
MultiJob1.run(paths);
-
MultiJob2.run(paths);
-
-
System.exit( 0);
-
}
-
-
public static JobConf config(){
-
//创建配置
-
JobConf conf = new JobConf(MultiJobTest.class);
-
conf.setJobName( "MultiJobTest");
-
-
//设置配置文件
-
/*conf.addResource("classpath:/hadoop/core-site.xml");
-
conf.addResource("classpath:/hadoop/hdfs-site.xml");
-
conf.addResource("classpath:/hadoop/mapred-site.xml");*/
-
-
conf.set( "io.sort.mb", "1024");
-
-
return conf;
-
}
-
}
程序运行的结果如下: