#测试数据:
# more user.txt(用户ID,用户名)
1 lavimer 2 liaozhongmin 3 liaozemin#more post.txt(用户ID,帖子ID,标题)
1 1 java 1 2 c 2 3 hadoop 4 4 hive 5 5 hbase 5 6 pig 5 7 flume
1 lavimer 1 1 java 1 lavimer 1 2 c 2 liaozhongmin 2 3 hadoop
1 lavimer 1 1 java 1 lavimer 1 2 c 2 liaozhongmin 2 3 hadoop 3 liaozemin NULL
1 lavimer 1 1 java 1 lavimer 1 2 c 2 liaozhongmin 2 3 hadoop NULL 4 4 hive NULL 5 5 hbase NULL 5 6 pig NULL 5 7 flume
1 lavimer 1 1 java 1 lavimer 1 2 c 2 liaozhongmin 2 3 hadoop 3 liaozemin NULL NULL 4 4 hive NULL 5 5 hbase NULL 5 6 pig NULL 5 7 flume
/** * * @author 廖钟民 * time : 2015年1月30日下午1:23:36 * @version */ public class UserPostJoin { // 定义输入路径 private static final String INPUT_PATH1 = "hdfs://liaozhongmin:9000/user_post_join/user.txt"; private static final String INPUT_PATH2 = "hdfs://liaozhongmin:9000/user_post_join/post.txt"; // 定义输出路径 private static final String OUT_PATH = "hdfs://liaozhongmin:9000/out"; public static void main(String[] args) { try { // 创建配置信息 Configuration conf = new Configuration(); // 创建文件系统 FileSystem fileSystem = FileSystem.get(new URI(OUT_PATH), conf); // 如果输出目录存在,我们就删除 if (fileSystem.exists(new Path(OUT_PATH))) { fileSystem.delete(new Path(OUT_PATH), true); } // 创建任务 Job job = new Job(conf, UserPostJoin.class.getName()); // 设置连接类型 job.getConfiguration().set("joinType", "allOuter"); // 设置多路径输入 MultipleInputs.addInputPath(job, new Path(INPUT_PATH1), TextInputFormat.class, UserMapper.class); MultipleInputs.addInputPath(job, new Path(INPUT_PATH2), TextInputFormat.class, PostMapper.class); //1.2 设置自定义Mapper类和设置map函数输出数据的key和value的类型 job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(UserPost.class); //1.3 设置分区和reduce数量(reduce的数量,和分区的数量对应,因为分区为一个,所以reduce的数量也是一个) job.setPartitionerClass(HashPartitioner.class); job.setNumReduceTasks(1); //1.4 排序 //1.5 归约 //2.1 Shuffle把数据从Map端拷贝到Reduce端。 //2.2 指定Reducer类和输出key和value的类型 job.setReducerClass(UserPostReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(Text.class); //2.3 指定输出的路径和设置输出的格式化类 FileOutputFormat.setOutputPath(job, new Path(OUT_PATH)); job.setOutputFormatClass(TextOutputFormat.class); // 提交作业 退出 System.exit(job.waitForCompletion(true) ? 0 : 1); } catch (Exception e) { e.printStackTrace(); } } /** * 自定义Mapper类用于处理来自user.txt文件的数据 * @author 廖钟民 * time : 2015年1月30日下午1:22:12 * @version */ public static class UserMapper extends Mapper<LongWritable, Text, Text, UserPost> { @Override protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, UserPost>.Context context) throws IOException, InterruptedException { // 对字符串进行切分 String[] arr = value.toString().split("\t"); // 创建UserId Text userId = new Text(arr[0]); // 把结果写出去 context.write(userId, new UserPost("U", value.toString())); } } /** * 自定义Mapper类用于处理来自post.txt文件的数据 * @author 廖钟民 * time : 2015年1月30日下午1:22:16 * @version */ public static class PostMapper extends Mapper<LongWritable, Text, Text, UserPost> { @Override protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, UserPost>.Context context) throws IOException, InterruptedException { // 对数据进行切分 String[] arr = value.toString().split("\t"); // 创建用户ID Text userId = new Text(arr[0]); context.write(userId, new UserPost("P", value.toString())); } } /** * 自定义Reducer类用于处理不同Mapper类的输出 * @author 廖钟民 * time : 2015年1月30日下午1:23:05 * @version */ public static class UserPostReducer extends Reducer<Text, UserPost, Text, Text> { // 定义List集合用于存放用户 private List<Text> users = new ArrayList<Text>(); private List<Text> posts = new ArrayList<Text>(); // 定义连接类型 private String joinType; @Override protected void setup(Reducer<Text, UserPost, Text, Text>.Context context) throws IOException, InterruptedException { this.joinType = context.getConfiguration().get("joinType"); System.out.println(joinType); } /** * 经过Shuffle后数据会分组,每一组数据都会调用一次reduce()函数 *第一组数据: *1 lavimer *1 1 java *1 2 c * *第二组数据: *2 3 hadoop *2 liaozhongmin * *第三组数据: *3 liaozemin * *第四组数据: *4 4 hive * *第五组数据: *5 5 hbase *5 6 pig *5 7 flume * *每一组数据都会调用一次reduce()函数,我们以第一组数据为例进行讲解: * *进入reduce函数后,<1,lavimer>会被添加到users集合中 *<1 1 java>和<1 2 c>会被添加到posts集合中 * *然后是判断当前操作是什么类型的连接,我们以等值连接为例: *遍历两个集合得到的数据为: *【1 lavimer 1 1 java】 *【1 lavimer 1 2 c】 * *这是第一组数据的执行轨迹,其他依次类推就可以得到相关的操作 */ protected void reduce(Text key, Iterable<UserPost> values, Reducer<Text, UserPost, Text, Text>.Context context) throws IOException, InterruptedException { // 清空集合 users.clear(); posts.clear(); // 迭代values集合把当前穿进来的某个组中的数据分别添加到对应的集合中 for (UserPost val : values) { System.out.println("实际值:" + key + "===>" + values.toString()); if (val.getType().equals("U")) { users.add(new Text(val.getData())); } else { posts.add(new Text(val.getData())); } } // 根据joinType关键字做对应的连接操作 if (joinType.equals("innerJoin")) {// 内连接 if (users.size() > 0 && posts.size() > 0) { for (Text user : users) { for (Text post : posts) { context.write(new Text(user), new Text(post)); } } } } else if (joinType.equals("leftOuter")) {// 左外连接 for (Text user : users) { if (posts.size() > 0) { for (Text post : posts) { context.write(new Text(user), new Text(post)); } } else { context.write(new Text(user), createEmptyPost()); } } } else if (joinType.equals("rightOuter")) {// 右外连接 for (Text post : posts) { if (users.size() > 0) { for (Text user : users) { context.write(new Text(user), new Text(post)); } } else { context.write(createEmptyUser(), post); } } } else if (joinType.equals("allOuter")) {// 全外连接 if (users.size() > 0) { for (Text user : users) { if (posts.size() > 0) { for (Text post : posts) { context.write(new Text(user), new Text(post)); } } else { context.write(new Text(user), createEmptyPost()); } } } else { for (Text post : posts) { if (users.size() > 0) { for (Text user : users) { context.write(new Text(user), new Text(post)); } } else { context.write(createEmptyUser(), post); } } } } } /** * 用户为空时用制表符代替 * * @return */ private Text createEmptyUser() { return new Text("NULL"); } /** * 帖子为空时用制表符代替 * * @return */ private Text createEmptyPost() { return new Text("NULL"); } } } /** * 自定义实体类封装两个表的数据 * @author 廖钟民 * time : 2015年1月30日下午1:23:50 * @version */ class UserPost implements Writable { // 类型(U表示用户,P表示帖子) private String type; private String data; public UserPost() { } public UserPost(String type, String data) { this.type = type; this.data = data; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getData() { return data; } public void setData(String data) { this.data = data; } public void write(DataOutput out) throws IOException { out.writeUTF(this.type); out.writeUTF(this.data); } public void readFields(DataInput in) throws IOException { this.type = in.readUTF(); this.data = in.readUTF(); } }