YARN——任务运行异常处理

2023-02-28 14:33:41 浏览数 (2)

【概述】


上篇文章讲述了yarn任务提交运行的流程,本文来聊聊整个运行过程中的一些异常情况,以及yarn是如何处理的。

【container进程异常】


在NM内部,对于container进程的启动,都是在一个独立线程中,创建对应的子进程。当container进程结束时(不管是成功结束,还是异常退出),对应的线程均能感知其状态并获取其退出码。

但NM并没有做任何重试处理(不管container进程是AM还是一般任务),而是通过心跳汇报给RM,包括container的状态和结束码(注意:在NM中只有container的状态转换到DONE,上报RM的状态才是complete,其他状态上报时都算是running状态)。

RM内部处理NM的心跳请求,最终会通知到调度器,对于complete状态的container,事件通知对应的container,container进一步通知attempt。

在attempt中会进行逻辑判断,如果该container不是AM,则将container加入到结束列表中,当对应的AM心跳上报时,将结束的container列表作为心跳响应的一部分告知AM。由AM决定是否需要重新运行对应的任务container。

如果该container是AM,首先反向通知调度器attempt失败,调度器根据参数配置决定是否需要将该AM申请分配的所有container清理结束。然后告知APP,APP判断是否达到任务失败重试的最大次数,如未达到上限,则创建一个新的Attempt,重新进行任务提交运行的后续逻辑处理。

这里有几点需要注意:

  • AM正常结束前会通过rpc接口向RM发送注销事件,RM内部处理过程中会通知到对应的attempt,attempt进行相应的处理和状态机切换,然后才是进程的退出,由NM通过心跳告知,这时候流程和上面讲到的差不多,但是状态机的变化和处理稍有不同,这样可以区分应用是成功结束还是失败
  • 调度器根据参数配置决定是否需要将该AM申请分配的所有container清理结束。其意图是AM异常后,其申请运行的任务可以继续运行,这样减少不必要的重复工作。当新的AM启动后,RM会将之前的container信息告知该AM。 另外,这个是AM的参数,并不是所有的AM都支持,例如spark的driver就不支持,而flink的jobmanger则支持。

【nm进程异常】


nm启动后会向rm进行注册,随后定时向rm发送心跳,以进行保活,在心跳请求中携带了nm所在节点的资源信息,以及该节点上所有container的运行状态。

rm收到nm心跳请求后,根据需要在该节点上分配任务container,同时通过心跳响应,告知该nm哪些container需要结束和清理。

如果rm一段时间未收到nm的心跳请求,则判断该nm处于下线状态,对该nm上运行的container进行相应的处理。

因此当nm出现异常时,例如nm的进程被kill掉,需要分两种场景来讨论。

一种情况是,nm异常结束后快速被重启恢复了,rm还未感知到nm的异常,简单来说就是rm还未感知到nm心跳超时(nm重启后会重新注册,这时候rm就知道了)。

另一种就是nm异常一段时间后仍旧未恢复,rm感知到了nm心跳超时了。

  • rm未感知nm异常(心跳未超时) 该节点上的所有container均继续运行,nm重启后根据本地记录的情况进行恢复和进行必要的container重新创建。(涉及重启恢复流程,这里简单介绍,后面单独整理说明),然后向RM注册和心跳汇报continer运行情况。
  • rm感知nm异常(心跳超时) rm感知nm异常后的处理流程如下图所示:

1. NM节点心跳处理模块感知NM心跳超时,向对应的节点实例对象发送expire事件(该实例对象在节点注册时创建) 2. 节点实例对象收到expire事件后向调度器发送节点移除事件 3. 调度器遍历找到分配在该节点上的container列表,依次向这些container发送kill事件。 4. container实例对象中收到kill事件后,先向所在的Node实例对象发送清理container事件。(对于container本身而言,不区分是人为主动调用的kill,还是被动进行的结束动作) 5. container实例对象继续通知对应的attempt,container运行结束。 6. attempt收到container结束事件后,将container添加到结束列表中,然后对该container进行判断,如果container不是AM,则无后续操作。如果是AM,进入下一步骤。 7. attempt继续向app通知,attempt执行失败,由app根据参数决定是否需要进行任务的重试。 8. attempt向调度器发送移除attemp事件,调度器收到该事件后,会结束该attempt分配的所有container,即对这些container重复执行第3步的处理动作。 9. 通知AMLaunch模块清理attempt,AMLaunch对该事件的处理方式为通过RPC接口向对应的NM发送请求,要求结束AM对应的container进程。 注意: rmnodeimpl收到expire事件后,自身变为lost状态,该状态下不会处理第5步的事件。 第4步,rmnodeimpl收到清除container事件后,只是在内存中进行记录,等待nm下次心跳时,将待清除的container告知nm,由NM进行实际的结束清理动作。

【am超时】


前面也提到了,AM启动后会向RM进行注册,并定时发送心跳请求,一方面是为了保活,另一方面是通过心跳申请任务的资源,并获取container的运行情况。AM的心跳间隔是可以独立配置的。

而NM也会向RM进行注册,并定时发送心跳,保活的同时汇报该节点上container的运行情况。其心跳的超时时间也有独立的配置项。

因此,如果因为AM自身某些原因,或者网络出现故障(但NM的心跳超时时间长于AM的心跳超时时间),就会出现AM心跳超时,而对应节点上的NM还为心跳超时的情况。

对于AM的心跳超时,RM的处理逻辑和上面NM异常的大同小异。

AM的心跳超时模块感知超时后,向对应的Attempt发送expire事件消息,Attempt告知App运行失败,App根据重试次数决定是否创建新的attempt。同时attempt还会通知调度器attempt运行失败,以便调度器进行相应的清理动作和资源的释放,最后通知AMLaunch模块进行清理。

简单流程如下图所示:

【总结】


任务运行的过程中,大部分是因为进程异常或者网络异常导致的超时引起的,本文也就此进行了总结。当然,实际常见中可能出现的情况会更多,例如NM所在的磁盘故障,主备RM均异常等,但处理流程不会有太大的区别,有兴趣的可以结合源码和实际场景进行测试分析。

上面的总结如有不对,也欢迎指正交流。

0 人点赞