hadoop 概要
其实做了这么些大数据的东西,包括BI工具做可视化,我们至今是没有引入hadoop的,纯硬解硬写,我在此文建立的时刻还是没有真实的项目内使用hadoop的经历。故而大量查阅和记录一些内容,目的是总结规整一些内容,以便于建立基本认识,以及未来若用到可以快速查询,不再需要紧急的大量搜寻零散的知识。
一、Hadoop 原理和核心
-
HDFS (Hadoop Distributed File System)
- HDFS 是一种分布式文件系统,设计用于在大规模集群上运行。
- 它将数据分片成固定大小的块(通常是 128MB 或 256MB),并将这些块分布存储在集群的不同节点上。
- HDFS 提供高容错性,数据被复制多份以保证可靠性。
-
MapReduce
- 一种编程模型,用于处理大规模数据集。
- 包括两个主要步骤:Map 和 Reduce。
- Map:将输入数据转换为键值对。输入数据被分割成多个数据块(通常对应于 HDFS 中的数据块),每个数据块由独立的 Map 任务处理,生成中间结果。
- Reduce:对具有相同键的所有值进行合并和处理,中间结果被合并以生成最终输出结果。
-
YARN (Yet Another Resource Negotiator)
- 资源管理平台,负责任务调度和集群资源管理。
二、Hadoop 应用中的 Java 代码示例
假设我们要用 Hadoop 统计一组文本文件中每个单词出现的次数,这是 Hadoop 的经典案例之一:词频统计 (WordCount)。
基本pom
在使用 Hadoop 进行 MapReduce 编程时,你需要在 pom 文件中引入一些必要的依赖来支持你的项目。这些依赖包括 Hadoop 核心库和 MapReduce 相关的库。
以下是一个基本的配置样例
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>hadoop-mapreduce-example</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<hadoop.version>3.3.4</hadoop.version> <!-- 这里可以根据需要调整Hadoop版本 -->
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!-- Hadoop Common -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>${hadoop.version}</version>
</dependency>
<!-- Hadoop MapReduce Client Core -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-core</artifactId>
<version>${hadoop.version}</version>
</dependency>
<!-- Hadoop MapReduce Client Common -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-common</artifactId>
<version>${hadoop.version}</version>
</dependency>
<!-- Hadoop MapReduce Client App -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-app</artifactId>
<version>${hadoop.version}</version>
</dependency>
<!-- Hadoop MapReduce Client Job Client -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-jobclient</artifactId>
<version>${hadoop.version}</version>
</dependency>
</dependencies>
</project>
注意事项:
-
Hadoop版本:确保将
<hadoop.version>替换为适合你的 Hadoop 集群的版本。使用不匹配的版本可能导致兼容性问题。 -
Java版本:Hadoop 3.x 通常需要至少 Java 8,因此确保你的
maven.compiler.source和maven.compiler.target设置为 1.8 或更高。 -
覆盖默认配置:在某些情况下,你可能需要进一步添加或覆盖其他依赖,例如
hadoop-hdfs,hadoop-yarn-api,hadoop-yarn-common等,具体取决于应用的具体功能和需要。 -
使用特定的Hadoop发布版本:如果您使用的是特定发行版(例如 Cloudera、Hortonworks),您可能需要使用这些发行版特定的依赖项。
确保在运行项目之前,Hadoop 已正确配置和安装在你的环境中。
1. 相关包导入
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
2. 实现 Mapper 类
/*
Mapper<Object, Text, Text, IntWritable>可以看作是两个键值对 k1,v1,k2,v2
输入键值对 (k1 -> v1):
Object : 输入键(k1),通常是输入数据的偏移量或一些不需要处理的标识。它在大多数情况下不直接参与业务逻辑。
Text : 输入值(v1),是实际的输入数据内容,例如文本文件中的一行数据。
输出键值对 (k2 -> v2):
Text : 输出键(k2),实际需要输出和处理的业务逻辑相关的键。例如,在单词计数程序中,这个键可能是从输入文本中解析出来的单词。
IntWritable : 输出值(v2),通常对应于与输出键相关联的统计数据或结果值。例如,单词计数程序中每个单词的初始计数值通常为 1。( Hadoop 提供了自己的序列化和数据类型系统,不使用 Java 的基础类型(如 int))
这种设计允许 Mapper 从原始输入数据中提取有意义的信息,并将其转换为适合 Reducer 进一步处理的格式。
在集群环境中,每个 Mapper 实例可以并行处理输入数据,这种键值对的设计是 Hadoop 提供高度可扩展性和灵活性的基础之一。
*/
public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable> {
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
StringTokenizer itr = new StringTokenizer(value.toString());
while (itr.hasMoreTokens()) {
word.set(itr.nextToken());
context.write(word, one);
}
}
}
/*
在 Hadoop 的 MapReduce 编程模型中,`Context` 对象起着至关重要的作用。它充当 MapReduce 程序中 Mapper 和 Reducer 之间,以及每个阶段与框架本身之间的桥梁。以下是 `Context` 的几个主要功能:
1. **配置访问**:
- `Context` 提供对作业配置信息的访问。你可以通过 `context.getConfiguration()` 来获取作业配置,读取设定的参数和环境设置。
2. **输出写入**:
- 在 Mapper 中,`Context` 用来输出键值对给 Reducer。这通常是通过 `context.write(key, value)` 方法来实现的。
- 在 Reducer 中,同样使用 `context.write(key, value)` 方法来输出最终的结果。
3. **报告状态**:
- `Context` 提供了一种机制允许 Mapper 和 Reducer 向 Hadoop 框架报告其进度以及自定义的状态信息。通过 `context.setStatus("current status")` 设置当前状态,通过 `context.progress()` 更新进度。
4. **计数器**:
- `Context` 允许用户定义和更新计数器,这可以用来用于作业的实时监控和调试。你可以使用 `context.getCounter("GroupName", "CounterName").increment(1)` 来递增一个计数器。
5. **数据传递**:
- `Context` 也是在各个任务中传递数据的媒介,包括将 Mapper 的输出传递到 Reducer。
综上所述,`Context` 对象提供了一系列方法,帮助开发人员在 Hadoop MapReduce 作业中轻松处理配置、数据输出、任务状态更新和计数等操作。
*/
Mapper<Object, Text, Text, IntWritable>:Mapper 类具有输入键/值类型 和 输出键/值类型。在这个例子中,输入是文本行,输出是单词及其计数。
- 其中的泛型可以自己定义么?
- 输入的键和值泛型(对于 Mapper 的输入) :
- 输入键(第一对泛型中的第一个参数)通常是
LongWritable类型,表示文件中的字节偏移量,但这并不是强制的。这取决于你使用的 InputFormat。 - 输入值(第一对泛型中的第二个参数)通常是 Text 类型,用于表示读取的一行文本或其他格式的数据。然而,你可以根据输入数据源的类型选择其他合适的类型(如
IntWritable、BytesWritable等)。
- 输入键(第一对泛型中的第一个参数)通常是
- 输出的键和值泛型(对于 Mapper 和 Reducer 的输出) :
- 输出键可以是任何实现了 Hadoop 的
WritableComparable接口的类。Text 类型常被用于字符串键,但如果你的应用需要不同的键类型,你可以用其他的合适类型。 - 输出值可以是任何实现了
Writable接口的类。常见的类型有IntWritable、LongWritable、FloatWritable等,具体取决于你的处理结果的需求。
- 输出键可以是任何实现了 Hadoop 的
- 如果自定义类型:
如果你需要自定义类型(不满足现有的Writable接口),需要创建一个类来实现 Writable 或 WritableComparable 接口,并实现相应的接口方法,如write(DataOutput out)和readFields(DataInput in),以及compareTo(Object o)(如果可比较)。
- 输入的键和值泛型(对于 Mapper 的输入) :
map 方法:对于输入的每一行(value),将其分割成单词,并将每个单词映射为 <单词, 1>,输出到 context。
3. 实现 Reducer 类
public static class IntSumReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable result = new IntWritable();
public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
result.set(sum);
context.write(key, result);
}
}
Reducer<Text, IntWritable, Text, IntWritable>: Reducer 类定义了输入和输出的键/值类型。
reduce 方法: 对于输入的每个单词,将所有的计数值汇总,输出 <单词, 总计数>。
4. 配置作业并运行
public static void main(String[] args) throws Exception {
//Job 对象中的设置是专门为当前 MapReduce 作业量身定制的。这意味着这些设置只会影响到这个作业本身的执行,而不会对其它作业造成影响。
//你可以继承一些全局Configuration 的配置,比如集群设置、调度器配置等
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "word count");
job.setJarByClass(WordCount.class);
job.setMapperClass(TokenizerMapper.class);
job.setCombinerClass(IntSumReducer.class); // 本地预处理减少网络传输, 这个可选用,底下的拓展标题内会讲到
job.setReducerClass(IntSumReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
Job job = Job.getInstance(conf, "word count"): 创建一个新的作业并命名。
setJarByClass(WordCount.class):设置 JAR 文件内容。
setMapperClass 和 setReducerClass:指定 Mapper 和 Reducer 类。
addInputPath 和 setOutputPath:指定输入和输出路径。
job.waitForCompletion(true):提交作业并等待其完成。
三、总结
Hadoop MapReduce 是一个分布式计算框架,执行过程主要分为两个阶段:Map 和 Reduce。以下是一个典型的 MapReduce 作业从开始到结束的执行步骤:
1. 作业初始化
- 作业提交:用户通过客户端提交一个 MapReduce 作业,这包括应用程序的代码和作业的配置。
- 作业分配:Hadoop 集群中的 ResourceManager 负责接受作业并寻找一个合适的 NodeManager 来运行作业的 ApplicationMaster。
2. 输入数据分片(Splitting)
- 作业提交后,Hadoop 会首先根据输入数据集创建输入分片(Splits),这通常依赖于 HDFS 的块大小。
- 每个输入分片通常对应于一个 Mapper 任务。
3. Map 阶段
- Mapper 执行:每个分片的数据被读取并由 Mapper 任务进行处理。Mapper 接受输入键值对,并输出中间键值对context.write(key, value)。
- 本地化存储:中间键值对会被暂时存储在 TaskTracker 本地,并按照键进行分区和排序。这一阶段被称为“Shuffle and Sort”。
4. Shuffle and Sort(洗牌排序)
- Shuffle:这是数据重分布的过程。Hadoop 会根据每个中间键的哈希值将中间结果分散到不同的 Reducer 节点上。这个分配过程确保同一键的所有对应值都被路由到同一个 Reducer。
- Sort:在数据到达 Reducer 节点之前,Hadoop 对中间结果按键进行排序。这种排序确保 Reducer 处理时同类键的数据是有序的。
5. Reduce 阶段
- Reducer 执行:Reducer 接收到排序后的键值对组,并对每个键的一组数据进行处理,通常会进行汇总、过滤或转换。(每个 Reducer 接收到的是某些中间键的一组键和对应的值集合,Hadoop 自动完成了这部分的键值对分组和传递,其实就是Mapper最后写入到context的中间键值对被分组等处理后的)
- Reducer 程序中,reduce 方法接受三个参数:
key:当前处理的键。
values:对应于该键的所有值的迭代器。
context:用于输出最终结果的上下文 - Reducer 的输出是最终的计算结果,这是另一个键值对的集合。
6. 输出阶段
- 由 Reducer 输出的结果会被存储到 HDFS 或其他配置的输出目的地。
7. 作业完成
- 当所有 Reducer 任务完成后,整个 MapReduce 作业也宣告完成。
- ApplicationMaster 向 ResourceManager 汇报任务完成,释放资源。
注意事项
- Fault Tolerance:MapReduce 框架提供容错机制,能够在任务失败时重试任务,并在必要时重新调度到不同的节点。
- 数据本地性:为了提升效率,尽量将 Mapper 任务调度到包含其输入数据的节点上,以减少数据传输开销。
- 并行性:每个 Map 和 Reduce 阶段的任务可以独立并行执行,只要集群资源允许。
这种流程通过多次并行的数据处理和计算,能够高效地在分布式环境下处理大规模数据集。MapReduce 框架通过抽象化这种过程,使得开发者可以专注于定义如何进行键值对的转换和处理,而不必担忧底层的并行处理复杂性。
四、拓展
在 Hadoop MapReduce 架构中,Mapper 和 Reducer 的映射和分配是由框架自动管理的,确保了正确的数据流动和处理逻辑。为了处理不同的逻辑,你需要设置作业的配置,以及正确地将数据传递给目标 Reducer。这就是 MapReduce 作业的设计中的一些关键要素:
1. 作业配置
每一个 MapReduce 作业都有一个作业配置(Job Configuration),在这个配置中你可以指定:
- 使用哪个 Mapper 类。
- 使用哪个 Reducer 类。
- Mapper 的输出键值类型。
- Reducer 的输出键值类型。
- 分区器(Partitioner)的算法,可以控制如何将键分配给 Reducer。
单独的逻辑应通过为不同任务定义独立的 Mapper 和 Reducer 类来处理。
2. 自定义分区器(Partitioner)
如果需要特定的逻辑将某些键值对分配给特定的 Reducer,您可以实现自定义分区器(Partitioner)。默认情况下,Hadoop 使用键的哈希值进行分区,通过将其除以 Reducer 的数量来决定数据发送到哪个 Reducer。
定义一个自定义分区器可以让你使用自己的规则:
public class MyCustomPartitioner extends Partitioner<Text, IntWritable> {
@Override
public int getPartition(Text key, IntWritable value, int numReduceTasks) {
// 自定义逻辑来确定键发送到哪个 Reducer,这里只是一个示例
return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
}
}
3. Multiple Outputs
Hadoop 支持多输出(Multiple Outputs),从而允许同一个 Reducer 以不同的方式输出数据。你可以定义多个命名输出,并根据业务逻辑将数据写入不同的输出:
MultipleOutputs.addNamedOutput(job, "wordcount", TextOutputFormat.class, Text.class, IntWritable.class);
4. 控制逻辑
在 Reducer 中,控制逻辑确保你按照特定的处理方式处理不同的数据:
public void reduce(Text key, Iterable<IntWritable> values, Context context) {
if (key.toString().startsWith("wordcount")) {
// word count 特定的逻辑
} else {
// 其他逻辑
}
}
通过使用这些技术,Hadoop MapReduce 可以处理复杂的逻辑流,并确保不同的 Mapper 和 Reducer 之间正确地传递数据。总的来说,使用正确的配置、分区器和业务逻辑实现,可以确保特定 Reducer 处理关联的 Mapper 输出。
5. 那什么是 Combiner?
在 Hadoop 的 MapReduce 框架中,流程一般是这样的:
- Mapper:处理输入数据并生成键值对(key-value pairs)。
- Reducer:接收来自不同 Mapper 的键值对并进行进一步的处理。
Combiner 是一个可选的步骤,它作用于 Mapper 和 Reducer 之间。它在每个 Map Task 的输出结果上本地执行一些简单的聚合操作(比如求和、计数等),其目的是减少要传输给 Reducer 的数据量。
为什么使用 Combiner?
-
减少网络传输量:在大型集群中,网络带宽可能是一个瓶颈。Combiner 可以在本地先对 Mapper 的输出进行部分合并,这样要通过网络传输到 Reducer 的数据就会减少。
-
提高程序效率:通过减少中间数据量,Combiner 可以减小系统的 I/O 负载以及提升 MapReduce 作业的整体执行速度。
比如上述例子的 IntSumReducer 作为 Combiner
假设我们在做一个“单词计数”的任务:
- Mapper 输出每个单词和其出现的次数,类似这种形式:
<word, 1>。 - Reducer 负责统计每个单词出现的总次数。
在加入 Combiner 的情况下:
- Combiner 会在 Mapper 本地统计每个单词的出现次数,然后再把这个部分结果传递给 Reducer。例如,如果某个 Mapper 输出了 3 个
cat,Combiner 会把这 3 个cat先合并为<cat, 3>。 - 这样,传送给 Reducer 的数据多了一层聚合,能够减少向 Reducer 发出的数据量,优化性能。
确保使用 Combiner 的场景
Combiner 并不是必须的,也不是一直有效。它适用的情境是 Combiner 和最终 Reducer 所做的逻辑是相同的(即满足结合性和交换性原则)。这通常是像求和、计数这样简单的聚合操作。
五、其他应用场景
当然,Hadoop 及其生态系统在各种大数据处理场景中都有广泛的应用。以下是一些常见的应用场景:
1. 数据存储与管理
- 数据湖:Hadoop 可以作为数据湖的基础架构,存储多种类型的数据(结构化、半结构化和非结构化)。HDFS 提供了可扩展的、经过复制的增强数据存储能力。
2. 数据分析
- 批处理数据分析:利用 MapReduce 来处理和分析大批量数据。例如,日志分析、点击流分析、客户行为分析等。
- 机器学习:借助像 Apache Mahout 和 MLlib(Spark 的机器学习库)这样的工具来构建和训练大规模机器学习模型。
3. 数据集成
- ETL (Extract, Transform, Load):Hadoop 可以用于大规模数据的提取、转换和加载。工具如 Apache Hive 和 Apache Pig 提供了更简单的数据处理语言来执行复杂的 ETL 作业。
4. 实时数据处理
- 实时流处理:尽管 Hadoop 的核心主要是批处理,结合如 Apache Storm 或 Apache Kafka(用于数据流的入流架构)可以处理实时数据流。
5. 商业智能 (BI)
- 数据仓库解决方案:Hadoop 是许多现代数据仓库解决方案的一部分,能够支撑 BI 工具的有效操作。通过 Hive 和类似工具,可以用 SQL 查询存储在 Hadoop 中的数据。
6. 搜索引擎
- 索引和搜索:Hadoop 可以用于处理和索引海量数据以构建搜索引擎。例如,Apache Solr 和 Elasticsearch 可以在 Hadoop 生态系统中运行来提供强大的搜索和索引功能。
7. 算法与建模
- 图计算:通过 Apache Giraph 实现大规模图计算,用于社交网络分析、网络爬虫、推荐系统等。
8. 企业应用
- 风险管理和欺诈检测:许多金融机构使用 Hadoop 架构来实时分析交易数据,以检测和预防欺诈。
9. IOT 数据处理
- 物联网数据处理:随着物联网设备的普及,Hadoop 可以帮助处理和存储来自这些设备的海量数据。
10. 基因组学
- 生物信息学分析:利用 Hadoop 来处理和分析大型基因组数据集,为医学研究和临床应用提供支持。

浙公网安备 33010602011771号