实验报告:MapReduce 自定义分区器——按部门输出独立文件¶
一、实验目的¶
- 理解 MapReduce 中分区器(Partitioner)的作用:决定 Map 输出记录进入哪个 Reduce 任务。
- 掌握自定义
Partitioner的编写方法与 Job 注册方式。 - 理解分区器与
numReduceTasks的耦合关系,能验证分区结果。
二、实验环境¶
| 项目 | 说明 |
|---|---|
| 平台 | Hadoop(伪分布式/集群均可) |
| 语言 | Java |
| 输入 | emp.csv(员工表,14 行,第 8 列为部门号 deptno) |
输入数据中部门号分布:
| 部门号 | 10 | 20 | 30 |
|---|---|---|---|
| 员工数 | 3 | 5 | 6 |
实验目标:输出 3 个文件,每个文件装一个部门的全部员工。
三、实验原理¶
3.1 默认分区的问题¶
Map 输出后,由分区函数决定每条记录去哪个 Reduce。默认的 HashPartitioner 用 key.hashCode() % numPartitions 计算分区号。对部门号 10、20、30 取模 3:
| 部门号 | % 3 |
分区号 | 输出文件 |
|---|---|---|---|
| 10 | 1 | 1 | part-r-00001 |
| 20 | 2 | 2 | part-r-00002 |
| 30 | 0 | 0 | part-r-00000 |
三个部门确实分开到了三个文件,但文件名与部门号不对应(30 号部门在 00000 里)。业务方无法凭文件名判断内容。本实验通过自定义分区器,让分区号与部门号显式对应。
3.2 自定义分区器的两条约束¶
| 约束 | 要求 | 违反后果 |
|---|---|---|
| 值域 | 返回值 ∈ [0, numPartitions) |
任务抛异常失败 |
| 稳定性 | 同一 key 必须总返回同一分区 | 同一部门数据被拆到多个文件 |
3.3 本实验的分区目标¶
| 部门号 | 分区号 | 输出文件 |
|---|---|---|
| 10 | 0 | part-r-00000 |
| 20 | 1 | part-r-00001 |
| 30 | 2 | part-r-00002 |
四、实验步骤¶
步骤 1:准备数据¶
empno,ename,job,mgr,hiredate,sal,comm,deptno
7369,SMITH,CLERK,7902,1980/12/17,800,,20
7499,ALLEN,SALESMAN,7698,1981/2/20,1600,300,30
7521,WARD,SALESMAN,7698,1981/2/22,1250,500,30
7566,JONES,MANAGER,7839,1981/4/2,2975,,20
7654,MARTIN,SALESMAN,7698,1981/9/28,1250,1400,30
7698,BLAKE,MANAGER,7839,1981/5/1,2850,,30
7782,CLARK,MANAGER,7839,1981/6/9,2450,,10
7788,SCOTT,ANALYST,7566,1987/4/19,3000,,20
7839,KING,PRESIDENT,,1981/11/17,5000,,10
7844,TURNER,SALESMAN,7698,1981/9/8,1500,0,30
7876,ADAMS,CLERK,7788,1987/5/23,1100,,20
7900,JAMES,CLERK,7698,1981/12/3,950,,30
7902,FORD,ANALYST,7566,1981/12/3,3000,,20
7934,MILLER,CLERK,7782,1982/1/23,1300,,10
步骤 2:实现 Mapper¶
步骤 3:实现Reducer¶
步骤 4:实现 Partitioner¶
步骤 5:注册到 Job¶
两行配置必须成对出现:
setNumReduceTasks(3)定义了分区号的值域[0, 3),也决定输出文件个数。- 只设 Partitioner 不设 reduce 数,则 reduce 默认为 1,分区器根本不会被调用。
五、实验结果¶
5.1 输出文件与行数¶
| 输出文件 | 部门号 | 员工数 |
|---|---|---|
| part-r-00000 | 10 | 3 |
| part-r-00001 | 20 | 5 |
| part-r-00002 | 30 | 6 |
5.2 正确性判据¶
每个文件里的部门号必须唯一。
- 若 part-r-00000 中同时出现 10 和 30 → 取模把两个部门合并了,分区设计有问题。
- 若某个部门分散在多个文件 → 分区函数不稳定(错误地使用了 value)。
六、变式实验¶
根据职位(job)映射表将员工输出到不同的文件中;
private static final Map<String, Integer> MAP = new HashMap<>();
static {
MAP.put("ANALYST", 0);
MAP.put("CLERK", 1);
MAP.put("MANAGER", 2);
MAP.put("PRESIDENT", 3);
MAP.put("SALESMAN", 4);
}
七、思考题¶
若销售部有 100 万员工,其他部门各几百人,按部门分区会出现什么问题?这叫什么现象?如何缓解?
八、实验小结¶
- 分区器的唯一职责:给定 key,返回一个落在
[0, numPartitions)的分区号。 - 分区号与输出文件名一一对应,文件名中的编号由分区器返回值决定,不由业务含义自动决定。
setPartitionerClass与setNumReduceTasks必须配对使用,缺一则分区器不生效。- 分区只能依赖 key;它决定文件划分与 Reduce 负载,是数据倾斜的成因之一,但不负责排序。