Hadoop源码解析之distributedshell

25 篇文章 0 订阅
14 篇文章 1 订阅

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()) {

      addToLocalResources(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 = createNMCallbackHandler();

    nmClientAsync = newNMClientAsyncImpl(containerListener);

    nmClientAsync.init(conf);

    nmClientAsync.start();

 

3.2 向RM注册

RegisterApplicationMasterResponse response =amRMClient

.registerApplicationMaster(appMasterHostname,appMasterRpcPort,appMasterTrackingUrl);

 

3.3计算需要的Container,向RM发起请求

for (int i =0; i < numTotalContainersToRequest; ++i) {

      ContainerRequest containerAsk = setupContainerAskForRM();

      amRMClient.addContainerRequest(containerAsk);

    }

private ContainerRequest setupContainerAskForRM() {

    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 LaunchContainerRunnable(allocatedContainer,containerListener);

    //新建线程

    Thread launchThread = new Thread(runnableLaunchContainer); 

    // 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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值