应用程序在提交 streaming job 的命令样例,需要指定 JAR 包以及相应的参数值。
$HADOOP_HOME/bin/hadoop jar $HADOOP_HOME/hadoop-streaming.jar /
-input myInputDirs /
-output myOutputDir /
-mapper myPythonScript.py /
-reducer /bin/wc /
-file myPythonScript.py /
-file myDictionary.txt
客户端运行该命令后,会启动一个进程,该进程的 main 函数为 StreamJob 类。 而由 StreamJob 类完成 job 的提交工作。
所以在 StreamJob 提交 job 之前,需要构造 map,reduce 的 class 类,以及 map,reduce 的 k,v 的 class name 等。
在 jar 包上,有 streaming 的 map,reduce 的 class ,如 PipeMapRed , PipeMapper , PipeReducer 。它们分别实现了接口 Mapper 和 Reducer 。
过程跟普通的 job 是一样的,只不过 streaming 的 job 在我们用其他语言写的 m,r 程序之上用 streaming 自带的 PipeMapper , PipeReducer 类包装了一层。
这二个类起了中间的数据传递作用。
PipeMapper , PipeReducer 二个类就有 configure,m/r,close 三个函数的。
在 configure 函数时创建相应语言的进程,进程的 main 函数为应用指定的程序,
同时创建 PipeMapper , PipeReducer 类与该 streaming 进程的标准输入输出流。
// Start the process
ProcessBuilder builder = new ProcessBuilder(argvSplit);
builder.environment().putAll(childEnv.toMap());
sim = builder.start();
clientOut_ = new DataOutputStream( new BufferedOutputStream(
sim .getOutputStream(),
BUFFER_SIZE ));
clientIn_ = new DataInputStream( new BufferedInputStream (
sim .getInputStream(),
BUFFER_SIZE ));
clientErr_ = new DataInputStream( new BufferedInputStream( sim .getErrorStream()));
errThread_ = new MRErrorThread();
errThread_ .start();
然后 map,reduce() 函数接口去调用上面创建好的输出流,就会把 K,V 输送到 streaming 相应的 map,reduce 程序。
@Override
public void writeKey(Object key) throws IOException {
writeUTF8(key);
clientOut .write( inputSeparator );
}
@Override
public void writeValue(Object value) throws IOException {
writeUTF8(value);
clientOut .write( '/n' );
}
而对 streaming 程序的 K,V 如何输送到 m,r 接口的呢。
可以看到,在 Map,reduce 接口函数的最开始有这么一个语句。
if ( outerrThreadsThrowable != null ) {
mapRedFinished();
throw new IOException ( "MROutput/MRErrThread failed:"
+ StringUtils.stringifyException (
outerrThreadsThrowable ));
}
如果线程对象为空,则需要去会创建一个线程,用来监听 streaming 进程的输出数据。
同是把输入输出流包装成 k,v 对的形式。
void startOutputThreads(OutputCollector output, Reporter reporter)
throws IOException {
inWriter_ = createInputWriter();
outReader_ = createOutputReader ();
outThread_ = new MROutputThread ( outReader_ , output, reporter);
outThread_ .start();
errThread_ .setReporter(reporter);
}
守护线程从 streaming 的输出流中得到 K,V 后,最后调用 collect 类把 K,V 对传送给 map , reduce 接口。
public void run() {
try {
// 3/4 Tool to Hadoop
while ( outReader .readKeyValue()) {
Object key = outReader .getCurrentKey();
Object value = outReader .getCurrentValue();
outCollector .collect(key, value);
numRecWritten_ ++;
long now = System.currentTimeMillis ();
if (now- lastStdoutReport > reporterOutDelay_ ) {
lastStdoutReport = now;
String hline = "Records R/W=" + numRecRead_ + "/" + numRecWritten_ ;
if (! processProvidedStatus_ ) {
reporter .setStatus(hline);
} else {
reporter .progress();
}
logprintln(hline);
logflush();
}
}
}
大体的上的流程就是以上源码所述的,关键就是 K,V 对的输入输出,二个进程之间的交互过程。
Streaming 是利用进程之间的标准输入输出流来进行通信,而 pipes 是利用 socket 来通信。
由于 hadoop 是用 java 开发的,所以需要做的就是将其他语言跟 java 进程通信,即跨语言的通信。主要的问题是数据类型的转化问题,因为最低层都是走字节数组的方式。
在 pipes 包里面实现了 c/c++ 与 java 之间的通信。
同样有提交 job 的 main 函数类 Submitter, 以及管道的 m/r 接口实现类, PipesMapRunner 和 PipesReducer 。
通信协议包装在 DownwardProtocol 接口。而接口的具体实现类在 Application 类中的 BinaryProtocol 类。这些通信协议与 java 与 C 共同组成。
C++ 的实现在 HadoopPipes.cc 文件中。
process = runClient (cmd, env);
clientSocket = serverSocket .accept();
handler = new OutputHandler<K2, V2>(output, reporter, recordReader);
K2 outputKey = (K2)
ReflectionUtils.newInstance (outputKeyClass, conf);
V2 outputValue = (V2)
ReflectionUtils.newInstance (outputValueClass, conf);
downlink = new BinaryProtocol <K1, V1, K2, V2>( clientSocket , handler ,
outputKey, outputValue, conf);
downlink .start();
downlink .setJobConf(conf);
同样先创建起 pipes 的进程,然后启动网络端口,让 pipes 进程来连接 MR 进程,然后创建输入输出类, OutputHandler 和 BinaryProtocol ,所以具体的通信就在这二个类中。
总结:先启动一个其他语言的进程,然后与之通信,将 K,V 对输入输出到该进程,所以方式有很多种。这里有标准输入输出和网络通信。