首先翻译一下官方文档,然后写一个小的案例进行理解。更详细的内容可以参考最新的文档和源码(https://github.com/Yelp/mrjob)
官方链接:https://pythonhosted.org/mrjob/guides/writing-mrjobs.html#protocols
mrjob假设所有数据都是换行符分隔的字节。它使用protocols
序列和反序列化这些字节。每个job
(作业)都一个输入protocol
,一个输出procotol
和一个内部procotol
。
一个procotol
具有read()
方法和write()
方法。该 read()
方法将字节转换为python对象的键值(key和value)对。该write()
方法将一对Python对象
(表示key和value组成的键值对)转换回字节。
输入procotol
用于读取字节并发送给第一个mapper
(或者reducer,如果第一步骤中不使用mapper)。输出procotol
将最后一步输出以字节形式输出到输出文件。内部procotol
转换一个步骤的输出到下一个步骤的输入,如果该作业具有一个以上的步骤。
您可以指定作业使用的procotol
,如下所示:
class MyMRJob(mrjob.job.MRJob):
# these are the defaults
INPUT_PROTOCOL = mrjob.protocol.RawValueProtocol
INTERNAL_PROTOCOL = mrjob.protocol.JSONProtocol
OUTPUT_PROTOCOL = mrjob.protocol.JSONProtocol
默认输入协议是RawValueProtocol
,它只把每一行读成一个str。(该行不会有尾随换行符,因为MRJob
会将它删除 )。因此,默认情况下,在作业的第一步可以看到将每行输入转化为(None, line)的键值对。
默认输出和内部协议都是JSONProtocol
,它读取和写入由制表符分隔的JSON字符串。(默认情况下,Hadoop Streaming在对数据进行排序时使用制表符分隔一行内的键值对。)
如果你的头有点疼,可以这样想:RawValueProtocol
当你想读或写原始文本行时使用。JSONProtocol
当你想要读取或写入键值对(JSON编码字节的键值对)时使用。
注意: Hadoop Streaming不使用JSON或mrjob协议。它只是通过对第一个制表符前面的内容进行字符串比较来对行进行分组。
可以通过查看mrjob.protocol(https://pythonhosted.org/mrjob/protocols.html#module-mrjob.protocol) 来了解mrjob内置协议的完整列表。
脚注:
RawValueProtocol
是两个不同协议之一的别名,具体取决于您的Python版本。JSONProtocol
是四种不同实现之一的别名; 我们尝试使用(更快)ujson库(如果可用),如果没有,尝试rapidjson或者simplejson库。在前边都没有的情况下, 使用python内置json模块。让我们从多步骤工作中重新审视我们的示例。它有两个步骤,并将纯文本文件作为输入
class MRMostUsedWord(MRJob):
def steps(self):
return [
MRStep(mapper=self.mapper_get_words,
combiner=self.combiner_count_words,
reducer=self.reducer_count_words),
MRStep(reducer=self.reducer_find_max_word)
]
第一步调用mapper_get_words()
函数:
def mapper_get_words(self, _, line):
# yield each word in the line
for word in WORD_RE.findall(line):
yield (word.lower(), 1)
由于输入协议是RawValueProtocol
,键始终是None
,值将是行的文本
该函数丢弃中键(key),并对每行的每个单词返回(word, 1)形式的元组。由于内部协议是JSONProtocol
,输出都被序列化为JSON。序列化组件被写入stdout(标准输出),由制表符分隔并以换行符结尾,如下所示:
"mrjob" 1
"is" 1
"a" 1
"python" 1
接下来是combiner
和reducer
def combiner_count_words(self, word, counts):
# sum the words we've seen so far
yield (word, sum(counts))
def reducer_count_words(self, word, counts):
# send all (num_occurrences, word) pairs to the same reducer.
# num_occurrences is so we can easily use Python's max() function.
yield None, (sum(counts), word)
在这两种情况下,字节通过JSONProtocol
被反序列化为键值对(word, counts) ,并以相同的方式输出被序列化为JSON(因为二者都后跟另一个步骤)。它看起来就像第一个mapper的输出,但结果被相加如下:
"mrjob" 31
"is" 2
"a" 2
"Python" 1
最后一步是一个reducer:
# discard the key; it is just None
def reducer_find_max_word(self, _, word_count_pairs):
# each item of word_count_pairs is (count, word),
# so yielding one results in key=counts, value=word
yield max(word_count_pairs)
由于此步骤的所有输入都具有相同的键(None),因此单个任务将获得所有行。同样,JSONProtocol
将处理反序列化并生成参数给reducer_find_max_word()
输出协议也是JSONProtocol
,所以最终输出将是:
31 "mrjob"
我们完成了!但那有点难看; 根本没有必要写出键(key)。所以我们使用JSONValueProtocol
协议,所以我们只看到JSON编码的值:
class MRMostUsedWord(MRJob):
OUTPUT_PROTOCOL = JSONValueProtocol
现在我们应该有与examples/mr_most_used_word.pymrjob
源代码相同的代码。让我们尝试运行它(-q
防止调试日志输出):
$ python mr_most_used_word.py README.txt -q
"mrjob"
Hooray!
通常情况下,你只需要设置一个或多个类属性 INPUT_PROTOCOL, INTERNAL_PROTOCOL以及 OUTPUT_PROTOCOL:
class BasicProtocolJob(MRJob):
# get input as raw strings
INPUT_PROTOCOL = RawValueProtocol
# pass data internally with pickle
INTERNAL_PROTOCOL = PickleProtocol
# write output as JSON
OUTPUT_PROTOCOL = JSONProtocol
如果您需要更复杂的行为,你可以重写 input_protocol(), internal_protocol()或者 output_protocol()方法并返回一个协议对象实例。这是一个定义命令行选项文档(https://pythonhosted.org/mrjob/guides/writing-mrjobs.html#writing-cl-opts)中的示例:
class CommandLineProtocolJob(MRJob):
def configure_options(self):
super(CommandLineProtocolJob, self).configure_options()
self.add_passthrough_option(
'--output-format', default='raw', choices=['raw', 'json'],
help="Specify the output format of the job")
def output_protocol(self):
if self.options.output_format == 'json':
return JSONValueProtocol()
elif self.options.output_format == 'raw':
return RawValueProtocol()
最后,如果您需要使用完全不同的协议分配概念,则可以覆盖pick_protocols():
class WhatIsThisIDontEvenProtocolJob(MRJob):
def pick_protocols(self, step_num, step_type):
return random.choice([Protocololol, ROFLcol, Trolltocol, Locotorp])
协议是一个拥有read(self, line)
和write(self, key, value)
方法的对象。read()
方法接受一个bytestring
并返回一个2元组的解码对象,write()
方法获取键和值并返回字节以传递回Hadoop Streaming或作为输出。
协议不必担心添加或删除换行符; 这由MRJob自动处理。
这是mrjob的JSON协议的简化版本:
import json
class JSONProtocol(object):
def read(self, line):
k_str, v_str = line.split('\t', 1)
return json.loads(k_str), json.loads(v_str)
def write(self, key, value):
return '%s\t%s' % (json.dumps(key), json.dumps(value))
你还可以通过缓存序列化/反序列化key的结果来显着提高性能。具体请查看查看mrjob.protocol(https://pythonhosted.org/mrjob/protocols.html#module-mrjob.protocol) 示例的源代码 。
from mrjob.job import MRJob
class MRWordCount(MRJob):
def mapper(self, key, line):
print('key---',key)
for word in line.split():
yield(word, 1)
def reducer(self, word, counts):
yield(word, sum(counts))
if __name__ == '__main__':
MRWordCount.run()
jack be nimble
jack be quick
jack jumped over the candlestick
RawValueProtocol
是如何处理的python3 demo.py --mapper /home/hadoop/HadoopWithPython-master/resources/input.txt
key--- None
"jack" 1
"be" 1
"nimble" 1
key--- None
"jack" 1
"be" 1
"quick" 1
key--- None
"jack" 1
"jumped" 1
"over" 1
"the" 1
"candlestick" 1