大数据(Storm)-原理

角色

Client

client的主要作用是提交topology到集群

Worker

Worker是运行在Supervisor节点上的一个独立的JVM进程,主要作用是运行topology,一个topology可以包含多个worker,但一个worker只能属于一个topology

Exceutor

在Worker中运行的线程,一个Executor可以对应一个或多个Task,每个Task(Spout或Bolt)必须对应一个Executor。

Task

一个独立的处理逻辑的实例,每个Spout或Bolt都可以对应多个Task在集群中运行,每个线程对应到一个Executor线程上。
streaminggroup定义了如何从一堆Task发送数据到另外一堆Task上。

Storm集群的启动、任务提交与执行流程

启动

客户运行storm nimbusstorm supervisor时,在storm脚本内部实际对应了两个python函数,这两个函数最终会生成一条java命令,用于启动一个storm的java进程:

java -server xxxx.xxxx.nimbus/supervisor args
任务提交
  • 运行storm java xxxx.MainClass name,此时会执行Driver驱动类的main函数
  • 在驱动类中,会调用topologyBuilder.createTopology()方法,该方法会生成spout和bolt的序列化对象
  • 客户端把topology对应的jar上传的到nimbus的storm-local/nimbus/inbox目录下
  • 首先,nimbus会将storm.jar复制到/home/hadoop/storm-local/nimbus/stormdist/wordcount01-2-1525621662目录下,根据第二步生成的序列化对象生成task的序列化文件和相关配置的序列化文件(wordcount01-2-1525621662为storm生成的一个唯一的topology名称),此时,nimbus就可以进行任务分配了
-rw-rw-r--. 1 hadoop hadoop    3615 56 23:47 stormcode.ser
-rw-rw-r--. 1 hadoop hadoop     733 56 23:47 stormconf.ser
-rw-rw-r--. 1 hadoop hadoop 3248667 56 23:47 stormjar.jar
  • 接下来进行任务分配,分配完成后会产生一个assegiment对象,该对象会被序列化后保存到zookeeper的/storm/assignments/wordcount01-2-1525621662目录下

  • supervisor通过zookeeper的watch机制感知/storm/assignments目录变化,拉取数据自己的topology(nimbus进行分配时,会指定task所属的supervisor)

  • supversior根据拉取到的信息在指定端口上启动worker,实际上就是执行一条java脚本

java -server xxxxx.xxxx.worker
  • worker启动后,根据分配的task信息开始执行。

Storm内部通信机制

Topology在启动后,会在各个Spout和Bolt之间传递数据,有些会跨jvm跨主机,有些在同一个jvm内部。

要理解Storm内部通信机制,需要了解两个组件:一个是用来在节点间进行数据传输的netty,一个是用于为Task缓冲数据的Disruptor队列。每个supervisor会有一个模块用于在节点之间进行高效数据传输,它的底层使用的是基于nio的socket通信,另外每个task还会维护一个Disruptor队列,解决各个任务之间数据流量不同而造成数据积压的问题。

以一个topology为例,内部通信主要流程如下:

1. executor驱动spout并不断调用nextTuple()方法产生数据
2. spout将数据/目标主机/Bolt编号发送给底层通信组件(Netty)
3. 通信组件通过Socket将数据发送到目标主机的通信组件中
4. 目标通信组件根据Bolt编号找到对应的Disruptor队列,并将输入存入队列
5. Bolt从它对应的队列中取出Tuple进行处理,然后将结果发送到通信模块
6. 循环这个处理过程

Storm的ack-fail机制

需要ack-fail机制时,请为每个tuple生成一个messageid,这个messageid用来唯一表示一个tuple,当处理完成时,storm会调用spout的ack方法,否则调用fail方法,对应消息如何处理,完全➡由程序员决定在topology中会存在多个Bolt组件,需要在每个环节中,都要Bolt组件显示告诉storm框架,自己对当前tuple对处理状态。在开启Ack-Fail机制时,每个Topology默认会有一个acker的task,它负责跟踪每个tuple的状态。

状态跟踪的原理

因为从spout task都bolt task一般情况下tuple都会进行多次裂变,如果想跟踪最终状态,就需要跟踪每次裂变是否成功,storm给出来一个高效且需要很少额外存储空间的方案。

Acker task维护一个0/1类型都变量,并且把每个裂变过程分为发送和处理两个节点,当task从spout中发出时,这个变量值为1,tuple进入Bolt前,会有一个标志发送状态的0/1,当下一个Bold处理完成后会调用collector.ack()方法再产生一个用来表示处理状态的0/1,在发送和处理完成后,acker task会用这个值和表示最终状态的变量左异或操作,若与发送时的结果异或为0,则发送成功,若为1,则发送阶段失败,若与处理结果异或为0,则表示处理成功,若为1,则处理节点失败。这种机制高效简洁,每个tuple只需要20字节的额外状态跟踪空间。

    问题1:某个tuple在一个处理节点上一直失败,怎么办?
            可以记录每个tuple被发送的次数,如果重发次数超过上限,则直接丢弃这条Tuple数据。
    问题2:某个task处理tuple数据时一直失败,怎么办?
            如果这时引入来ack-fail机制,会导致数据在Spout的Map中持续积压,最终内存溢出。
    问题3:对于像操作数据库这种Bolt任务,如果某个Bold失败触发Tuple重发,那么如果对数据库中已经被操作对数据进行回滚?
            Storm Trident(待研究)

Storm的使用场景是流式计算,如果对数据对完整性和一致性要求不高对话,可以关闭ack-fail机制,这样因为没有ack-fail的数据交互,可以减少系统中一般左右对消息量,提高吞吐量,关闭办法有两种:

  • Config.TOPOLOGY_ACKERS设置为0,这样Spout发送数据后马上调用ack方法,也就不跟踪tuple树了。
  • Spout发送数据时,不指定messageId

猜你喜欢

转载自blog.csdn.net/zhangdong2012/article/details/80216511
今日推荐