Storm基本知识

一 概念

1 元组(tuple)

元组是Storm中消息传输的基本单元,是一个命名的值列表(List)。

元组支持所有基本类型、字符串、字节数组作为字段的值,只要实现类型的序列化接口就可以使用该类型的对象。

元组本来应该是一个Key-value的Map,但是由于组件之间传递的元组的字段名称已经事先定义好,所以只需要按照顺序,将值填入List即可。


2 流(Stream)

流是一个无序的元组序列。每个流被声明后都会赋予一个id,用于唯一识别这个流。

由于单个流的Spout和Bolt很多(Spout和Bolt都可以声明输出多个流,但是情况很少),因此,使用OutputFieldsDeclarer可以声明一个不指定id的流,这个时候,流被赋予了一个默认的id值(64位)。


3 喷口(Spout)

Spout是Storm的数据来源,一般Spout会从消息队列中读取数据。

Spout可以是可靠的,也可以是不可靠的。可靠的Spout会重发处理失败的元组,而不可靠的Spout选择遗忘。

nextTuple()是Spout的核心函数,Storm框架会不断调用这个函数,从外部读取tuple。

当一个元组被完全处理后,Storm调用Spout的ack()函数,否则调用fail()函数。

IRichSpout是Spout必须要实验的一个接口。


4 螺栓(Bolt)

Topology中的所有业务处理,都在Bolt中完成,比如数据的过滤、业务处理、存储等。

Bolt的关键函数是execute(),这个函数的主要功能是处理接收到的数据,发射0个或者多个基于当前元组的元组,然后应答输入元组(ack)。


5 拓扑(Topology)

拓扑就是Storm中真正在运行的程序,节点表示Spout或Bolt,完成数据处理;边表示数据的流动方式。


6 主控节点和工作节点

Storm集群中节点有两类:主控节点和工作节点。其中主控节点1个,工作节点多个。

主控节点上运行了一个称为Numbus的守护进程,负责在集群中分发代码,对节点分配任务,并监视主机故障。

工作节点上运行了一个称为Supervisor的守护进程,负责监听其主机上分配的作业,启动和停止已经分配的工作进程。


7 流分组(Stream grouping)

是拓扑定义的一部分,负责确定每个Bolt应该从哪个拓扑节点获取数据。

Storm内置7中流分组方式,此外,也可以通过实现CustomStreamGrouping接口,设计自定义的流分组方式。


8 工作进程(Worker)

worker在物理上,是一个JVM和拓扑中任务子集。

每一台服务器可以部署一个或者多个worker进程,每个 worker进程执行一个Topology中的部分任务。


9 任务(task)

worker中的每一个Spout或者Bolt线程(相当于Topology中的一个节点),称为一个任务。

Storm集群中,每个Spout或者Bolt执行很多任务,可以通过设置拓扑中每个Spout或Bolt的任务数(setNumTasks()),调整他们执行的任务数。


10 执行器(Executor)

Storm0.8之后,task不再和线程对应,多个task可以共享一个线程。

所以一台服务器可以部署多个worker进程;每个worker进程中可以有多个Executor(对应一个线程);每个Executor中执行一个或者多个task。

Executor的个数,可以通过setSpout或者SetBolt中的并行度参数进行调整。


11 可靠性

Storm可以保证每一个Spout元组被完全处理,它跟踪每个Spout元组的元组树,当元组树中创建元组节点或者完成元组节点时,对应的组件(Spout或Bolt)会通知Storm。

当一个Spout元组没有在“消息超时时间”内完成时,Spout认为元组处理失败,并重发元组。


二 Storm配置

Storm有大量配置,可以调整Nimbus、Supervisor以及拓扑的参数,有些系统配置无法修改,有些可以修改。

配置的方式可以有五种,优先级是

defaults.yaml < storm.yaml < t特定拓扑的配置 < 内部特定组件的配置 < 外部特定组件的配置


1 defaults.yaml

存储了所有配置的默认值。


2 storm.yaml

可以覆盖defaults.yaml中的默认值,常用的配置有:

(1)storm.zookeeper.servers

zookeeper集群主机的ip地址列表。

(2)storm.zookeeper.port

zookeeper集群主机使用的端口,不配置即使用默认端口。

(3)storm.local.dir

Nimbus或者Supervisor守护进程需要在本机存储少量状态,这个目录就是状态存储目录。

(4)nimbus.host

主控节点ip地址

(5)supervisor.slots.prots

每个工作节点,可以配置多个worker,每个worker需要一个端口来接收消息。这个配置定义哪些端口是打开的。


3 使用StormSubmitter提交拓扑的时候,可以定义一个指定拓扑的配置,但是只能覆盖前缀为TOPOLOGY的配置项。


4 内部组件配置

Spout或者Bolt类中,覆盖getComponentConfiguration方法,返回组件配置的Map对象。


5 外部组件配置

TopologyBuilder的setSpout和setBolt方法返回SpoutDeclarer对象和BoltDeclarer对象,这两个对象实现了ComponetConfigurationDeclarer接口,可以通过调用这个接口中的addConfiguration或者addConfigurations方法覆盖组件的配置。


三 序列化(Serialization)

1 元组中可以包含任意的对象,所以,元组在任务之间传递时,需要有序列化和反序列化的过程。

2 Storm使用Kryo序列化,Kryo是一个灵活快速的序列化库。

3 除了使用Kryo,Storm中还可以使用自定义序列化(需要注册)和Java序列化(消耗大量资源)

4 动态类型

Storm将对象放置到字段时,并没有声明字段的类型。Strom会动态的找出字段类型并将其序列化。

使用动态类型的原因:

(1)简化API,动态类型简答且易用。

(2)Bolt方法中的元组可能来自任意流,所以有不同的类型组合。

(3)Storm可以被动态语音更直接的使用(Clojure和Jruby)


四 容错机制

1 Worker进程死亡

Worker死亡时,对应节点的Supervisor会尝试重启Worker。

如果连续重启失败一定次数,无法发送心跳信息到Nimbus,Nimbus会在另一台主机上重新分配Worker.


2 节点死亡

Nimbus会将当前节点的任务分配给其他节点的主机。


3 Nimbus或Supervisor死亡

Nimbus和Supervisor被设计成快速失败(出现故障时,进程自动毁灭)和无状态的(所有状态保存在Zookeeper或者磁盘上)。

Nimbus和Supervisor应该配合Daemontool或者monit工具监控运行,当他们死亡了,可以马上重启。

由于他们是无状态的,所以重启后会向什么都没发生一样继续工作,并且不影响Worker进程的工作。


4 Nimbus的“单点故障”

虽然失去Nimbus节点,worker节点可以正常工作,但是当worker或者节点死亡后,他们的任务无法被安排到其他节点上。


五 可靠性机制(保证消息完整处理)

1 消息被“完全处理”的含义

对于任意一个Spout发出的tuple,经过Topology中Bolt的一系列运算,都会形成一个“元组树”。

当这个树创建完成(Anchor锚定),并且树中的每一个元素都已经被处理(ack),那么Storm认为这个Spout的tuple被“完全处理”。

如果在超时时间内(默认30s),元组树中的元组没有被处理完,认为这个Spout的tuple处理失败。


2 Spout的处理过程

(1)Storm调用Spout的nextTuple方法从Spout请求一个元组。

(2)Spout调用open方法,发射一个元组到输出流,并给这个元组提供一个唯一的id。

(3)元组被发送到Bolt,Storm负责跟踪元组的链接已经处理状态。

(4)如果处理成功,Storm调用ack方法,并将Spout的tuple的id传递给ack函数;否则调用fail方法,同样传递id。


3 Storm如何保证可靠性

想要保证可靠性,需要保证两点:一是当在元组树上创建一个新链接时,需要告诉Storm;二是,当一个元组处理完成时,告诉Storm。

(1)保证第一条

建立链接的过程其实就是Bolt发射元组的过程。Bolt通过“锚定”来通知Storm,这里建立的新的数据链接。


具体的方法是,Bolt调用emit方法时,将输入tuple作为emit方法的第一个参数,这样,后续发送出去的tuple就都锚定在了输入tuple上,形成了一个元组树。只有后续的元组都被ack,这个输入元组才被认为是ack的。


一个输出元组也可以锚定多个输入元组,称为“复合锚定”,输出参数时多个输入元组组成的List。这样当下游元组未被成功处理时,将会触发Spout的多个元组的重发。


其实emit也可以不把输入tuple作为第一个参数(即不使用锚定),这个时候,如果下游的节点没有被处理,Storm的机制将无法将对应的Spout元组重发。这取决于用户需要的可靠性级别。因为不使用锚定可以提高一些效率。

(2)保证第二条

当完成元组树中的单个元组操作时,需要调用OutputCollector类的ack或者fail方法,通知Storm。

fail方法会使对应的Spout元组立即失败,这样Spout就不需要等到超时时间重发,速度会更快。

ack方法通知Storm,当前组件的输入tuple处理成功。


需要注意的是,每一个元组必须执行ack或者fail方法,Storm使用内存追踪每个元组,付过不返回ack/fail,那么最终会导致内存耗尽。


4 Storm如何实现可靠性

Storm的可靠性,主要依赖一组Acker任务。对于每一个Spout元组,Acker任务跟踪元组的有向图。


Storm使用哈希取模的方式,将Spout元组的id(64位id)映射到一个Acker任务,对应的Acker任务就负责跟踪这个Spout元组。


当Spout的元组发送到Bolt中时,Spout的id会复制到Bolt产生的新的tuple中,同理,Bolt向下游Bolt转发tuple时,同样会把这个Spout元组的id复制到新的Tuple中。这样,一系列的tuple都可以根据Spout的id,找到他们对应的Acker任务。


在进行计算时,当一个组件ack时,它发送给Acker一个消息,告诉Acker,当前的tuple已经处理完成,后面又锚定了新的tuple。这样,Acker任务就可以实时的追踪Spout元组的元组树以及元组树中元祖的完成情况。


Acker任务默认只有一个,当拓扑规模比较大时,可以通过配置,让storm产生多个Acker任务。


5 Acker的跟踪算法

Storm中有大量的节点,如果Acker显示的跟踪Spout元组的所有后续节点,那将导致内存溢出。


事实上,Acker只存储来自一个Spout元组的一对key-value的map值,key是创建Spout元组的任务的id,value是一个64位的值,称为ack val,它是所以在元组树上创建的元组以及ack的元组的id的异或值(XOR),如果这个值为0,表示这个Spout元组已经被处理成功。


上面的算法有一个问题,由于Spout元组的id是随机生成的,如果元组生成的id就是0,那么将无法判断是否真的完成。但是这种情况的概率非常小,可以忽略不计。


6 Storm面对失败案例如何保证数据不丢失

(1)任务挂了,导致元组没有ack

对应的Spout元组将会超时重发。

(2)acker任务挂了

同样,Spout元组会超时重发。

(3)Spout任务挂了

Spout的ack方法不会被执行,重启后,可以重新从队列中读取未处理的值。


7 调节可靠性

如果用户不要求每条数据都要被处理,而是对效率比较看重,那么可以选择取消可靠性保护机制。有以下三种方法:

(1)设置Config.TOPOLOGY_ACKERS为0,这种情况下,Storm不启动Acker任务,Spout发射一个元组后,它的ack方法会立即被调用。

(2)在Spout的SpoutOutputCollector.emit方法中,忽略消息的spout id,这样,这个Spout发射的元组将不会被追踪,且Spout不会受到任何关于ack或fail方法的回调。

(3)如果不介意下游元组的特定子集无法被处理,可以作为非锚定元组发射,这样,下游的元组不会被追踪。


六 消息传输机制

1 默认的ZeroMQ

(1)本地化的消息库,过度依赖操作系统。

(2)安装麻烦。

(3)在不同的Storm版本之间,ZeroMQ的稳定性差异大。

2 新引入的Netty

(1)纯Java的消息通信解决方案,不依赖平台。

(2)传输性能是ZeroMQ的两倍。


七 Storm的并行度

与并行度相关的有三个实体:工作进程(Worker),执行器(Executor,即线程),任务(Task)

1 三者之间的关系

Nimbus将Worker平均分配到服务器集群中的节点上。

将所有Executor平均分配到所有的Worker上。

所有任务平均分配到“其对应的”Executor上。(这里需要对应,原因是创建Topology时,同一个组件的Executor和Tast是对应设置的)


2 设置方式

(1)worker

配置选项:TOPOLOGY_WORKERS

代码:Config.setNumWorkers

(2)Executor

配置选项:无

代码:TopologyBuilder.setSpout/setBolt

(3)Task

配置选项:TOPOLOGY_TASK

代码:ComponentConfigurationDeclarer.setNumTasks

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值