Hadoop Streaming

hadoop 基础命令

下载

下载单个文件:hadoop fs -get /hdfs/path/to/file local_file

合并下载文件夹下所有文件:将多个part合并到本地文件

hadoop fs -getmerge /hdfs/path local_file

上传

hadoop fs -put [-f] local_file /hdfs/path
hadoop fs -copyFromLocal [-f] local_file /hdfs/path
-f表示如果文件已存在,则强制覆盖,否则会提示文件已存在不会上传。
在上传的过程中不会逐渐覆盖原文件,而是上传到临时文件,并且用同路径下同名的._COPYING_标志正在上传,上传完成后才会覆盖原文件,并删除_COPYING_标志文件。所以我们不用担心在上传过程中有其它流程下载该文件出现不完整的问题。

批量拷贝
hadoop distcp 命令可以启动MapReduce任务来拷贝hdfs路径,这比单线程的fs -cp拷贝要快很多。
常用选项:hadoop distcp -Dipc.client.fallback-to-simple-auth-allowed=true -Dyarn.app.mapreduce.am.resource.mb=9000 -Dyarn.app.mapreduce.am.command-opts=-Xmx8000m -update -skipcrccheck src_path dst_path
会执行增量拷贝,将src_path下边的子文件(夹)拷贝到dst_path.

其它类似于linux命令的选项:

  • mv 移动、重命名
  • ls -h
  • mkdir -p
  • cp 拷贝的过程中,正在拷贝的文件会有一个._COPYING_后缀. 成功输出的文件夹里边会有_SUCCESS文件,可以用来判断该流程是否已经执行成功。
  • du -h,hadoop fs -du -s input
    第一列为文件或文件夹中内容的占用大小,单位是Byte。hadoop fs -du -h 会列出每个子文件的大小。

参考 https://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/FileSystemShell.html

hadoop crc文件

运行hadoop -getmerge命令时会在输出目录下产生一个.xxx.crc的隐藏文件,用于校验。-get 选项在加 -crc子选项时也会在本地产生校验文件。源文件越大,产生的校验文件就可能越大。
运行getmerge命令之后可以手动删除来节省空间:find . -type f -name '*.crc' -exec rm {} +
参考:here and there

此外,在上传文件如果本地存在校验文件,会在本地校验后再上传,如在使用-files选项来上传一些脚本或数据时,如果修改了内容导致校验后和校验文件中不一致时会发生失败,此时可删除校验文件。参考这里

查找删除crc文件:locate *.crc | xargs rm -f {} ;

查看与停止hadoop任务

可使用yarn命令对job进行搜索,再进行kill
yarn application -list | grep job_name
yarn application -kill $ApplicationId

或者打开hadoop web UI界面中对job_name进行搜索

job_前缀后边的id和application_前缀后边的id是一样的,就是任务id。

yarn application命令相比旧版本的hadoop job命令,多了任务名称、任务执行进度百分比的显示。

1. version 小于2.3.0

查看正在运行的 Hadoop 任务:hadoop job -list
关闭Hadoop 任务进程:hadoop job -kill $jobId
组合以上两条命令就可以实现 kill 掉指定用户的 job

for i in `hadoop job -list | grep -w  username| awk '{print $1}' | grep job_`; do hadoop job -kill $i; done

username 就是你希望关闭 Hadoop 任务的用户。
job 在高版本hadoop中已不建议使用,DEPRECATED。

2. version 大于等于2.3.0

查看正在运行的 Hadoop 任务:yarn application -list
关闭 Hadoop 任务进程:yarn application -kill $ApplicationId

kill批量提交的任务:yarn application -list | grep $AppName | cut -f1 | xargs -n1 yarn application -kill

OOM 问题

GC overhead limit exceeded原因分析及解决方案

hadoop命令执行任务时出现:java.lang.OutOfMemoryError: GC overhead limit exceeded错误
oracle官方给出了这个错误产生的原因和解决方法:

Exception in thread thread_name: java.lang.OutOfMemoryError: GC Overhead limit exceeded Cause: The detail message "GC overhead limit exceeded" indicates that the garbage collector is running all the time and Java program is making very slow progress. After a garbage collection, if the Java process is spending more than approximately 98% of its time doing garbage collection and if it is recovering less than 2% of the heap and has been doing so far the last 5 (compile time constant) consecutive garbage collections, then a java.lang.OutOfMemoryError is thrown. This exception is typically thrown because the amount of live data barely fits into the Java heap having little free space for new allocations.
Action: Increase the heap size. The java.lang.OutOfMemoryError exception for GC Overhead limit exceeded can be turned off with the command line flag -XX:-UseGCOverheadLimit.

原因:
大概意思就是说,JVM花费了98%的时间进行垃圾回收,而只得到2%可用的内存,频繁的进行内存回收(至少已经进行了5次连续的垃圾回收),JVM就会曝出这个错误。
这个是jdk1.6新增的错误类型。

如果没有这个异常,会出现什么情况呢?经过垃圾回收释放的2%可用内存空间会快速的被填满,迫使GC再次执行,出现频繁的执行GC操作, 服务器会因为频繁的执行GC垃圾回收操作而达到100%的时使用率,服务器运行变慢,应用系统会出现卡死现象,平常只需几毫秒就可以执行的操作,现在需要更长时间,甚至是好几分钟才可以完成。

假如hadoop任务运行失败了,但并没有给出上述那么明显的原因,可以到hadoop web页面中查找history失败的任务,查看日志,如果是因为:java.lang.OutOfMemoryError: Java heap space 也有可能是这个原因引起的。

最先考虑的解决办法是增加heap堆内存。其次是优化代码减少内存对象的创建,升级JDK使用新的垃圾回收算法等。
解决办法:增加客户端端JVM heap大小:
export HADOOP_CLIENT_OPTS="-Xmx4g" 或者
export HADOOP_CLIENT_OPTS="$HADOOP_CLIENT_OPTS -Xmx4g"

参考:

har 归档

HDFS中提供了archive功能,将文件压缩起来,减少空间使用。
HDFS的归档压缩文件的后缀名是.har,一个har文件中包括文件的元数据(保存在_index和_masterindex)以及具体数据(保存在part-XX)。

任务队列与quota限制

任务队列
任务队列与计算资源分配有关。hadoop管理员可以配置每个队列可由哪些用户来使用,多个用户可以共享计算队列。
队列资源限制包括:CPU核心数、存储空间大小、文件数量等。

客户端身份验证
不同的客户端可能具有不同的用户身份验证方式,如某个客户端执行hadoop fs命令时会读取 ~/.ugi_config 文件中记录的用户名作为用户身份,在hadoop streaming通过 -D hadoop.client.ugi=xx,xx 来指定提交任务的用户。某些旧客户端可能通过 -D hadoop.job.ugi指定。

quota限制
运行hadoop的过程中出现 NSQuotaExceededException 则是文件数目超出了quota限制,
查看HDFS目录的Quota信息:
hadoop fs -count -q -v /user/xxx
要配置quota信息需要hdfs管理员来操作(hdfs dfsadmin),而我们遇到这种情况优先清理文件或者合并小文件,减少文件数量。
如果管理员未对某个目录设置quota限额,则显示为 none 和 inf。

$ hadoop fs -count -q -v /path/to/directory
  QUOTA       REM_QUOTA     SPACE_QUOTA    REM_SPACE_QUOTA    DIR_COUNT   FILE_COUNT       CONTENT_SIZE PATHNAME
   none       inf      54975581388800      5277747062870      3922        418464    16565944775310 /path/to/directory

参考:

清空 trash

rm命令删除时会移动到回收站,还可以找回,据说可以设置自动删除过久的文件。

清空回收站:hdfs dfs -expunge

直接删除文件,而不是移动到回收站:hdfs dfs -rm -skipTrash /path/to/file/you/want/to/remove/permanently

hadoop性能调优

(1)HDFS block size
HDFS作为mapreduce的基础分布式文件系统,对mapred的运行效果也有直接的影响。主要的参数是block.size,在网络环境很好、存储空间较大的集群中,可以将这个参数提升,大小可以到128或256或更大(默认64M)。次要的参数是备份数(默认是3备份),由于mapred执行时需要的数据是分布式存储的,如果备份数较少,那么往往需要将其它节点的数据拉到本地增加网络IO的开销。备份数较多时本地便能查找到数据时任务的执行速度会更快,但是副作用是磁盘开销过大。
(2)每台计算节点上的并行任务数:mapred.tasktracker.map.tasks.maximum 、 mapred.tasktracker.reduce.tasks.maximum。通常设置为占满CPU核数,如分别设置为$CORES/2-1. 决定了集群能够同时运行的进程数量。
(3)JVM 内存相关参数。比如sort时可用的buffer大小 io.sort.mb (溢出io.sort.spill.percent的部分会被spill进入硬盘,降低性能)

Hadoop Streaming

Hadoop Streaming是Hadoop提供的一种编程工具,提供了一种非常灵活的编程接口, 允许用户使用任何语言编写MapReduce作业,是一种常用的非Java API编写MapReduce的工具。
Hadoop Streaming要求用户编写的Mapper/Reducer从标准输入(stdin)中读取数据,将结果写入到标准输出(stdout)中, 这非常类似于Linux的管道机制。这里的streaming是输入输出流的意思,但 Hadoop Streaming 仍属于离线批处理框架,不能进行实时数据流的流式处理,这与Spark Streaming/Storm等流式处理框架有比较大的区别。

再介绍一下MapReduce框架的执行流程。
map分两个阶段(phase):map、combine,combine可以将相同的key group在一起进行一些操作(Java API中可以指定combiner),在hadoop streaming中仅指定mapper。
reduce分三个阶段:shuffle & sort 、partition、reduce
mapper的数据按key分组后在组内排序,通过partitioner分派给各个reducer(通常是hash方式),reducer内执行reduce,相同的key会被分到同一个reducer,且已经按key排好序。
参考

mapreduce程序

hadoop streaming 支持命令行来对输入流进行处理,因此可以用各类开发语言。复杂的处理常常需要打包完整的运行环境。
用法如下:
$HADOOP_HOME/bin/hadoop jar $HADOOP_HOME/hadoop-streaming.jar 【选项】
例如:

$HADOOP_HOME/bin/hadoop jar $HADOOP_HOME/hadoop-streaming.jar \
    -input myInputDirs \
    -output myOutputDir \
    -mapper "cat" \
    -reducer "python reducer.py" \
    -files ./reducer.py \
    -partitioner org.apache.hadoop.mapred.lib.KeyFieldBasedPartitioner \
    -D stream.num.map.output.key.fields=2

hadoop streaming必须要同时指定 mapper和reducer,如果我们只用到其中一个,可以将另一个指定为cat。
网上说也可以用org.apache.hadoop.mapred.lib.IdentityMapper、org.apache.hadoop.mapred.lib.IdentityReducer (但我在使用这两个类时发现多出了第一列,像是索引,最好别使用这两个类,老实用cat)。

参数设置

本部分介绍hadoop streaming中可通过 -D / -jobconf 指定的参数。

内存设置

限制MapTask 和 ReduceTask最多使用的内存限制:

  • mapreduce.map.memory.mb: 为每一个MapTask申请的资源,也就是每一个MapTask最多只能使用这么内存,默认1024M
  • mapreduce.reduce.memory.mb:为每一个ReduceTask申请的资源,一个ReduceTask最多只能使用这么内存,默认1024M

如果只是把任务container所允许的资源调高了,但是启动的YarnChild任务JVM参数并没有调高,那么还是不起作用:

  • mapreduce.map.java.opts:这个是Container启动MapTask的YarnChild进程设置的堆大小参数,默认1024M
  • mapreduce.reduce.java.opts: 这个是Container启动ReduceTask的YarnChild进程设置的堆大小参数, 默认1024M
    这里的JVM参数有-Xms2000m -Xmx4000m 或者mapreduce.reduce.java.opts.max.heap=3.3G

所以理想的配置应该是java.opts的值必须大于等于memory.mb的值.否则配置不当的方式也会引发频繁的Full GC.

排序、分桶参数设置

基本概念

Partition:分桶过程,用户输出的key经过partition分发到不同的reduce里,因而partitioner就是分桶器,一般用平台默认的hash分桶也可以自己指定。
Key:是需要排序的字段,相同分桶&&相同key的行排序到一起。先进行的hash分桶,再在每个桶内各自按指定的key排序。

参数设置

在streaming模式中, hadoop默认会把map输出的一行中遇到的第一个设定的字段分隔符前面的部分作为key,后面的作为value,如果输出的一行中没有指定的字段分隔符,则整行作为key,value被设置为空字符串。streaming中默认字段分割符是tab

  • stream.map.output.field.separator 设置map输出的字段分隔符
  • num.key.fields.for.partition 指定partition分桶时,按照分隔符切割后,其中用于分桶的key所占的列数(前几个字段),需要配合 -partitioner org.apache.hadoop.mapred.lib.KeyFieldBasedPartitioner 使用
  • stream.num.map.output.key.fields 设置map输出的前几个字段作为key。而reduce收到的数据正是经过按这前几个字段排序的。需要注意的是如果将所有的列作为key,那么reduce收到的数据会多一列(空的value),这种情况下将行尾的\t或者设定的分隔符去除即可。

hadoop streaming默认按 tab 分割输入行,按第一列进行partition,每个partition中,按第一列字符序排序后送入reducer。
如何按某一列的数字大小来排序?
通过指定比较器及其选项来实现:

  • mapred.output.key.comparator.class=org.apache.hadoop.mapred.lib.KeyFieldBasedComparator
  • mapred.text.key.comparator.options=-k2,2nr # 同时需要 stream.num.map.output.key.fields >=2 才可以

KeyFieldBasedComparator实现了Unix/GNU Sort的一些功能特性,可以采用-k来指定排序的key以及排序选项(比较数字、从大到小等),参考sort命令。

stream.num.map.output.key.fields指明了前n列作为参与排序的key列,具体的子列排序方式通过mapred.text.key.comparator.options来指定。

在按照某些排序时,还要考虑sort是在partition内部,因此需要指定正确的partition key,否则达不到全局sort的目的。通过 num.key.fields.for.partition 或 mapred.text.key.partitioner.options 来指定。后者可以认为是前者的升级版,它可以指定不仅限于key中的前几个字段用做partition,而是可以单独指定key中某个字段或者某几个字段一起做partition。如果两个参数一起使用,则只采纳 num.key.fields.for.partition 。

示例:mapred.text.key.partitioner.options=-k1,2 等价于 num.key.fields.for.partition=2

例子:对(query, pv) pair进行排序

简单方法:按行调换query、pv顺序=>(pv, query), 再采用以上的参数设置按第一列的数值从大到小排序。这样每个reducer的输入就是有序的。在得到有序的各个part文件之后,下载之后通过sort -m归并排序各个文件,得到单一的文件。如果数据量比较少,设置reducer数量为1也行,就不再需要下载后归并。

计算任务数量

hadoop Mapper的数量在默认情况下不可直接控制干预,因为Mapper的数量由输入的大小和个数决定。hadooop提供了一个设置map个数的参数mapred.map.tasks,我们可以通过这个参数来控制map的个数。但是通过这种方式设置map的个数,并不是每次都有效的。原因是mapred.map.tasks只是一个hadoop的参考数值,最终map的个数,还取决于其他的因素。参数map的数量仅用于参考,实际可能会多于设置的数量或少于设置的数量,具体的数量可以在web UI界面中查看任务的map数量。
Reducer的数量可以通过参数指定,输出文件的数量就是Reducer的数量,最后可以通过-merge或者-cat 合并文件。

通过参数 -D mapred.map.tasks=1000 设置的mapper数量不一定会生效,主要取决于输入文件的分块的数量。
因此通常只需要设置-D mapred.reduce.tasks。
网上有一些设置mapper数量的教程可参考,相对比较麻烦。

slowstart

在map之后进行reduce前,需要进过shuffle, sort, 之后才是真正的reduce。但是在运行进度显示时会将shuffle, sort作为reduce的一部分。sort、reduce需要在所有mapper运行完毕,shuffle完毕之后才开始。而shuffle可以不必等待所有mapper运行完毕就可以提前开始。通过reduce进度可以区分正在运行哪个步骤:0-33% 对应 shuffle, 34-66% sort, 67%-100% reduce. 有时候见到reduce卡在 33% 就是在等待所有 mappers 运行完毕,这种情况是配置了mapred.reduce.slowstart.completed.maps < 1.0. 在不经常同时跑多个任务时可以设置成0.9来节省时间。如果经常同时跑多个任务,那提前启动reduce(shuffle)会提前占用计算资源,但是在空闲等待,可能造成其它任务不能获取到计算资源。

缓存文件 cacheFile cacheArchive

通过-file选项执行hadoop streaming任务时,每台机器的多个mapper都会下载该文件,会造成1台机器多次下载相同的数据,效率低下。
任务使用-cacheFile和-cacheArchive选项在集群中分发文件和档案,选项的参数是用户已上传至HDFS的文件或档案的URI。这些文件和档案在不同的作业间缓存。
-cacheFile hdfs://host:fs_port/user/testfile.txt#testlink
在这个例子里,url中#后面的部分是建立在任务当前工作目录下的符号链接的名字。任务执行的当前工作目录下有一个“testlink”符号链接,它指向testfile.txt文件在本地的拷贝。
-cacheArchive hdfs://host:fs_port/user/testfile.jar#testlink2
会将压缩包解压后建立指向解压后的文件夹的符号链接,archive的压缩可以是zip、gz等。
比如调用自己指定的python bin版本:
-cacheArchive "${base_hdfs_path}/py3.tar.gz#py3"
-mapper "py3/bin/python3 xx.py"

调用外部python包

hadoop streaming除了可以使用cacheArchive自动解压hdfs上的压缩包,还可以通过-file 选项来上传文件到机群,因此可以可以将python代码或者shell、c++程序及依赖包等上传上去通过设置环境变量的方式来访问调用。
还可以通过 -cacheFile参数将hdfs的路径缓存到执行节点进行访问,参考hadoop streaming 使用 C++中文分词工具

对于python package可以通过python中的zipimport模块来加载zip压缩包导入所需的模块,比如对于jieba分词这个纯python实现的版本,从pypi或者github下载源码之后将其中的jieba目录压缩到一个压缩包内,如 jieba.mod (文件名随意,命令:zip -r jieba.mod jieba/), 调用方法示例:

import sys
sys.path.insert(0, './') # 防止在某些系统上没有包含当前路径

## 方式1:zipimporter导入压缩模块,文件参数可以是相对路径或绝对路径。
## 当需要读入压缩包中的数据文件时,hadoop streaming中运行时容易遇到 Not a directory的问题,
## 暂时没找到解决方法,可用方式2来运行
import zipimport
importer = zipimport.zipimporter('jieba.mod')
jieba = importer.load_module('jieba')

## 方式2:ZipFile 正常方式解压缩
from zipfile import ZipFile
zf = ZipFile('jieba.mod')
zf.extractall()
zf.close()

import jieba

text = u'在输出层后再增加CRF层,加强了文本间信息的相关性,针对序列标注问题,每个句子的每个词都有一个标注结果,对句子中第i个词进行高
维特征的抽取,通过学习特征到标注结果的映射,可以得到特征到任意标签的概率,通过这些概率,得到最优序列结果'
doc_cut = jieba.cut(text)
result = ' '.join(doc_cut)
print(result)

参考 如何在hadoop中使用外部的python程序文件

这种直接导入压缩包的方式适用于纯python开发或者已经包含了编译好的动态链接库的情况。对于包含cython模块,需要调用setup.py进行编译的则需要提前编译好再打包。是否需要cython编译,参考setup.py中是否包含编译模块的内容。

此外需要注意,如果模块中包含数据文件(数据、配置文件等),通过zipimporter导入后不能直接得到文件系统的文件位置,通过module_name.loader.get_data('module_name/README.txt')可以读取到文件内容。参考:
zipimport – Load Python code from inside ZIP archives

方法二:
我们可以在本地的python中安装上各种第三方库后再将文件打包上传,这样节点上就可以直接使用这些第三方库而不受系统限制(含C扩展的需要注意编译系统与节点系统一致)。然后通过cacheArchive将本地的python工具上传到hadoop streaming中使用,为了加速计算,可以选择使用python3和pypy。概括地讲,代码逻辑越复杂(计算密集)使用PyPy得到的性能提升越大。Portable PyPy能够比较完美地解决对运算性能以及运行环境的灵活配置的要求,其binary能够在大多数linux系统上直接运行。

以使用增强的正则库regex为例,将PyPy解压到本地后:

wget https://bootstrap.pypa.io/get-pip.py .
/PATH/TO/PYPY/bin/pypy get-pip.py
/PATH/TO/PYPY/bin/pip install regex

然后再重新将PyPy打包并上传到HDFS:

cd /PATH/TO/PYPY
tar czf pypy.tar.gz pypy
hadoop fs -put pypy.tar.gz /home/spider/personal/nobody/archives/

MapReduce时指定该包即可在节点上调用regex库。

参考 pypy_on_hadoop.md

区分输入文件

如果指定了多个hdfs地址作为输入,在python脚本中需要区分时,可以通过文件名来区分:

import os
filepath = os.environ.get('mapreduce_map_input_file')

if filepath.find("path_tag1") != -1:
    # 来自文件1
elif filepath.find("path_tag2") != -1:
    # 来自文件2

多路径输入

-input 选项可以指定多次,并且输入的hdfs路径可以用逗号、空格分割,也可以用数组转换成字符串,
参考 https://blog.csdn.net/wcy23580/article/details/84790208

压缩输出

参数: -Dmapred.output.compress=true -Dmapred.output.compression.codec=xx
注:两个参数需要同时指定,只指定第二个不起作用。

支持gzip、bz2、lzo、snappy等压缩格式,注意lzo、snappy非开源版hadoop自带,需要额外安装。但由于lzo、snappy比较流行,公司集群一般都带了。

codec列表:
org.apache.hadoop.io.compress.GzipCodec
org.apache.hadoop.io.compress.BZip2Codec
org.apache.hadoop.io.compress.SnappyCodec
com.hadoop.compression.lzo.LzopCodec

参考:

比较重要的一点是可切分性(splittable):BZip2, LZO, Snappy 格式是可切分的, 而 不可以。随机选择任意一个起点都可解压读取。

由于HDFS是按块存储的,一个文件会被切块后存储,并在MapReduce/Spark作业中按块进行多核处理。对于纯文本很容易按照换行符等进行分块,但是压缩文件是否可切分成独立的块与压缩算法有关。可切分的压缩格式则可以对每块分别解压缩,充分利用多核。
但是通常是对文本预先分割后再分别压缩成gzip等格式(hadoop streaming的mapred.reduce.tasks控制了输出文件数量),在后续处理时仍能够利用多核,因此不可切分性的缺点会被缩小。

Snappy、GZip

对纯文本文件进行Snappy或GZip压缩,不可切分。如果使用parquet (or ORC) 文件格式再进行Snappy/GZip压缩,则是可切分的。因为 parquet 总是可切分的,与压缩算法无关了。
可分割的codec需要实现 org.apache.hadoop.io.compress.SplittableCompressionCodec 接口,在 hadoop 2.7 中, org.apache.hadoop.io.compress.SnappyCodec 未实现这个接口,因此可被归类为不可切分
参考 Is Snappy splittable or not splittable?

LZO 压缩格式

LZO is a compression codec which gives better compression and decompression speed than gzip, and also the capability to split. LZO allows this because its composed of many smaller (~256K) blocks of compressed data, allowing jobs to be split along block boundaries, as opposed to gzip where the dictionary for the whole file is written at the top.

注意在原生的LZO版本中对纯文本的压缩是不可切分的,而 twitter 开源的 hadoop-lzo 做了些修改使其支持可切分,并能添加到hadoop压缩格式扩展中进行使用。但是需要产生额外的.lzo.index 后缀的索引文件来支持多核并行。hadoop-4mc是另一种可切分的压缩算法,不需要产生额外的索引文件。
参考 Best splittable compression for Hadoop input = bz2?

lzop命令用法:

# -l list -d decompress -v verbose
lzop -l test.lzo # 列出test.lzo中各个文件的压缩信息
cat test | lzop > t.lzo # 压缩标准输入并定向到标准输出
lzop -dv test.lzo # 解压test.lzo得到test文件,输出详细信息

新旧api

org.apache.hadoop.mapred is the older API and org.apache.hadoop.mapreduce is the new one.
在使用hadoop streaming时设置的参数也有不同的名称。
-D 是用来替代-jobconf的,-jobconf参数将被废弃
当前版本 job 中可以同时使用 -jobconf和-D。
但是如果job中有-D参数,那么一定要把所有的-D参数写在所有参数前面(包括-file和-input参数之前),要不然会报错。

代码示例:词频统计

除了调用hadoop的Java API编写Java代码之外,还可以使用hadoop-streaming 来调用python或shell脚本等来作为mapper/reducer。下面利用python实现WordCunt。

map部分的代码:mapper.py

#!/usr/bin/env python  
  
import sys  
  
# input comes from STDIN (standard input)  
for line in sys.stdin:  
    # remove leading and trailing whitespace  
    line = line.strip()  
    # split the line into words  
    words = line.split()  
    # increase counters  
    for word in words:  
        # write the results to STDOUT (standard output);  
        # what we output here will be the input for the  
        # Reduce step, i.e. the input for reducer.py  
        #  
        # tab-delimited; the trivial word count is 1  
        print '%s\t%s' % (word, 1)

mapper其实只做了一个功能,就是读取每行文字,然后分割成一个一个的单词。reduce部分代码:reducer.py

import sys  
  
current_word = None  
current_count = 0  
word = None  
  
# input comes from STDIN  
for line in sys.stdin:  
    # remove leading and trailing whitespace  
    line = line.strip()  
  
    # parse the input we got from mapper.py  
    word, count = line.split('\t', 1)  
  
    # convert count (currently a string) to int  
    try:  
        count = int(count)  
    except ValueError:  
        # count was not a number, so silently  
        # ignore/discard this line  
        continue  
  
    # this IF-switch only works because Hadoop sorts map output  
    # by key (here: word) before it is passed to the reducer  
    if current_word == word:  
        current_count += count  
    else:  
        if current_word:  
            # write result to STDOUT  
            print '%s\t%s' % (current_word, current_count)  
        current_count = count  
        current_word = word  
  
# do not forget to output the last word if needed!  
if current_word == word:  
    print '%s\t%s' % (current_word, current_count)

这里,对上面的map步骤的输出做聚合处理。
接下来,执行hadoop命令,设定-files参数上传脚本文件,通过-mapper、-reducer指定运行的脚本,通过 -input、-output 设定输入输出(HDFS的路径)。

hadoop jar /path/to/hadoop/tools/lib/hadoop-streaming-x.x.x.jar -files ./mapper.py,./reducer.py -mapper ./mapper.py -reducer ./reducer.py -input /hdfs/test.txt -output /hdfs/out

运行成功后再通过hadoop fs -cat/-text来检查结果。

参考:

posted @ 2022-07-28 20:15  康行天下  阅读(587)  评论(0编辑  收藏  举报