一、Storm中各个角色的作用
• Nimbus
– 集群管理
– 调度topology
• Supervisor
– 启停worker
• Worker
– 一个JVM进程资源分配的单位
– 启动executor
• Executor
– 实际干活的线程
• Zookeeper
– 存储状态信息,调度信息,心跳
二、Storm各个角色介绍
• nimbus:
相当于master,storm是master/slave的架构
– 它负责两个事情,第一个负责管理集群,这些slave都向zookeeper写信息,然后nimbus通过zk获取这些slave节点的信息,这样nimbus就知道集群里面有多少个节点,节点处于什么样的状态,都运行着什么样的任务
– 第二就是调度topology,当一个topology通过接口提交到集群上面之后呢,负责把topology里面的worker分配到supervisor上面去执行
• supervisor:
每台机器会起一个supervisor进程,supervisor就是slave
– supervisor其实就干一件事情,就是启停worker,当nimbus调用之后呢,supervisor去把worker启动起来,这样的话worker就可以开始干活了
• worker:
实际干活的呢是worker,每个机器上面supervisor会启动很多个worker,每个机器会配置一定的worker,比如4个worker
一个worker呢,其实就是一个jvm,JVM做什么事情呢,其实就干两件事情,第一件事情就是启动executor,第二件事情呢它负责worker与worker之间,比如这里还有worker,这里还有worker,负责同一个topolopy中worker和worker之间的传输,task这些数据传输是通过worker来做的,topology和topology之间呢是没有任何相关性的。
• executor:
真正干活的是worker里面的executor
– worker启动executor,那executor做什么事情呢,executor就是执我们的Spout、Bolt里面的nextTuple()、execute()这些回调函数的,是真正运行的线程
三、Storm到底给ZK写了些什么
• /storm/supervisors/supervisor-id
– 首先最基本的每个supervisor的信息它要写进去吧,因为它要知道整个集群的状态,还要写一些topology的信息,name,id,状态的信息。
• /storm/storms/storm-id
– 实际上这个地方就应该叫topology-id,只不过在storm最早开发的时候呢,它每一个topology不叫topology,叫一个storm,但是对外文档都叫做了topology,内部的调用什么没改,还是沿用之前的,所以这里看到的还是storm-id,这里storms其实就是storm,storm-id是topology-id,所以大家以后要是深入到storm代码里面去的时候,有些storm-id的地方其实就是topology-id的意思,并不是两个不一样的东西,就是同一个东西。
• /storm/assignments/storm-id
– 当topolgy提交到集群里面去之后呢,nimbus会对它进行调度,当然nimbus会把调度信息写入到zookeeper里面去,包括topology分配了多少个worker啊,每个worker有个id号啊,worker分配到哪些机器啊,等等这些信息。
• 4./storm/workerbeats/storm-id/node-port
– 那这些worker启动来之后呢,它也需要把这些心跳信息写到集群里面去,方便后面去监控,每个worker它会把信息写到对应的文件上面去,命名是node加port的形式,node就是supervisor的ID,port就是这个worker对应的port,因为不同的supervisor上对应的port一定也是唯一的,因为port是他们数据交换的端口,所以说这个地方node加port是可以保证唯一性的。
• 5./storm/errors/storm-id/component-id
– 最后一部分是错误信息,就是Spout和Bolt产生的错误会写到zookeeper上面去,方便分析问题,这个error信息写到zookeeper里面的时候,这里说明一下,它有个策略,就是每个component就是Spout或者Bolt,它最多写最近的20条,这样就防止往zk里面存储过多的数据,导致压力太大,因为zk不适合存储过多的数据,那你要是想看历史的数据呢,那再storm的日志里面有,再去Storm的日志里面翻。
三、Storm各个角色详细介绍
• nimbus:
1.调用topology,将调度信息写入ZK
2.检查supervisor/worker心跳,处理异常
它要检查supervisor/worker是否正常,其实这个很简单就是去zookeeper里面去检查supervisor/worker的心跳里面的时间是不是OK,如果是超时时间范围内的就不管它,如果是超时时间外的就需要做一些超时处理,比如说重新调度等等
3.处理topology submit、kill、rebalance等请求
topology提交的话需要经过nimbus,kill的话需要经过nimbus,rebalance的话也是需要经过nimbus,rebalance的话就是能使得topology分布的更加均匀,比如说给topology增加worker,原来4个worker,我现在给它增加到20个worker,rebalance会把topology重新调度一次
4.提供查询cluster/topology状态的thrift接口
一个集群运行的时候是什么状态的,给暴露出来也是非常重要,提供查询比如cluster有多少机器,topology的状态,topology有多少个worker,有多少个executor,worker和executor运行的状态分别是什么样子的,然后有些计数器的信息等等都在topology的信息里面,这些查询都是通过提供thrift接口,通过thrift接口各种语言都可以来用,比如说stormUI就是通过thrift来调用nimbus的查询接口,这些东西展现在Web上,是非常有用的,因为我们需要看应用是否是稳定的,然后我们来通过这些接口呢做一些监控,来监控cluster是否正常,topology是否正常等等
5.提供文件(如topology程序jar包)上传下载服务
nimbus还提供了一个上传下载服务,这个也是通过thrift接口来实现的,因为咱们写程序比如JAVA写好了肯定是需要打成JAR包的上传到集群里面,然后各个supervisor再从master上面把JAR给它下载下来运行程序,这里会想storm怎么会通过主节点nimbus来做这件事情呢?其实也不奇怪,storm和hadoop不一样,mapreduce它是自带存储系统hdfs的,它的jar包放在hdfs上,各个节点是从hdfs上面读的,storm它是一个纯的计算系统,它没有一个存储系统和它结合,所以storm它自己实现了一个上传和下载的服务,当然这个服务很简单,所以当程序规模比较大的时候呢,各个节点都去nimbus节点下载JAR包,那么nimbus的带宽很快就会成为瓶颈
• supervisor:
1.启动worker
它和hadoop不一样的是,它不会像hadoop一样会去向master要我要去启动什么样的worker,而是它定期会去zookeeper上查阅master写的调度信息,然后它看看这个调度信息里面有没有我这个机器要做的事情啊,要做的任务,发现有我这台机器要去做的事情,那么它就去启动,这个启动呢主要是做两件事情,第一呢就是下载topology的JAR包,而且把这个JAR包解压出来,在解压出来后创建相应的目录,这样呢后面就可以用这个JAR包了。
2.监控worker
在解压出来后创建相应的目录后,就开始启动worker的JVM进程,后面这个worker就可以开始干活了,那么在启动了这个worker之后呢,后面很自然的就可以对这个worker进行监控,它怎么去监控这些worker呢,这些Worker启动之后呢,它会在本地写一些监控信息,这个时候supervisor就去看这些信息,当长的时很间没有这个worker的信息了的话,就认为这个worker有问题了,然后就处理异常,把这个worker重新启动等等。
3.把自己的情况汇报出去
定期在ZK上面更新supervisor的心跳,定期默认是5秒钟一次,5秒钟supervisor就把自己的默认信息写到zookeeper上去,然后这个主要是为了告诉nimbus说表明supervisor没有问题,否则nimbus会认为没有心跳,supervisor挂了nimbus就是处理。
解压出来,在解压出来后创建相应的目录,这样呢后面就可以用这个JAR。
• worker:
1.启动executor
没什么可说的,值得一提的就是,一个worker可能会启动多个executor,当然这里多个executor必须是属于一个topology的,比如说这个topology有10个worker,30个executor,那每个Worker里面就会起3个Executor。
2.数据传输
第二个就是网络传输,当然模型也非常简单,就是一个topology的worker之间呢建立网络连接,那不同的topology之间肯定是没有网络连接的,这个是毫无疑问的,就只有同一个topology内部呢才会有交换数据的需求,第二个就是在网络连接建好之后呢,在每个Worker内呢有一个收线程,和有一个发线程,负责进行实际数据Tuple收发的工作,其实对应呢每个线程还有一个buffer,一个收buffer,还有一个发送的buffer,来做数据的缓冲
3.最后一个定期会在本地系统和ZK来写心跳信息
本地它会写一个叫WorkerHeartbeat的数据结构,这个结构非常简单,就是会记录现在写心跳的时间,然后storm-id,就是topology-id,然后worker里面对应有哪些个executors,就是记录executor的ID,然后worker对应的是哪个端口,这个心跳信息有什么用呢?主要本地信息worker说我还活着,然后supervisor会去拿来读,哦看到说你还有心跳,还活着哈哈,相对然后worker往ZK里面写的信息,相对要复杂一些,是以executor为单位的,每个executor都有一些状态信息,这些基本信息包括一些时间啊,ID啊,还有这个executor处理了多少个tuple,然后它处理的延迟平均是多少,它ack了多少tuple,它emit多少tuple,等等这些统计信息还有
• executor:
1.去创建实际的Spout/Bolt的对象
没什么可说的,值得一提的就是,一个worker可能会启动多个executor,当然这里多个executor必须是属于一个topology的,比如说这个topology有10个worker,30个executor,那每个Worker里面就会起3个Executor。
2.执行线程
它会去执行nextTuple()/execute()两个回调函数,这两个回调函数分别是Spout和Bolt的,这两个方法稍有区别,Spout的nextTuple()执行是死循环的,当然里面有sleep的,就是当上一次调用nextTuple()的时候发现没有数据,那就睡几毫秒,还有一种情况呢就是,当我SpoutNextPending已经到达的时候,当我发现数据还有大量的没有处理完的时候,那我要做流控,这个时候那我就不要再反复执行nextTuple()了,睡一会儿再执行
Bolt的execute()方法呢不是说我哗哗哗一直的去调用,而是说当我收到一个新来的Tuple的时候呢,我才会去调用这个execute()函数,Tuple作为参数传给bolt对象,execute()方法就会去处理这个tuple
3.传输线程
传输线程负责做一件什么事情呢,它就是负责将新产生的tuple放到worker的传输队列里面去,因为worker它是最后做数据传输的地方嘛,executor它本身不会做数据传输,它只会处理完数据把数据放到内存里面
其实这里面呢,刚开始executor会从内存里面receive-queue里面取数据,然后进行处理,处理完了呢,再通过emit将tuple放到transfer-queue里面去