上面的图来自http://www.cnblogs.com/xia520pi/p/4825326.html,我自己再原图上加了一个Client
通过阅读源代码可以发现,client是独立于Nimbus的一段代码,并不是Nimbus上附带的,只是通常情况下,我们在Nimbus上提交Topology而已
整体的过程是这样的:
Client上提交我们的Topology,其实就是调用submit函数而已,提交一个StormTopology的类,没有什么神奇的地方。将topology提交给Nimbus,而我们的jar包并不是在client上提交传输给nimbus的,而是提前部署到nimbus上,因为在Client上submit函数提供的是一个jarLocation,这个location是nimbus上jar包的location。这里的Client与nimbus交互式利用的Thrift的Server/client模型实现的RPC,利用远程过程调用来实现Topology的提交。可以在jstor的thrift文件中发现这些定义的接口,其中还包含一些killTopology,rebalance等客户端操作。Nimbus是一个调度工作的节点,上面记录有一些什么机器给我工作,在内部包含一个结构Node的列表,里面记录了Worker的IP和Port,知道有一些什么worker后,当有一个topology被提交,才可以分配这些worker来计算。nimbus分配的过程中会得到一个Assign的对象,这个对象中就记录了worker的分配情况。在分配了任务后,niumbus还负责任务的解析,nimbus把topology解析后,知道topolopy是干什么的,得到一些task(还不对应一个线程),nimbus把这些task写到zookeeperzookeeper是干什么的呢?zookeeper也是一个集群,其用于记录信息,所有的信息都不记录在nimbus或者worker中,而是记录在zookeepr中,这样机器死掉后,可以从zookeeper中恢复出来,而zookeepr本身是一个集群,所以zookeeper是很健壮的。这体现了jstorm的无状态性,因为状态记录在zookeeper中。supervisor时常地查询zookeepr中记录的信息,看看是任务分配,如果nimbus分配了任务,supervisor就会启动机器上的一个进程来作为worker,从而运行这些task(thread)。supervisor就是一个守护进程,用于监听zookeepr上分配的任务,以及汇报心跳,nimbus同样也是一个守护进程,而worker才是实打实的工作进程,而且worker平时是没有被启动的,而是由supervisor来负责启动这个图是从官网上截图下来的 水龙头:数据源(spout) 闪电:数据处理单元(bolt) 这一部分我其实在我的调研系列(一) 中已经有讲到,大家可以去看看里面的基础概念部分。
我在这里大概的初略回顾一下吧:
Spout中不断调用nextTuple函数,产生一个一个的Tuple,每一个Tuple就是我们需要处理的数据,可能一个Tuple就是一条日志或者一个访问请求,这个Spout有一个名字Spout1。bolt会订阅spout1产生的每一个的tuple,也许就是响应一个请求,或者简单的字符串处理提取日志信息存储到数据库中。当然,bolt可以同时订阅spout1,也可以再订阅一个spout2,不会冲突的,我们可以在bolt的逻辑中判断是当前处理的tuple的来自哪一个spout。不光如此,bolt可能并没有处理完这一个tuple,比如他第一步只是简单的将日志进行了字段切分,再发送每一个字段给下游的bolt2(这个过程会产生一系列新的tuple)。下游的bolt只要订阅了这个上有的bolt,就可以接收上游bolt发送的tuple了。每一个tuple代表的是一个数据,每一个tuple都会有一个ID,这样的一个接一个的tuple就形成Stream(流)每一个Tuple都有一个id,在我们运行的topology中会有一个task叫_ack,专门负责确认每一个tuple是否被处理完成,当spout发送一个tuple后,spout同时也会把这个tuple发送给_ack,如果tuple处理失败,用户显式的调用fail,那么_ack就会调用spout的fail函数,fail函数可以由用户定义干什么,比如重新发送这一个tuple,或者是忽略掉。当一个tuple被传递了很多次后,需要知道这个tuple最初的那个spout,那么需要利用jstorm提供的anchoring的机制,anchoring机制就是把新发送的tuple与其父tuple绑定在一起,当新的tuple失败了,就可以根据anchoring来确定其最初的父tuple了。