当我们发布了一个appMaster时,通常是appMaster来停止某个container,这个过程是调用NMClientImpl的stopContainer方法实现的。
客户端请求实现:
核心方法如下
首先hadoop内部是使用基于GOOGLE的protocol buffers实现客户端与服务端的RPC通信的。停止contianer的过程也是如此,客户端会通过代理,向NodeManager发送rpc请求,然后服务端返回结果。下面我们来看服务端接受请求后的处理。
服务端接受请求实现:
通过断点,我们找到了服务端接受rpc消息的地方来做stopContainers处理,之后,我们一步一步跟断点。
启动dispatcher.getEventHandler().handle()是这个方法的核心。我们可以看到,nodeManager封装了一个ContainerKill事件,然后由对应的handler来进行处理。
说到这里,我们可以顺便看一下所有的container事件实现。
我们知道yarn使用container来表示资源,由上图可知,yarn对于container的申请,初始化,销毁,成功完成退出都是基于这种事件模型由相关的handler处理的。
我们接着跟进断点:
我们发现,最后是将这个事件放到了一个事件队列中,这是一个异步处理,这里没有真正的杀死container,然后我们来看哪个任务会从中取出这个事件,来真正的杀死container。
执行found usage:
找到相关代码,标记断点:
我们可以看到这里有一个循环取事件并处理的方法,其中的dispatch(event)是这个方法的核心。
这个方法是这里启动的。
我们跟进断点,查看dispatch内的内容
这里根据事件类型,在一个类似于事件调度注册中心中,找到了对应的handler,然后进行handle。继续跟进断点,查看handle内的内容
最后我们看到,这里是更改了contianer的状态,hadoop-yarn的状态机模式很复杂,这里不过多说明,不过此时虽然更改了contianer的状态,还是没有真正执行container清理工作。
我们继续跟进断点。
这里,从事件队列中由取到了一个事件,这个事件类型像是执行清理工作。
我们可以看到这里取得的handler为ContainersLauncher,之前修改状态的为ContainerManagerImpl.他们都是EventHandler的实现类。我们继续跟进断点:
进入handle方法,最后执行laucher.cleanupContainer()
这个方法过长,不过上面的截图已经找到了清除container的核心,ContainerLauch的cleanupConatiner()方法,其中核心步骤为这一段:
其中sleepDelayBeforeSigKill默认是250,这个参数可以在yarn-site.xml中配置,
我们跟进断点,来看下exec.signalContainer(user, processId, signal)
进入killContainer
不断的断点后,进入了如下方法:
最后我们得出结论,nodeManager的事件处理模型,收到containerKill事件后,会对相关container进行状态修改操作,状态修改成功后,会发送一个cleanContainer事件,之后便是真正的对contianer进行清理操作,清理的方式说到底就是java本地运行shell通过ProcessBuilder。nodeManager是先找到container对应的pid,然后执行kill -15 pid过了sleepDelayBeforeSigKill毫秒后,再执行kill -9 pid,现在我们清楚container是如何被停止的了。
最后说一下,kill -15 pid会尝试杀掉此进程跟与其相关的所有子进程,而kill-9只会杀掉此进程,不会杀掉其子进程,所以,这里先执行kill -15是合理的。如果直接执行kill -9会导致子进程不会被回收,最后成为僵尸进程,操作资源无法回收。不过kill -15不能完全保证子进程一定被杀掉。所以,稳妥起见,开发者最好还是有自己的监控策略,来保证相关资源被回收。