Hadoop 本身主要是用 Java 语言编写的,它的核心组件,如 HDFS(Hadoop Distributed File System)和 YARN(Yet Another Resource Negotiator),以及 MapReduce 的框架,都是基于 Java 的,当人们谈论“Hadoop C语言”时,通常不是指用 C 语言从头重写 Hadoop,而是指在 Hadoop 生态系统中使用 C/C++ 语言进行开发。

这主要有以下几种场景和实现方式:
编写 C/C++ MapReduce 程序
这是最直接的理解,Hadoop 提供了一套 API,允许开发者用 C++ 编写 MapReduce 的 Mapper 和 Reducer 函数,然后将它们编译成可执行文件,提交到 Hadoop 集群上运行。
工作原理
Hadoop 的 Java 框架负责处理所有分布式环境的复杂性,
- 数据分块和任务调度。
- 进程启动和监控。
- 网络通信和容错。
你的 C++ 程序(mapper 和 reducer)作为子进程被 Hadoop Java 进程调用,Hadoop 通过 标准输入 和 标准输出 与你的 C++ 程序进行通信。
数据流
- 输入阶段:Hadoop 将输入数据的一个切片(split)作为文本流,通过标准输入(
stdin)逐行传递给你的 C++ Mapper 程序。 - Mapper 阶段:你的 Mapper 程序逐行读取输入,处理数据,然后将键值对通过标准输出(
stdout)打印出来,格式通常是key\tvalue(键和值用制表符分隔)。 - Shuffle and Sort 阶段:Hadoop Java 框架收集所有 Mapper 的输出,根据 key 进行排序、分区和分组。
- Reducer 阶段:Hadoop 将分组后的数据(一个 key 对应的所有 value)通过标准输入(
stdin)传递给你的 C++ Reducer 程序,每个 key 的一组 value 会连续输入。 - 输出阶段:你的 Reducer 程序处理数据,将最终结果通过标准输出(
stdout)打印出来,Hadoop Java 框架捕获这些输出并写入最终的 HDFS 文件。
一个简单的 C++ MapReduce 示例:Word Count
Mapper (mapper.cpp)
它的任务是读取每一行文本,将单词分割出来,并输出每个单词及其出现次数(初始为1)。
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
// 将一行字符串分割成单词
std::vector<std::string> split(const std::string &s, char delimiter) {
std::vector<std::string> tokens;
std::string token;
std::istringstream tokenStream(s);
while (std::getline(tokenStream, token, delimiter)) {
tokens.push_back(token);
}
return tokens;
}
int main() {
std::string line;
// 从标准输入逐行读取
while (std::getline(std::cin, line)) {
// 将行按空格和制表符分割成单词
std::vector<std::string> words = split(line, ' ');
for (const std::string &word : words) {
// 过滤掉空字符串
if (!word.empty()) {
// 输出单词和1,用制表符分隔
std::cout << word << "\t1" << std::endl;
}
}
}
return 0;
}
Reducer (reducer.cpp)
它的任务是读取所有相同的单词(key),将它们的计数值(value)累加起来。
#include <iostream>
#include <string>
#include <map>
int main() {
std::string word;
int count;
std::string prev_word;
int total_count = 0;
// 从标准输入读取 key\tvalue
while (std::cin >> word >> count) {
// 如果是同一个单词,累加计数
if (word == prev_word) {
total_count += count;
} else {
// 如果是新单词,输出上一个单词的总计数
if (!prev_word.empty()) {
std::cout << prev_word << "\t" << total_count << std::endl;
}
// 重置计数器和当前单词
total_count = count;
prev_word = word;
}
}
// 输出最后一个单词的计数
if (!prev_word.empty()) {
std::cout << prev_word << "\t" << total_count << std::endl;
}
return 0;
}
如何编译和运行
-
编译:使用
g++将 C++ 代码编译成可执行文件。g++ -o mapper mapper.cpp -std=c++11 g++ -o reducer reducer.cpp -std=c++11
-
打包:将
mapper和reducer以及它们的依赖项打包成一个 Hadoop 可执行文件(通常是 JAR 包),这通常需要一个pom.xml文件,使用 Maven 来构建,Hadoop 提供了hadoop-streaming.jar来简化这个过程。 -
提交任务:使用
hadoop jar命令提交任务。hadoop jar hadoop-streaming.jar \ -input /input/data.txt \ # HDFS上的输入路径 -output /output/cpp_wordcount \ # HDFS上的输出路径 -mapper ./mapper \ # 本地mapper可执行文件路径 -reducer ./reducer \ # 本地reducer可执行文件路径 -file ./mapper \ # 将mapper文件打包到job中 -file ./reducer # 将reducer文件打包到job中
通过 Hadoop C API 与 HDFS 交互
HDFS 提供了一个 C 语言 API(也称为 HDFS C API),允许 C/C++ 程序直接与 HDFS 文件系统进行交互,就像在本地文件系统上操作一样,这对于需要在 C/C++ 应用程序中读写 HDFS 文件的场景非常有用。
工作原理
这个 API 本身是用 C 语言实现的,它通过 JNI(Java Native Interface)与 HDFS 的 Java 客户端库进行通信,你需要链接 HDFS 的 C 库(libhdfs.so)。
主要功能
hdfsConnect: 连接到 HDFS 集群。hdfsOpenFile: 打开一个 HDFS 文件进行读写。hdfsRead: 从打开的文件中读取数据。hdfsWrite: 向打开的文件中写入数据。hdfsCloseFile: 关闭文件。hdfsDisconnect: 断开与 HDFS 的连接。
一个简单的 HDFS C API 示例:读取文件
#include "hdfs.h"
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <namenode:port> <hdfs_path>\n", argv[0]);
return -1;
}
const char *nn = argv[1]; // "localhost:9000"
const char *path = argv[2]; // "/user/hadoop/test.txt"
hdfsFS fs = hdfsConnect(nn, 0); // 连接到HDFS,0表示不使用Kerberos
if (!fs) {
fprintf(stderr, "Failed to connect to hdfs://%s\n", nn);
return -1;
}
hdfsFile file = hdfsOpenFile(fs, path, O_RDONLY, 0, 0, 0);
if (!file) {
fprintf(stderr, "Failed to open file %s\n", path);
hdfsDisconnect(fs);
return -1;
}
char buffer[1024];
tSize numRead;
while ((numRead = hdfsRead(fs, file, buffer, sizeof(buffer))) > 0) {
// 将读取到的数据写入标准输出
fwrite(buffer, sizeof(char), numRead, stdout);
}
hdfsCloseFile(fs, file);
hdfsDisconnect(fs);
return 0;
}
如何编译和运行
-
编译:需要链接 HDFS 的 C 库头文件和库文件。
# 假设 HADOOP_HOME 是你的 Hadoop 安装目录 gcc -o hdfs_reader hdfs_reader.c -I$HADOOP_HOME/include -L$HADOOP_HOME/lib -lhdfs -lcrypto
-
运行:确保 Hadoop 库在
LD_LIBRARY_PATH中,或者设置DYLD_LIBRARY_PATH(macOS)。export LD_LIBRARY_PATH=$HADOOP_HOME/lib:$LD_LIBRARY_PATH ./hdfs_reader localhost:9000 /user/hadoop/test.txt
使用 JNI 在 Java 中调用 C/C++ 代码
这是一种更常见的模式,当你的核心算法或性能瓶颈是用 C/C++ 实现的,但你希望利用 Hadoop Java 生态的便利性(如 Hadoop MapReduce API、Spark API 等)时,可以采用这种方式。
工作原理
- 编写 C/C++ 代码:将你的核心逻辑封装在一个 C/C++ 库(如
.so或.dll)中。 - 编写 JNI 接口:创建一个 Java 类,在这个类中声明
native方法。 - 生成 JNI 头文件:使用
javac和javah工具生成 C/C++ 头文件。 - 实现 JNI 方法:在 C/C++ 代码中实现这些方法,使其能够调用你的核心逻辑,并处理 Java 和 C/C++ 之间的数据类型转换。
- 编译生成共享库:将 C/C++ 代码编译成 JNI 可以加载的共享库。
- 在 Java 中调用:在 Java 代码中加载这个库,然后像调用普通 Java 方法一样调用
native方法。
优缺点
- 优点:可以充分利用 Java 生态的成熟框架(如 Spark、Flink、Hive),同时获得 C/C++ 的高性能。
- 缺点:开发复杂度高,需要处理 JNI 的繁琐细节,调试困难,并且失去了 Hadoop Streaming 的简洁性。
总结与对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Hadoop Streaming (C++) | 简单、快速、语言无关,任何能读写标准输入输出的语言都可以,性能接近 Java。 | 进程间通信有开销,调试稍复杂,依赖 Hadoop 环境。 | 快速原型开发、使用非 Java 语言实现 MapReduce、算法逻辑简单。 |
| HDFS C API | 直接、高效,可以直接在 C/C++ 应用中读写 HDFS,无需启动 MapReduce Job。 | 只能操作 HDFS,不能直接参与 MapReduce 计算,API 相对底层。 | 需要在 C/C++ 应用程序中与 HDFS 集成,例如数据导入/导出工具。 |
| JNI | 性能最高,可以无缝集成到 Java 生态的任何框架中。 | 开发复杂、调试困难,需要深入了解 JNI 和 Java 内存模型。 | 核心计算密集型算法(如科学计算、机器学习模型)是 C/C++ 实现,但希望用 Java 生态进行分布式计算。 |
“Hadoop C语言”是一个广义的概念,它提供了多种方式将 C/C++ 的能力和性能引入到 Hadoop 生态系统中,对于大多数 MapReduce 任务,Hadoop Streaming 是最常用和最推荐的方法,而对于直接文件系统交互,HDFS C API 是不二之选。
