当使用 Hadoop MapReduce 框架进行数据处理时,按照典型的 MapReduce 流程,需要对输入数据进行一系列的处理,具体流程如下:
-
输入数据必须存储在 Hadoop 分布式文件系统(HDFS)中或在 HDFS 上的指定输入目录中。
-
Hadoop 会将输入数据分成多个数据块,以便并行处理。对于每个数据块,Hadoop 都会启动一个 Map 任务来处理。
-
在 Map 阶段,Hadoop 使用 Map 函数将输入的 Key-Value 对映射到多个中间 Key-Value 对上,以便进一步聚合和处理。数据按照 输入 Key 去重,按照 Key 进行分组,并按组别进行排序输出。
-
在 Reduce 阶段,Hadoop 使用 Reduce 函数对中间 Key-Value 对进行聚合和处理,得到最终的输出 Key-Value 对。
-
最终输出的 Key-Value 对存储在 Hadoop 分布式文件系统的指定输出目录中。
基于上述流程,具体实现取 top 10 的 MapReduce 程序如下:
- Mapper
Mapper 程序需要将输入数据转换成 Key-Value 对,并按照输入 Key 对数据进行去重,而 Value 设置为 1,表示出现次数为 1。Mapper 程序的实现如下:
func mapper(text string, ch chan<- Pair) {
words := strings.Fields(text) // 将文本切分为 term 序列
for _, w := range words {
ch <- Pair{w, uint(1)} // 将 term 当作 key 传递给 Reducer,将计数作为 value
}
}
- Reducer
Reducer 程序首先需要将输入数据根据 Key 进行分组,并对每个 Key 的 Value 进行求和,最终使用排序算法对 Key-Value 对按照出现次数降序排序,取排序后的前 10 个 Key-Value 对作为结果。Reducer 程序的实现如下:
type PairList []Pair
func (p PairList) Len() int { return len(p) }
func (p PairList) Less(i, j int) bool { return p[i].Value > p[j].Value }
func (p PairList) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func reduce(key string, values []uint) Pair {
var count uint = 0
for _, v := range values {
count += v // 将所有相同 key 的 value 求和
}
return Pair{key, count}
}
func GetTop10(data map[string][]uint) []Pair {
pairs := make(PairList, 0, len(data))
for k, v := range data {
pairs = append(pairs, reduce(k, v))
}
sort.Sort(pairs)
if len(pairs) > 10 {
pairs = pairs[:10]
}
return pairs
}
在使用 Hadoop MapReduce 框架进行数据处理时,我们需要通过编写 Map 和 Reduce 程序,并打包为 Jar 包来提交到 Hadoop 集群进行执行。同时,还需要编写用于启动 MapReduce 任务的代码。在使用 Hadoop streaming 调用编写的 Map 和 Reduce 程序时,我们可以使用类似于以下的 shell 命令:
hadoop jar /path/to/hadoop-streaming.jar \
-D mapred.reduce.tasks=10 \
-input /path/to/input \
-output /path/to/output \
-mapper /path/to/mapper \
-reducer /path/to/reducer
其中,-D mapred.reduce.tasks=10 表示设置 Reduce 任务的数量为 10(默认为 1)。-input 和 -output 分别表示输入和输出路径,-mapper 和 -reducer 分别表示 Mapper 和 Reducer 的位置。
需要注意的是,以上代码示例中用到的 Pair 消息结构体需要进行定义,可以在自己的程序中定义,或者使用 Go 的第三方库实现(如 mr库)。