《Data_Algorithm
》读书笔记四 — 使用MapReduce
实现左外连接
1.左外连接
左外连接即left join
,这个在 mysql
中是很常见的操作,如下表:
1.1 创建表
create table users
(user_id varchar(10),
location_id varchar(10)
);
create table transactions(
transaction_id varchar(10),
product_id varchar(10),
user_id varchar(10),
quantity int,
amount int
);
1.2 插入数据
insert into users values('u1','UT');
insert into users values('u2','GA');
insert into users values('u3','CA');
insert into users values('u4','CA');
insert into users values('u5','GA');
insert into transactions values('t1','p3','u1',3,330 );
insert into transactions values('t2','p1','u2',1,400 );
insert into transactions values('t3','p1','u1',3,600 );
insert into transactions values('t4','p2','u2',10,1000);
insert into transactions values('t5','p4','u4',9,90 );
insert into transactions values('t6','p1','u1',4,120 );
insert into transactions values('t7','p4','u1',8,160 );
insert into transactions values('t8','p4','u5',2,40 );
1.3 查询数据
select
t.product_id
,count(distinct u.location_id) as frequency
from transactions t
left join users u
on u.user_id = t.user_id
group by t.product_id;
+------------+-----------+
| product_id | frequency |
+------------+-----------+
| p1 | 2 |
| p2 | 1 |
| p3 | 1 |
| p4 | 3 |
+------------+-----------+
4 rows in set (0.00 sec)
这样,以transactions
作为左表,然后得到每个 product_id
热销到不同 location
的数目。例如:这里的| p1 | 2 |
表示的意思就是:p1这个商品,销往了2个不同的地区。其它同理
那么现在的问题是:如果这个表很大,打到无法使用mysql等关系型数据库使用join语句,那么该怎么办呢?别急,这时候有MapReduce
来帮助我们解决这个问题。
2. MapReduce
实现思路
这里主要讲解一下我的思路。
step 1:针对user
表我们可以得到的信息是:<user_id,location_id>
; 针对transactions
表,我们可以的得到的信息是<user_id,product_id>
。那么我们可以使用两个mapper分别从不同的文件中得到这些数据。这个功能可以由MultipleInputs
类实现
在从不同的Mapper
中得到这个<user_id,location_id>
和 <user_id,product_id>
这种键值对之后,需要将其交由 reducer 处理,但是我们在 reducer 之后得到的数据可能是无序的,如下:
[root@server4 hadoop]# hdfs dfs -cat hdfs://server4:9000/output/leftJoin/part-r-00000
u1 UT,p4,p1,p1,p3,
u2 p2,p1,GA,
u3 CA,
u4 CA,p4,
u5 p4,GA,
出现这种问题的原因是:我们未控制到达 reduce
中键值对的value的顺序,所以会出现这种 location_id
和 product_id
没有顺序的结果。但是MapReduce
提供了一种叫做Second Sort
的功能,可以帮助我们对 value 进行一个排序,所以就能够保证到达reduce
的数据是有顺序的。即想得到如下这样的数据:
[root@server4 hadoop]# hdfs dfs -cat /output/leftJoin/part-r-00000
u1 UT,p4,p1,p1,p3,
u2 GA,p2,p1,
u3 CA,
u4 CA,p4,
u5 GA,p4,
step 2:使用一个类 User
封装user_id,location_id,product_id,level
等信息。其中user_id,location_id,product_id
都是业务属性,而level
是我们构造出来,用于对两种不同的 mapper
进行排序的字段。
step 3:在使用mapper
产生了key-value
对之后,进行一个二次排序,然后使得相同的key
的数据能够有一种排序规则。
step 4:得到如下的这种数据之后,仍然不是我们想要的结果。因为我们想要的是一个每个prodcut_id
销往的不同的产地,还需要再使用一次MapReduce job
才能得到最终的结果。
[root@server4 hadoop]# hdfs dfs -cat /output/leftJoin/part-r-00000
u1 UT,p4,p1,p1,p3,
u2 GA,p2,p1,
u3 CA,
u4 CA,p4,
u5 GA,p4,
第二次使用MapReduce job 时,只需要将上述输出文件形成如下这种键值对:
p4,UT
p4,CA,
p4,GA,
p1,UT
p1,UT
p1,GA
p3,UT
p2,GA
得到这种键值对之后,我们就可以在reduce
中使用一个set
简单去重一下即可。
step 4:最后即可得到如下这种数据
[root@server4 hadoop]# hdfs dfs -cat /output/leftJoin/final/part-r-00000
p1 2
p2 1
p3 1
p4 3
3. 实现代码
因为实现代码较多,不宜在这里全部展示,只展示部分重要的类代码。如有兴趣,可以去我的github中获取,或者是留言获取即可。
3.1 主要实现类
主要的实现类如下:
3.2 SecondarySortGroupComparator
package data_algorithm.chapter_4.step_1_leftJoin;
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;
public class SecondarySortGroupComparator extends WritableComparator {
//这个构造参数一定要实现,否则会报错
public SecondarySortGroupComparator() {
super(User.class,true);
}
@Override
public int compare(WritableComparable wc1, WritableComparable wc2) {
User user1 = (User)wc1;
User user2 = (User)wc2;
return user1.getUser_id().compareTo(user2.getUser_id());
}
}
3.2 User
类
package data_algorithm.chapter_4.step_1_leftJoin;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class User implements Writable, WritableComparable<User>{
private String user_id;
private int level;//the level of 1 or 2
private String location;//user location
private String product_name;//the product name of user buy
//为什么需要添加无参的构造函数
public User() {
}
public User(String user_id, int level, String location, String product_name) {
this.user_id = user_id;
this.level = level;
this.location = location;
this.product_name = product_name;
}
public String getUser_id() {
return user_id;
}
public void setUser_id(String user_id) {
this.user_id = user_id;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public String getProduct_name() {
return product_name;
}
public void setProduct_name(String product_name) {
this.product_name = product_name;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public int compareTo(User user) {
int compareValue = this.getUser_id().compareTo(user.getUser_id());
if (compareValue == 0) {
return this.getLevel() - user.getLevel(); //先出现 location, 再出现 product
//return user.getLevel() - this.getLevel(); //先出现product , 再出现 location
}
return compareValue;
}
public void write(DataOutput out) throws IOException {
out.writeUTF(this.user_id);
out.writeInt(this.level);
out.writeUTF(this.location);
out.writeUTF(this.product_name);
}
public void readFields(DataInput in) throws IOException {
this.user_id = in.readUTF();
this.level = in.readInt();
this.location = in.readUTF();
this.product_name = in.readUTF();
}
@Override
public int hashCode() {
int result = this.getUser_id() != null ? this.getUser_id().hashCode() : 0;
//result = 31 * result + level ;
//不能使用这个+ level 值的,否则会将相同user_id 的分到不同的区?
if (this.getUser_id().equals("u1")) {
System.out.println("u1");
return 1;
}
if (this.getUser_id().equals("u2")) {
System.out.println("u2");
return 2;
}
// if (this.getUser_id().equals("u3")) return 3;
// if (this.getUser_id().equals("u4")) return 4;
// if (this.getUser_id().equals("u5")) return 5;
return result;
}
@Override
public String toString() {
return this.getUser_id();
}
}