Hadoop源码解析之distributedshell
1. 概述
本文介绍YARN自带的一个非常简单的应用程序编程实例—distributedshell,他可以看做YARN编程中的“helloworld”,它的主要功能是并行执行用户提供的shell命令或者shell脚本。本文主要介绍distributedshell的实现方法。
版本为hadoop-2.5.2
Distributedshell的源代码在文件夹
hadoop-2.5.2-src\hadoop-yarn-project\hadoop-yarn\hadoop-yarn-applications\hadoop-yarn-applications-distributedshell下。
Distributedshell的实现完全与一般YARN应用程序的编写方法完全一致。
2. 客户端解析
DistributedshellClient的入口main函数如下:
public static void main(String[]args) {
boolean result = false;
try {
Client client =new Client();
LOG.info("Initializing Client");
try {
boolean doRun = client.init(args);
if (!doRun) {
System.exit(0);
}
} catch (IllegalArgumentExceptione) {
System.err.println(e.getLocalizedMessage());
client.printUsage();
System.exit(-1);
}
result = client.run();
} catch (Throwable t) {
LOG.fatal("Error running CLient",t);
System.exit(1);
}
…
}
2.1 构造yarn的客户端对象yarnClient。
创建时会指定本Client要用到的AM。 创建yarnClient。yarn将client与RM的交互抽象出了编程库YarnClient,用以应用程序提交、状态查询和控制等,简化应用程序。
public Client(Configurationconf) throws Exception {
this( //指定AM
"org.apache.hadoop.yarn.applications.distributedshell.ApplicationMaster",
conf);
}
利用YarnClient类创建一个可以直接与ResourceManager交互的客户端yarnClient。
Client(String appMasterMainClass,Configuration conf) {
…
yarnClient = YarnClient.createYarnClient(); //创建yarnClient
yarnClient.init(conf);
opts = new Options();
opts.addOption("appname", true, "ApplicationName. Default value - DistributedShell");
opts.addOption("priority", true, "ApplicationPriority. Default 0");
}
2.2 初始化
init会解析命令行传入的参数,例如使用的jar包、内存大小、cpu个数等。 代码里使用GnuParser解析:init时定义所有的参数opts(可以认为是一个模板),然后将opts和实际的args传入解析后得到一个CommnadLine对象,后面查询选项直接操作该CommnadLine对象即可,如cliParser.hasOption("help")和cliParser.getOptionValue("jar")
public boolean init(String[] args)throws ParseException {
CommandLine cliParser =new GnuParser().parse(opts,args);
amMemory = Integer.parseInt(cliParser.getOptionValue("master_memory","10"));
amVCores = Integer.parseInt(cliParser.getOptionValue("master_vcores","1"))
;
…
2.3 运行
Run方法中,启动客户端
DistributedShellClient中最重要的是函数为run(),该函数实现过程如下:
public boolean run() throws IOException, YarnException {
…
//先启动yarnClient,会建立跟RM的RPC连接,之后就跟调用本地方法一样。通过此yarnClient查询NM个数、NM详细信息(ID/地址/Container个数等)
yarnClient.start();
YarnClusterMetrics clusterMetrics= yarnClient.getYarnClusterMetrics();
//通过yarnClient向ASM获取全部节点信息:
List<NodeReport>clusterNodeReports = yarnClient.getNodeReports(NodeState.RUNNING);
//收集提交AM所需的信息
YarnClientApplication app = yarnClient.createApplication();//创建app
GetNewApplicationResponse appResponse = app.getNewApplicationResponse();
…
//构造ApplicationSubmissionContext,用于提交ApplicationMaster。
ApplicationSubmissionContext appContext = app.getApplicationSubmissionContext();
//构造AM的container,加载上下文,包含本地资源,环境变量,实际命令。
ContainerLaunchContext amContainer = Records.newRecord(ContainerLaunchContext.class);
//AM需要的本地资源,如jar包、log文件
Map<String, LocalResource> localResources = new HashMap<String, LocalResource>();
FileSystem fs = FileSystem.get(conf);
addToLocalResources(fs,appMasterJar,appMasterJarPath, appId.toString(),localResources,null);//添加localResource
//Set the log4j properties if needed
if (!log4jPropFile.isEmpty()) {
fs,log4jPropFile,log4jPath, appId.toString(),localResources,null);
}
//添加localResource到amContainer。
amContainer.setLocalResources(localResources);
//设置环境变量
Map<String, String> env = newHashMap<String, String>();
env.put(DSConstants.DISTRIBUTEDSHELLSCRIPTLOCATION,hdfsShellScriptLocation);
env.put(DSConstants.DISTRIBUTEDSHELLSCRIPTTIMESTAMP,Long.toString(hdfsShellScriptTimestamp));
env.put(DSConstants.DISTRIBUTEDSHELLSCRIPTLEN,Long.toString(hdfsShellScriptLen));
//添加nev到amContainer。
amContainer.setEnvironment(env);
//添加命令行到amContainer
List<String>commands = new ArrayList<String>();
commands.add(command.toString());
amContainer.setCommands(commands);
//添加验证信息到amContainer
DataOutputBuffer dob =new DataOutputBuffer();
credentials.writeTokenStorageToStream(dob);
ByteBuffer fsTokens = ByteBuffer.wrap(dob.getData(),0, dob.getLength());
amContainer.setTokens(fsTokens);
// 添加amContainer到appContext
appContext.setAMContainerSpec(amContainer);
//设置优先级
appContext.setPriority(pri);
//设置队列
appContext.setQueue(amQueue);
//最后提交AM到yarnClient
yarnClient.submitApplication(appContext);
//启动监控。 Client只关心自己提交到RM的AM是否正常运行,而AM内部的多个task,由AM管理。如果Client要查询应用程序的任务信息,需要自己设计与AM的交互。
return monitorApplication(appId);
总的来说,Client做的事情比较简单,即建立与RM的连接,提交AM,监控AM运行状态。
3. ApplicationMaster解析
AM简化框架如下:
publicstaticvoidmain(String[]args) {
boolean result = false;
ApplicationMaster appMaster =new ApplicationMaster();
boolean doRun = appMaster.init(args);
if (!doRun) {
System.exit(0);
}
appMaster.run();
result = appMaster.finish();
yarn抽象了两个编程库,AMRMClient和NMClient(AM和RM都可以用),简化AM编程。
3.1设置RM、NM消息的异步处理方法
//设置并启动RM消息的响应类RMCallbackHandler
AMRMClientAsync.CallbackHandler allocListener = newRMCallbackHandler();
amRMClient= AMRMClientAsync.createAMRMClientAsync(1000, allocListener);
amRMClient.init(conf);
amRMClient.start();
//设置并启动NM消息的响应类NMCallbackHandler
containerListener
nmClientAsync = newNMClientAsyncImpl(containerListener);
nmClientAsync.init(conf);
nmClientAsync.start();
3.2 向RM注册
RegisterApplicationMasterResponse response =amRMClient
.registerApplicationMaster(appMasterHostname,appMasterRpcPort,appMasterTrackingUrl);
3.3计算需要的Container,向RM发起请求
for (int =0; i < numTotalContainersToRequest; ++i) {
ContainerRequest containerAsk = setupContainerAskForRM();
amRMClient.addContainerRequest(containerAsk);
}
private ContainerRequest
Priority pri= Records.newRecord(Priority.class);
Resource capability = Records.newRecord(Resource.class);
//指定需要的memory/cpu能力
capability.setMemory(containerMemory);
capability.setVirtualCores(containerVirtualCores);
ContainerRequest request=new ContainerRequest(capability,null,null,pri);
return request;
}
3.4 RM分配Container给AM,AM启动任务
RMCallbackHandler RM消息的响应,由RMCallbackHandler处理。示例中主要对前两种消息进行了处理。
private class MCallbackHandler implements AMRMClientAsync.CallbackHandler {
//处理消息:Container执行完毕。在RM返回的心跳应答中携带。如果心跳应答中有已完成和新分配两种Container,先处理已完成
public void onContainersCompleted(List<ContainerStatus> completedContainers) {}
...
//处理消息:RM新分配Container。在RM返回的心跳应答中携带
public void onContainersAllocated(List<Container> allocatedContainers) {}
public void onShutdownRequest() {done= true;}
//节点状态变化
public void onNodesUpdated(List<NodeReport> updatedNodes) {}
public floatgetProgress() {}
onContainersAllocated收到分配的Container之后,会提交任务到NM。
public void onContainersAllocated(List<Container> allocatedContainers) {
for (Container allocatedContainer: allocatedContainers) {
//创建runnable容器
LaunchContainerRunnable runnableLaunchContainer=
new
//新建线程
new
// launch and start the container on a separate thread to keep
// the main thread unblocked
// as all containers may not be allocated at one go.
launchThreads.add(launchThread);
//线程中提交Container到NM,不影响主流程
launchThread.start();
}
简单分析下LaunchContainerRunnable。该类实现自Runnable,其run方法准备任务命令
private class LaunchContainerRunnable implements Runnable {
public LaunchContainerRunnable(
Container lcontainer, NMCallbackHandlercontainerListener) {
this.container= lcontainer; //创建时记录待使用的Container
this.containerListener= containerListener;
}
public void run() {
//根据命令、环境变量、本地资源等创建Container加载上下文
ContainerLaunchContext ctx = Records.newRecord(ContainerLaunchContext.class);
ctx.setEnvironment(shellEnv);
ctx.setLocalResources(localResources);
ctx.setCommands(commands);
ctx.setTokens(allTokens.duplicate());
containerListener.addContainer(container.getId(), container);
//异步启动Container
nmClientAsync.startContainerAsync(container, ctx);
}
onContainersCompleted的功能比较简单,收到Container执行完毕的消息,检查其执行结果,如果执行失败,则重新发起请求,直到全部完成。
NM消息的响应,由NMCallbackHandler处理。
在示例里,回调句柄对NM通知过来的各种事件的处理比较简单,只是修改AM维护的Container执行完成、失败的个数。这样等到有Container执行完毕后,可以重启发起请求。失败处理和上面Container执行完毕消息的处理类似,达到了上面问题里所说的loopback效果。
static class NMCallbackHandler
implements NMClientAsync.CallbackHandler {
@Override
public void onContainerStopped(ContainerIdcontainerId) {
@Override
public void onContainerStatusReceived(ContainerIdcontainerId,
@Override
public void onContainerStarted(ContainerId containerId,
...
总的来说,AM做的事就是向RM/NM注册回调函数,然后请求Container;得到Container后提交任务,并跟踪这些任务的执行情况,如果失败了则重新提交,直到全部任务完成。
参考:
http://www.datastart.cn/tech/2015/05/05/yarn-dist-shell.html