使用 AWS CLI 管理 EMR
Amazon EMR (Elastic MapReduce) 是一种托管的大数据处理服务,使用户能够在云上便捷、快速地运行和管理大规模数据分析和处理任务。
我们可以在 EMR 集群上安装各种分布式程序。这里我们将介绍如何安装最基础的 Hadoop 分布式计算框架并在其上运行一个 MapReduce 程序。
创建 MapReduce 项目
MapReduce 是一种编程模型和处理大规模数据集的框架。MapReduce 的核心思想是将数据处理分为两个阶段:Map 阶段和 Reduce 阶段,通过分布式计算集群并行处理数据。
-
Map 阶段
- 输入:Map 阶段接收输入数据集,通常以键值对(key-value pairs)的形式存在。
- 处理:每个输入键值对由 Map 函数处理,产生一组中间键值对。
- 输出:输出是中间键值对,这些对通常保存在本地磁盘上并准备发送给 Reduce 阶段。
-
Shuffle 和 Sort
- 在 Map 阶段和 Reduce 阶段之间有一个重要的过程,称为 Shuffle 和 Sort。
- Shuffle:将 Map 阶段输出的中间键值对按键的哈希值分配到不同的 Reduce 任务中。
- Sort:在每个 Reduce 任务中,对接收到的中间键值对按键进行排序。
-
Reduce 阶段
- 输入:Reduce 阶段接收来自 Shuffle 过程的中间键值对,这些对已经按键排序。
- 处理:每个 Reduce 函数处理一个唯一键的所有相关值,并生成最终的输出键值对。
- 输出:Reduce 阶段输出的键值对被写入到持久化存储中,例如 HDFS(Hadoop Distributed File System)。
编辑 MapReduce 程序
我们使用 Java 作为 MapReduce 的实现语言。这里使用 Maven 来管理项目依赖。
-
使用 Maven 创建一个新项目:
mvn archetype:generate \ -DgroupId='com.example' \ -DartifactId='my-hadoop-project' \ -DarchetypeArtifactId=maven-archetype-quickstart \ -DinteractiveMode=false-DgroupId是你的项目组名,通常是你的组织名或域名反转。-DartifactId是你的项目名。
删去项目示例文件:
cd my-hadoop-project rm -rf src/test src/main/java/com/example/App.java -
配置
pom.xml。进入项目目录,编辑
pom.xml文件,添加 Hadoop 的依赖项:vim pom.xml<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>my-hadoop-project</artifactId> <version>1.0-SNAPSHOT</version> <properties> <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>3.2.0</version> </dependency> <!-- hadoop-client --> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-client</artifactId> <version>3.2.0</version> </dependency> </dependencies> <build> <plugins> <!-- 编译插件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> </plugin> <!-- JAR 打包插件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.1.2</version> <configuration> <archive> <manifest> <mainClass>com.example.WordCount</mainClass> </manifest> </archive> </configuration> </plugin> </plugins> </build> </project>使用到的依赖:
-
编写 Hadoop 应用程序。
这里我们创建一个分布式项目中经典的 Word Count 程序。它统计输入文件中各个单词的出现次数。
vim src/main/java/com/example/WordCount.javapackage com.example; import java.io.IOException; import java.util.StringTokenizer; 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; public class WordCount { // Mapper 类 public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable> { // 定义一个常量 IntWritable 对象,值为 1,用于计数 private final static IntWritable one = new IntWritable(1); // 用于存储单词的 Text 对象 private Text word = new Text(); // map 方法处理输入的每一行文本 public void map(Object key, Text value, Context context) throws IOException, InterruptedException { // 使用 StringTokenizer 将输入文本分割成单词 StringTokenizer itr = new StringTokenizer(value.toString()); // 遍历所有单词 while (itr.hasMoreTokens()) { // 将当前单词设置到 word 对象中 word.set(itr.nextToken()); // 输出 <单词, 1> 键值对 context.write(word, one); } } } // Reducer 类 public static class IntSumReducer extends Reducer<Text, IntWritable, Text, IntWritable> { // 用于存储单词计数结果的 IntWritable 对象 private IntWritable result = new IntWritable(); // reduce 方法处理每个 key(单词) 对应的所有 value(出现次数) public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { // 初始化单词计数总和 int sum = 0; // 遍历该单词的所有出现次数并累加 for (IntWritable val : values) { sum += val.get(); } // 将累加结果设置到 result 对象中 result.set(sum); // 输出最终的单词和对应的计数结果 context.write(key, result); } } // 主方法 public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); // 创建 Hadoop 配置对象 Job job = Job.getInstance(conf, "word count"); // 创建并配置 Job 实例 job.setJarByClass(WordCount.class); // 设置 Job 运行的类 job.setMapperClass(TokenizerMapper.class); // 设置 Map 任务的类 job.setCombinerClass(IntSumReducer.class); // 可选:在 Map 阶段后进行部分汇总,用于减少网络传输 job.setReducerClass(IntSumReducer.class); // 设置 Reduce 任务的类 job.setOutputKeyClass(Text.class); // 设置输出 key 的类型 job.setOutputValueClass(IntWritable.class); // 设置输出 value 的类型 FileInputFormat.addInputPath(job, new Path(args[1])); // 设置输入路径,从命令行参数获取 FileOutputFormat.setOutputPath(job, new Path(args[2])); // 设置输出路径,从命令行参数获取 System.exit(job.waitForCompletion(true) ? 0 : 1); // 提交作业并等待完成,根据作业结果返回退出码 } } -
构建程序:
mvn clean package构建产物在
target/my-hadoop-project-1.0-SNAPSHOT.jar。这就是我们要运行的 MapReduce 作业了。 -
准备输入数据。
最后,我们需要为 MapReduce 程序获取输入。这里我们自己创建一个输入文件:
vim input.txthello world hello hadoop welcome to the world of big data hadoop and big data
配置 MapReduce 作业
-
上传 MapReduce 作业到 Amazon S3。
我们一般将 MapReduce 作业保存到 S3 方便 EMR 集群直接访问。
mv target/my-hadoop-project-1.0-SNAPSHOT.jar job.jar aws s3 cp job.jar s3://my-bucket/job.jar -
上传输入数据到 Amazon S3:
aws s3 cp input.txt s3://my-bucket/input/ -
创建作业步骤配置。
我们要创建一个作业,让 EMR 集群运行
WordCount类,并以input目录下的文件为输入,并将结果输出到output目录。vim steps.json[ { "Type": "CUSTOM_JAR", "Name": "WordCount", "ActionOnFailure": "CONTINUE", "Jar": "s3://my-bucket/job.jar", "Args": ["s3://my-bucket/input/", "s3://my-bucket/output/"], "MainClass": "com.example.WordCount" } ]这个作业等同于在 NameNode 上运行如下命令:
hadoop jar \ s3://my-bucket/job.jar \ com.example.WordCount \ s3://my-bucket/input/ \ s3://my-bucket/output/
创建 EMR 集群
创建 EMR 集群
-
创建默认 IAM 角色:
aws emr create-default-roles -
查询 EMR 版本:
aws emr list-release-labels -
创建并启动 EMR 集群:
aws emr create-cluster \ --name 'MyEMRCluster' \ --release-label 'emr-7.3.0' \ --applications 'Name=Hadoop' \ --ec2-attributes KeyName='my-ssh-key' \ --instance-type 'm5.xlarge' \ --log-uri 's3://my-bucket/logs/' \ --instance-count 3 \ --use-default-roles \ --steps 'file://steps.json' \ --auto-terminate这里我们通过
--steps指定了要执行的作业,并且通过--auto-terminate指定了当作业执行完成时就终止 EMR 集群,以节省成本。启动集群需要一定时间,一般需要 5 分钟左右。
-
查看集群状态:
aws emr list-clusters --query 'Clusters[0].{Name:Name,Status:Status}' -
查看作业输出:
aws s3 sync s3://my-bucket/output output可以看到 MapReduce 程序生成了 4 个文件:
output ├── _SUCCESS ├── part-r-00000 ├── part-r-00001 └── part-r-00002其中,
_SUCCESS是空文件,标志着作业的成功完成。part-r-xxxxx是 Reducer 的输出。每个文件对应于一个 Reducer 任务的输出结果。文件内容如下:# part-r-00000 data 2 to 1 # part-r-00001 big 2 hadoop 2 of 1 the 1 world 2 # part-r-00002 and 1 hello 2 welcome 1可以看到,我们的 MapReduce 程序正确完成了单词计数任务。
添加任务
如果没有配置自动终止,则可以随时为集群添加新的任务:
CLUSTER_ID=$(aws emr list-clusters --query 'Clusters[0].Id' --output text)
aws emr add-steps \
--cluster-id $CLUSTER_ID \
--steps 'file://steps.json'
参见:add-steps | AWS CLI Command Reference
查看作业日志
-
查询作业执行状态:
CLUSTER_ID=$(aws emr list-clusters --query 'Clusters[0].Id' --output text) aws emr list-steps --cluster-id $CLUSTER_ID --query 'Steps[0]' --output json -
下载作业日志:
STEP_ID=$(aws emr list-steps --cluster-id $CLUSTER_ID --query 'Steps[0].Id' --output text) aws s3 sync s3://my-bucket/logs/$CLUSTER_ID/steps/$STEP_ID logs gunzip logs/*.gz
终止 EMR 集群
- 终止集群:
CLUSTER_ID=$(aws emr list-clusters --query 'Clusters[0].Id' --output text)
aws emr terminate-clusters --cluster-ids $CLUSTER_ID
- 检查集群:
aws emr list-clusters --query 'Clusters[*].{Id:Id,Name:Name,Status:Status}' --output json

浙公网安备 33010602011771号