由ApplicationMaster启动一个Container的步骤

本文详细解析了如何使用YARN框架申请容器、为容器分配任务以及轮询获取容器状态的过程,包括与ResourceManager的交互、ApplicationMaster的注册与申请容器请求的构造等关键步骤。

 

 

1. 申请Container

1) 连接ResourceManager

		Configuration conf = new Configuration();
		YarnRPC rpc = YarnRPC.create(conf);
		YarnConfiguration yarnConf = new YarnConfiguration(conf);
		// 获取ResourceManager的地址
		InetSocketAddress rmAddress = NetUtils.createSocketAddr(yarnConf.get(
			YarnConfiguration.RM_SCHEDULER_ADDRESS,
			YarnConfiguration.DEFAULT_RM_SCHEDULER_ADDRESS));
		AMRMProtocol resourceManager = ((AMRMProtocol) rpc.getProxy(AMRMProtocol.class, rmAddress, conf));	
    

 

2) 向ResourceManager注册为ApplicationMaster

		RegisterApplicationMasterRequest appMasterRequest = Records.newRecord(RegisterApplicationMasterRequest.class);
		// 设定该Application的相关信息
		appMasterRequest.setApplicationAttemptId(appAttemptID);
		appMasterRequest.setHost(appMasterHostname);
		appMasterRequest.setRpcPort(appMasterRpcPort);
		appMasterRequest.setTrackingUrl(appMasterTrackingUrl);
		RegisterApplicationMasterResponse response = resourceManager.registerApplicationMaster(appMasterRequest);
 

 

3) 向ResourceManager申请Container

		// 初始化申请Container的request (包含运行Container的host、优先级、占用内存)
		ResourceRequest request = Records.newRecord(ResourceRequest.class);
		request.setHostName("*");
		request.setNumContainers(numContainers);
		Priority pri = Records.newRecord(Priority.class);
		pri.setPriority(requestPriority);
		request.setPriority(pri);
		Resource capability = Records.newRecord(Resource.class);
		capability.setMemory(containerMemory);
		request.setCapability(capability);
		List<ResourceRequest> resourceReq = new ArrayList<ResourceRequest>();
		resourceReq.add(request);
		// 向ResourceManager发送报告 (包含第几次申请、需要申请Container的request、保存需要释放Container的List、已分配Container与总共Container的比例)
		AllocateRequest req = Records.newRecord(AllocateRequest.class);
		CopyOnWriteArrayList<ContainerId> releasedContainers = new CopyOnWriteArrayList<ContainerId
		AtomicInteger rmRequestID = new AtomicInteger();
		req.setResponseId(rmRequestID.incrementAndGet());
		req.setApplicationAttemptId(appAttemptID);
		req.addAllAsks(resourceReq);
		req.addAllReleases(releasedContainers);
		req.setProgress((float)numCompletedContainers.get()/numTotalContainers);
		AllocateResponse resp = resourceManager.allocate(req);
		AMResponse amResp = resp.getAMResponse();
   

 

2. 为申请到的Container分配任务

1) 获取上面申请到的Container

 

		List<Container> allocatedContainers = amResp.getAllocatedContainers();
		for (Container allocatedContainer : allocatedContainers) {
			
		}
 

2) 初始化运行Container的上下文 (

ContainerId

User:运行该Container的用户,即运行当前Application的用户

Resource:ResourceManager分配给该Container的资源

ContainerToken:Security模式下的SecurityTokens

LocalResources:该Container所运行的程序所需的资源,比如程序所在的jar包

ServiceData:

Environment:该Container所运行的程序所需的环境变量,KeyValue格式

Commands:该Container所运行程序的命令,比如运行的为java程序,即$JAVA_HOME/bin/java org.yourclass

ApplicationACLs:该Container所属的Application的访问控制列表

)

 

		ContainerLaunchContext ctx = Records.newRecord(ContainerLaunchContext.class);
		ctx.setContainerId(container.getId());
		ctx.setResource(container.getResource());
		ctx.setUser(UserGroupInformation.getCurrentUser().getShortUserName());
		
		Map<String, String> childEnv = new HashMap<String, String>();
		ctx.setEnvironment(childEnv);
		
		// 设定LocalResource
		// 将jar包上传到HDFS上
		FileSystem fs = FileSystem.get(conf);
		Path src = new Path(存放Container程序的本地路径);
		String pathSuffix = appName + "/" + appId.getId() + "/ChildProgram.jar";	    
		Path dst = new Path(fs.getHomeDirectory(), pathSuffix);
		fs.copyFromLocalFile(false, true, src, dst);
		FileStatus destStatus = fs.getFileStatus(dst);
		
		Map<String, LocalResource> localResources = new HashMap<String, LocalResource>();
		LocalResource childRsrc = Records.newRecord(LocalResource.class);
        childRsrc.setType(LocalResourceType.FILE);
        childRsrc.setVisibility(LocalResourceVisibility.APPLICATION);
	    childRsrc.setTimestamp(destStatus.getModificationTime());
		childRsrc.setSize(destStatus.getLen());	
        childRsrc.setResource(ConverterUtils.getYarnUrlFromURI(new URI(dst)));
		localResources.put("Child.jar",  amJarRsrc);
		ctx.setLocalResources(localResources);
		
		// 设定Command
		Vector<CharSequence> vargs = new Vector<CharSequence>(5); 
		vargs.add(JavaCommand);
		vargs.add(JavaArgs);
		vargs.add("1>" + ApplicationConstants.LOG_DIR_EXPANSION_VAR + "/stdout");
		vargs.add("2>" + ApplicationConstants.LOG_DIR_EXPANSION_VAR + "/stderr");
		StringBuilder command = new StringBuilder();
		for (CharSequence str : vargs) {
			command.append(str).append(" ");
		}
		List<String> commands = new ArrayList<String>();
		commands.add(command.toString());
		ctx.setCommands(commands);
		
		StartContainerRequest startReq = Records.newRecord(StartContainerRequest.class);
		startReq.setContainerLaunchContext(ctx);
 

3) 连接该Container属于的ContainerManager

 

		String cmIpPortStr = container.getNodeId().getHost() + ":"
			+ container.getNodeId().getPort();
        InetSocketAddress cmAddress = NetUtils.createSocketAddr(cmIpPortStr);
		ContainerManager cm = ((ContainerManager) rpc.getProxy(ContainerManager.class, cmAddress, conf));
 

4) 通过ContainerManager启动Container

 

		cm.startContainer(startReq);
 

3. 轮询获取Container的状态

1) 向ContainerManager获取Container的状态

 

		GetContainerStatusRequest statusReq = Records.newRecord(GetContainerStatusRequest.class);
		statusReq.setContainerId(container.getId());
        GetContainerStatusResponse statusResp = cm.getContainerStatus(statusReq);
		ContainerStatus containerStatus = statusResp.getStatus();
 

4. 更新Application状态

1) 在所有Container运行成功/失败后通知ResourceManager该Application运行成功/失败

 

		FinishApplicationMasterRequest finishReq = Records.newRecord(FinishApplicationMasterRequest.class);
		finishReq.setAppAttemptId(appAttemptID);
		finishReq.setFinishApplicationStatus(FinalApplicationStatus.SUCCEEDED);
		// finishReq.setFinishApplicationStatus(FinalApplicationStatus.FAILED);
		finishReq.setDiagnostics(diagnostics);
		resourceManager.finishApplicationMaster(finishReq);
<think> 我们正在讨论一个包含1000个map任务的YARN作业申请Container的完整过程。根据YARN的工作机制,这个过程涉及多个步骤,从作业提交到Application Master申请资源,再到NodeManager启动容器执行任务。下面我将详细描述整个过程。 ### 1. **作业提交** - 用户提交一个MapReduce作业(假设有1000个map任务)到ResourceManager (RM)。 - RM收到作业后,首先会为该作业分配一个唯一的Application ID,然后将其加入调度队列中等待资源分配。 ### 2. **启动Application Master (AM)** - 当调度器(如Capacity Scheduler或Fair Scheduler)根据队列优先级、资源可用性等条件决定为该作业分配资源时,RM会为AM申请第一个Container(通常称为AM Container)。 - 这个Container的资源请求是特定的,通常需要指定一定的内存和CPU。 - 一旦分配成功,RM会与对应的NodeManager (NM)通信,启动Container并运行AM进程。 ### 3. **AM注册和资源请求** - AM启动后,会向RM注册自己,并开始与RM通信(通过AMRMClientAsync.CallbackHandler)。 - AM需要为1000个map任务申请资源。AM会构造1000个Container请求(每个请求对应一个map任务),并将其发送给RM。这些请求包括: - 资源需求(如内存、CPU) - 数据本地性信息(优先选择存储有输入数据块的节点) - 优先级(通常map任务优先级高于reduce任务) - 请求示例代码(在AM中): ```java for (int i = 0; i < 1000; i++) { // 构造资源请求,指定资源大小(如1024MB内存,1个vCore) Resource capability = Resource.newInstance(1024, 1); // 数据本地性:指定该任务希望运行的节点(基于输入数据位置) String[] hosts = {"node1", "node2"}; // 假设数据块在node1和node2 // 创建Container请求,优先级设为默认(或根据任务类型设置) ContainerRequest request = new ContainerRequest(capability, hosts, null, Priority.newInstance(0)); amClient.addContainerRequest(request); } ``` ### 4. **资源调度** - RM的调度器(如CapacityScheduler)根据以下因素为AM分配Container: - **队列资源配额**:确保队列的资源使用未超限。 - **数据本地性**:优先将任务调度到存储有输入数据块的节点(Node Local),其次是同一机架(Rack Local),最后是任意节点(Off Switch)。 - **优先级**:如果设置了任务优先级,则高优先级任务优先分配。 - 调度器会周期性地(如每秒钟)检查资源分配,当有可用资源(如某个节点上有空闲资源)时,会尝试分配Container给AM。 ### 5. **Container分配与启动** - 一旦调度器决定分配Container,它会通过心跳响应告知AM(在AMRMClientAsync.CallbackHandler的onContainersAllocated方法中)。 - AM收到分配的Container列表后,会为每个Container准备启动上下文(ContainerLaunchContext),包括: - 启动命令(如运行map任务的Java命令) - 环境变量 - 必要的资源(如JAR文件、配置文件等,通常通过HDFS分发) - 然后,AM通过NMClient启动Container。AM与对应的NodeManager通信,发送启动Container的请求: ```java // 在AM中处理分配到的Container public void onContainersAllocated(List<Container> containers) { for (Container container : containers) { // 创建ContainerLaunchContext,包含启动命令 ContainerLaunchContext ctx = Records.newRecord(ContainerLaunchContext.class); ctx.setCommands(Collections.singletonList("java -cp ... org.apache.hadoop.mapred.YarnChild ...")); // 设置资源(如作业JAR、配置文件等) ctx.setLocalResources(...); // 启动Container nmClientAsync.startContainerAsync(container, ctx); } } ``` ### 6. **任务执行** - NodeManager收到启动命令后,会在分配的Container中执行启动命令,启动一个YarnChild进程来运行map任务。 - YarnChild进程负责: - 从HDFS下载任务所需的资源(如JAR文件、配置文件等) - 执行map任务(调用用户编写的Mapper类的map方法) - 将任务状态和进度报告给AM ### 7. **进度监控与容错** - AM通过心跳监控所有Container的状态。如果某个Container失败(如任务执行超时或NodeManager故障),AM会重新申请Container(重新发送该任务的Container请求)。 - 当map任务完成后,AM会释放其占用的Container(但不会立即释放,因为输出结果可能被后续任务使用)。然后,AM会开始申请reduce任务所需的Container(如果有的话)。 ### 8. **作业完成** - 当所有1000个map任务完成后,AM会开始申请reduce任务的Container(如果有reduce任务)。 - 当整个作业完成后,AM会向RM注销自己并退出。RM会清理作业相关的资源。 ### 关键点总结 - **资源申请是动态的**:AM不是一次性申请1000个Container,而是分批申请(根据集群资源情况,可能一次分配多个Container)。 - **数据本地性**:为了减少网络传输,AM会尽可能将任务调度到数据所在的节点。 - **资源限制**:集群资源有限时,AM需要等待资源释放才能获得新的Container,这可能导致任务执行时间延长。 ### 相关问题 1. YARN调度器如何处理资源碎片问题? 2. 如何配置一个YARN作业的map任务优先级? 3. 在YARN中,如何监控Container的资源使用情况(如内存、CPU)? 4. 如果某个NodeManager频繁失败,YARN如何避免将任务调度到该节点? 5. YARN的公平调度器(Fair Scheduler)和容量调度器(Capacity Scheduler)在分配Container时有何不同? </think>### YARN作业中1000个Map任务申请Container的完整过程 #### 1. **作业提交阶段** - **用户提交作业**:用户通过`hadoop jar`命令提交包含1000个Map任务的作业 - **ResourceManager响应**: - 创建`ApplicationSubmissionContext`对象 - 分配唯一Application ID(如`application_1620000000001_0001`) - 将作业加入调度队列(按队列优先级排序)[^1] - **资源初始化**:RM检查队列资源配额(如生产队列分配集群70%资源) #### 2. **Application Master启动** ```mermaid sequenceDiagram ResourceManager->>NodeManager: 分配AM Container NodeManager->>ApplicationMaster: 启动AM进程 ApplicationMaster->>ResourceManager: 注册AM(心跳机制) ``` - RM为AM分配首个Container(特殊Container) - AM启动后向RM注册(通过`AMRMClientAsync`) - AM加载作业分片信息(1000个输入分片对应1000个Map任务) #### 3. **Map任务资源请求** - **资源需求计算**: - 每个Map任务资源需求:$( \text{memory} = 1024\text{MB}, \text{vCores} = 1 )$(默认配置) - 总资源需求:$1000 \times (1024\text{MB} + 1\text{vCore}) = 1\text{GB} + 1000\text{vCores}$ - **批量请求生成**: ```java // AM生成1000个Container请求 for (int i=0; i<1000; i++) { Resource capability = Resource.newInstance(1024, 1); String[] dataLocations = getInputSplitLocations(i); // 数据本地性信息 ContainerRequest request = new ContainerRequest(capability, dataLocations, null, Priority.newInstance(5)); amRMClient.addContainerRequest(request); } ``` - **数据本地性优化**:每个请求包含输入分片所在的节点/机架信息[^1] #### 4. **调度器分配逻辑** ResourceManager处理请求的优先级排序: ```mermaid graph LR A[队列优先级] -->|生产队列>测试队列| B[应用优先级] B -->|紧急作业>普通作业| C[Container请求优先级] C -->|Map任务>Reduce任务| D[数据本地性] D -->|NODE_LOCAL>RACK_LOCAL| E[实际分配] ``` 1. 检查队列资源配额(防止队列超量) 2. 同队列内按应用优先级排序(如VIP用户作业优先) 3. 按Container请求优先级(默认Map任务优先级=5) 4. 数据本地性优化(NODE_LOCAL > RACK_LOCAL > ANY) #### 5. **渐进式分配过程** - **分批分配**:RM不会一次性分配1000个Container - **分配上限**: - 单节点:$ \text{最大Container数} = \frac{\text{节点总内存}}{\text{单个Container内存}} $(如128GB节点最多分配128个1024MB Container) - 集群:受`yarn.scheduler.maximum-allocation-mb`限制 - **分配节奏**: 1. 初始分配:首轮分配50-100个Container(取决于集群空闲资源) 2. 心跳增量分配:AM每次心跳(默认1秒)获取新分配的Container 3. 释放再分配:完成任务的Container释放资源后重新分配 #### 6. **Container启动流程 ```mermaid sequenceDiagram ApplicationMaster->>NodeManager: 启动Container(LaunchContext) NodeManager->>Container: 分配资源(cgroups隔离) Container->>YarnChild: 执行MapTask YarnChild->>ApplicationMaster: 进度报告(心跳) ``` - AM向NM发送`ContainerLaunchContext`包含: - JAR文件位置(HDFS路径) - 环境变量配置 - 启动命令:`java org.apache.hadoop.mapred.YarnChild ...` - NM使用cgroups进行资源隔离(内存/CPU限制)[^1] - 每个Container运行一个Map任务 #### 7. **容错与弹性扩展** - **任务失败处理**: - NM监控Container:超时则标记失败 - AM重新请求失败任务的Container - **动态调整**: ```java // AM可动态调整请求 if(someTasksSlow) { amRMClient.updatePriority(priority, request); // 提升优先级 } ``` - **资源回收**:Map任务完成后立即释放Container #### 8. **资源竞争场景处理** 当集群资源紧张时: 1. **层级降级**:先满足高优先级队列,再逐级分配 2. **本地性妥协**:优先分配RACK_LOCAL而非等待NODE_LOCAL 3. **饥饿预防**:低优先级作业等待超时后临时提升优先级 ### 监控指标示例 | **阶段** | **关键指标** | **典型值** | |---------|-------------|-----------| | 请求阶段 | `PendingContainers` | 1000 → 递减 | | 分配阶段 | `AllocatedContainers/sec` | 50-200个/秒 | | 运行阶段 | `AvgContainerAllocDelay` | 200-500ms | ### 相关问题 1. 如何优化大规模Map作业的Container分配速度? 2. YARN如何防止单个作业占用全部集群资源? 3. 数据本地性失效时调度器如何降级处理? 4. Container分配过程中可能产生哪些性能瓶颈? 5. 如何通过YARN REST API监控Container分配状态
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值