JStorm源码分析-5.Supervisor

Supervisor用于执行拓扑中指定的组件任务,是实际响应nimubus分配,控制worker运行的程序。

1. 启动Supervisor

启动Supervisor时,只需要输入

jstorm supervisor

jstorm.py脚本会将supervisor子命令解析为如下命令:

java -server 
-Djstorm.home=/Users/shishengjie/software/jstorm-0.9.1 
-Dstorm.options= 
-Djava.library.path=/usr/local/lib:/opt/local/lib:/usr/lib  
-Xms256m -Xmx256m -XX:+UseConcMarkSweepGC 
-XX:+UseCMSInitiatingOccupancyOnly 
-XX:CMSInitiatingOccupancyFraction=70  
-Dlogfile.name=supervisor.log 
-Dlog4j.configuration=File:/Users/shishengjie/software/jstorm-0.9.1/conf/jstorm.log4j.properties  
-cp /Users/3.2.7.Final.jar:/Users/shishengjie/software/jstorm-0.9.1/conf 
com.alibaba.jstorm.daemon.supervisor.Supervisor 

2. Supervisor的启动

2.1 主要流程

  1. 读取配置,依次从defaults.yaml、classpath下的storm.yaml和命令行的storm.options中读取storm的配置,并确保配置的模式为distributed。
  2. 清除在本地路径/{storm-local-dir}/supervisor/tmp中的内容,从nimbus下载topology的jar包、conf和topology序列化文件会临时放在这个文件夹内,下载完成后转移到/{storm-local-dir}/supervisor/stormdist/{topologyId}中,具体程序在downloadDistributeStormCode中。启动时先清理一遍文件夹。
  3. 创建LocalState,这是一个kv数据库,内容都保存在磁盘上。每次使用put的时候,会创建一个新的版本号,并将map中的所有内容都写入新文件。get是从最新的版本号中读取map再获取key对应的value。
  4. 从LocalState中获取supervisorId,如果不存在,使用uuid生成一个新的,保存到LocalState。
  5. 创建心跳线程Heartbeat。Heartbeat实现了RunnableCallback接口,可以看做一个任务,将其放入AsyncLoopThread中,每过supervisor.heartbeat.frequency.secs默认60秒,调用一次update方法。update的目的是向nimubus汇报supervisor的状态,通过修改心跳时间和运行时间,将SupervisorInfo序列化后的内容保存到zk的节点/jstorm/supervisors/{supervisorid}中,这是一个ephemeral节点,supervisor退出时会清除这个节点。
  6. EventManager是一种事件机制,内部包含一个执行线程runningThread,会从queue中不断获取event,然后执行,通过add方法将event加入到queue中。SyncProcessEvent是一个实际要执行的Event,主要作用是kill掉异常的worker,启动新worker。SyncSupervisorEvent是Supervisor的主要工作,会从zk上读取assignment,下载拓扑代码,然后找出需要自己处理的任务,让SyncProcessEvent去处理Worker。EventManagerPusher的作用是每隔supervisor.monitor.frequency.secs默认10秒或zk上的assignment发生变化时(由nimbus修改),执行一次syncSupervisorEvent。
  7. 最后,将这些线程都封装到SupervisorManger中,便于停止supervisor或其中的所有worker进程。

2.2 SyncSupervisorEvent

SyncSupervisorEvent的主要功能是读取zk上的assignment信息,找出自己需要执行的任务,下载topology代码,然后执行SyncProcessEvent去控制worker,执行流程如下:

  1. 从zk的节点/jstorm/assignment在读取待分配的任务列表assignments,并且为节点添加callback,当节点发生变化时,执行EventManagerZkPusher的run方法,会将当前的SyncSupervisorEvent加入到SyncSupervisorEvent的事件管理器syncSupEventManager,等待下次执行,这样做到目的是加入队列,不需要考虑同步问题。
  2. 从本地路径{strom.local.dir}/supervisor/stormdist/下读取已经下载代码的拓扑列表downloadedTopologyIds。
  3. 有了zk中的任务列表assignments,可以找出当前supervisor的各个worker对应的任务:遍历assignments,找到分配给当前节点的Assignment,由于Assignment内部保存了task列表和每个task包括port在内的资源,可以计算出每个port对应的task列表。计算的目的是因为每个port对应着一个worker进程,需要明确每个worker包含的task。最后,还需要校验一个port(即worker)只能运行一个Assignment(对应着topology)。最后将本地LocalAssignment写入localState中保存(启动worker时会读取并使用)。
  4. zk中的Assignment中记录了每个master上topology在的代码路径,downloadedTopologyIds的不需要下载,其他的需要下载到指定目录{strom.local.dir}/supervisor/stormdist/topologyId中。
  5. 第4步的时候,可以计算出当前的supervisor需要处理的topology,删除路径{strom.local.dir}/supervisor/stormdist/下面,不需要处理的topology文件夹
  6. 将syncProcesses加入到processEventManager中,也就是去执行一遍SyncProcessEvent。

2.3 SyncProcessEvent

SyncProcessEvent的主要功能是根据worker的心跳判断其状态,已经失效的需要kill掉,已经停止的需要重启,根据任务分配启动新的worker。主要流程如下:

  1. 从localState中读取LocalAssignment,SyncSupervisorEvent会从zk中的assignment中过滤出当前supervisor需要处理的task。
  2. 从本地读取worker和worker心跳判断worker的状态,状态记录在StateHeartbeat中,从{storm.local.dir}/workers下读取文件列表workerIds,{storm.local.dir}/workers/${woker-id}/heartbeats里面是心跳信息WorkerHeartbeat。有了worker和心跳,就可以计算worker的状态了。如果WorkerHeartbeat为null,说明没有启动State.notStarted;如果心跳状态与分配的任务不匹配,设置为State.disallowed;如果心跳时间超时,设置为State.timedOut;其余情况,状态为正常State.valid
  3. 计算处理worker的状态之后,就需要根据状态对Worker进程进程处理了,首先会将非State.valid的Worker全部kill掉,心跳中记录了pid,使用kill命令即可。
  4. 遍历LocalAssignment中的Worker,将没有启动的Worker启动起来,使用uuid生成workerId,在本地创建文件夹{storm.local.dir}/workers/{newworkid}/pids,在launchWorker方法中通过拼接java命令的方式启动Worker进程。Worker的启动类在com.alibaba.jstorm.daemon.worker.Worker中,向worker传递的参数有: topologyId 、supervisorId、port、workerId、workerClassPath:jar路径。

你可能感兴趣的:(JStorm源码分析-5.Supervisor)