跳转至

实验报告:MapReduce 自定义分区器——按部门输出独立文件

一、实验目的

  1. 理解 MapReduce 中分区器(Partitioner)的作用:决定 Map 输出记录进入哪个 Reduce 任务。
  2. 掌握自定义 Partitioner 的编写方法与 Job 注册方式。
  3. 理解分区器与 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

// TODO

步骤 3:实现Reducer

// TODO

步骤 4:实现 Partitioner

// TODO

步骤 5:注册到 Job

两行配置必须成对出现:

job.setNumReduceTasks(3);
job.setPartitionerClass(DeptPartitioner.class);
  • 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
# TODO 截图

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 负载,是数据倾斜的成因之一,但不负责排序。