Storm原理

来源:互联网 发布:国家行政学院博士 知乎 编辑:程序博客网 时间:2024/06/05 04:28
Nimbus
  –集群管理
  –调度topology
Supervisor
  –启停worker
Worker
  –一个JVM进程资源分配的单位
  –启动executor
Executor
  –实际干活的线程

Zookeeper

  - 存储状态信息,调度信息,心跳信息等
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()这些回调函数的,是真正运行的线程。


先来看看zookeeper。

zookeeper是nimbus和supervisor把里面读写一些原数据信息的,具体会写什么信息呢?

存储心跳,会把心跳写上去,具体读取心跳信息的人再去zookeeper上面去读取信息;

调度信息错误信息,当task执行错误的时候,把错误的信息写到zookeeper上去,这样的话,在webUI上我们就可以读取这些信息,把情况给展示出来。
实际上,Storm用Zookeeper,使用的非常简单,它就把它当做一个高可用的KV来使用,storm使用zookeeper需求非常简单,就是说让整个集群别变成无状态的,就是任何节点挂了,受影响都不大,包括nimbus这种master进程挂掉的话,整个集群都还是能工作的,它要做到这一点就需要把状态信息元数据都存储到一个KV里面去,存储到一个系统里面去,最简单的就是一个KV就解决了,之所以选用zookeeper,是因为它是一个分布式的,高可用的小KV的一个系统,它里面一般是3个或者5个机器,当它的机器坏掉的数目不超过一半的时候,zookeeper还是可用的,所以zookeeper在这里起到了一个高可用的作用,比如说我们一般部署5台机器,在宕掉一台或者两台机器的时候,都能正常使用,都没有问题。

storm如何使用zookeeper呢?

和其它的master/slave的技术使用zookeeper还是有点不一样,比如hadoop他是一个典型的master/slave架构,但是在hadoop里面,所有的salve都会向master去汇报状态,比如mapreduce里面的tracker都会向jobtracker汇报,这样在jobtracker里面的内存会有所有tracker的状态,同时在tracker向jobtracker汇报心跳的时候,jobtracker会在回应里面,带上你要执行的task,tracker你要执行什么task这样的信息,hadoop这种方式的好处是比较直接,slave向master汇报心跳,master给slave一些指令,很简单很清晰。storm之所以不采用这种架构是因为这种需要保证更好更高的可用性,因为如果宕机会有延迟,流式计算里面对延迟接受度是比较低的,因为业务如果延迟10分钟什么的话,影响是很大的,storm就是要保证当任何挂掉的时候,业务可以稳定运行,所以它需要各个进程都是无状态的。

比如nimbus,如果各个节点都向它汇报,如果nimbus一旦挂了,那就存在这些个状态信息怎么恢复的问题,各个节点连不上的时候,怎么处理的问题,所以storm它的做法是各个节点都把状态信息往zk上写,因为zk我们认为是可靠地,这样比如说如果nimbus挂了,那再新起一个nimbus,它去zk上面去取Supervisor的信息,它就可以立刻知道supervisor处于一个什么状态,supervisor也一样,它不需要与nimbus通信,它的心跳是往zk上面写的,而它启动task的时候,也不是从nimbus上面要task,而是nimbus把Task放到zookeeper上面去,然后supervisor再去zk上面去读,然后读到task之后再去启动task,所以相互之间通过zk做了一个解耦,zk又是高可用的,非常好的,又是集群的方式,达到了一个非常高的稳定性,这个从架构上来讲的话,比hadoop的架构要更先进一些了。

再来看看storm使用zk的时候都往zk里面写了一些什么样的东西。

/bin/zkClie.sh

ls /storm

ls /storm/supervisors

get /storm/supervisors/...

1、./storm/supervisors/supervisor-id
首先最基本的每个supervisor的信息它要写进去,因为它要知道整个集群的状态,还要写一些topology的信息,name,id,状态的信息,这个地方可能有些奇怪了,这里为什么叫storm-id而不叫topology-id?


2、./storm/storms/storm-id
实际上这个地方就应该叫topology-id,只不过在storm最早开发的时候,它每一个topology不叫topology,叫一个storm,但是对外文档都叫做了topology,内部的调用什么没改,还是沿用之前的,所以这里看到的还是storm-id,这里storms其实就是storm,storm-id是topology-id,所以大家以后要是深入到storm代码里面去的时候,有些storm-id的地方其实就是topology-id的意思,并不是两个不一样的东西,就是同一个东西。
3、./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的日志里面翻。


Nimbus的作用:

1.调用topology,将调度信息写入ZK
调用topology,将调度信息写到zookeeper里面去,除了调度信息,提交topology的时候,topology里面的信息也是写到zookeeper里面。
基本信息:
topology名字启动时间状态(active/kill/rebalance/deactive)几个worker component到executors的映射(就是component有哪些个executors)
调用信息:
在master/nimbus里面放代码的路径(后面supervisor要去这个地方下载代码,才能去执行worker)node到host的映射(node就是supervisor的ID,host就是机器名)executor分配到哪个worker上(就是executor的ID到node+port的映射,因为node+port就确定了唯一定位worker)executor的启动时间(监控executor是否在正常的时间内起来了,默认executor的启动超时时间是120秒)。

2.检查supervisor/worker心跳,处理异常
nimbus第二个功能就是作为整个集群的master,它要检查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的带宽很快就会成为瓶颈。

nimbus就是这些,其实核心的核心就是:topology要提交到这个niumbus上面来,然后他要调度topology,supervisor要去执行这些topology。


supervisor的作用:
supervisor是集群里面的slave,每个机器上面都运行一个supervisor的进程,supervisor干三件事情
第一件事情就是启动worker
第二件事情就是监控worker
第三件事情就是把自己的情况汇报出去
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的作用:

worker其实干三个事:
第一,它启动executor,executor真正去执行task,去运行Spout和Bolt的代码
第二,它还负责worker与worker之间的数据传输,因为分布式系统,肯定会有数据的传输
第三,把自己的信息定期往本地系统写一份,然后再往ZK上面写一份
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的作用:
executor它实际上做了3个关键的事情:
1.它会去创建实际的Spout/Bolt的对象
2.两个关键的线程:执行线程、传输线程
执行线程:
它会去执行nextTuple()/execute()两个回调函数,这两个回调函数分别是Spout和Bolt的,这两个方法稍有区别,Spout的nextTuple()执行是死循环的,当然里面有sleep的,就是当上一次调用nextTuple()的时候发现没有数据,那就睡几毫秒,还有一种情况就是,当我SpoutNextPending已经到达的时候,当我发现数据还有大量的没有处理完的时候,那我要做流控,这个时候那我就不要再反复执行nextTuple()了,睡一会儿再执行,Bolt的execute()方法不是一直的去调用,而是说当收到一个新来的Tuple的时候,才会去调用这个execute()函数,Tuple作为参数传给bolt对象,execute()方法就会去处理这个tuple。

传输线程:
传输线程负责做一件事情,它就是负责将新产生的tuple放到worker的传输队列里面去,因为worker它是最后做数据传输的地方,executor它本身不会做数据传输,它只会处理完数据把数据放到内存里面。其实这里面,刚开始executor会从内存里面receive-queue里面取数据,然后进行处理,处理完了,再通过emit将tuple放到transfer-queue里面去,也就是说这里面有两个关键内存DisruptorQueue。

原创粉丝点击