storm基础篇一— 基础概念(concepts)

649 阅读10分钟

Apache Storm是一个分布式实时计算框架,适用于处理无边界的数据流。 所以说Storm是一个基于流处理的工具,也就是说可以无限制地接收流数据,并执行一定地操作来处理数据。同时,Storm也是一个分布式地系统,允许根据情况实时地添加设备以增加运算能力。那么什么是实时呢?在软件系统中,实时定义为: 基于系统设置的运算时间截点,它反映了系统对一个特定事件的响应时长。正常情况下,在Storm的上下文中,实时(低于秒级)和近实时(基于用户场景的秒级或分钟级)的延迟都是可行的。

storm系统主要相关概念

Topologys(拓扑)、Stream(流)、Spouts(数据源)、Bolts(数据流处理组件)、Stream groupings(数据流分组)、Reliability(可靠性)、Tasks(任务)、Workers(工作进程)

Topology(拓扑)

Storm 的拓扑是对实时计算应用逻辑的封装,它的作用与 MapReduce 的任务(Job)很相似,区别在于 MapReduce 的一个 Job 在得到结果之后总会结束,而拓扑会一直在集群中运行,直到你手动去终止它。拓扑还可以理解成由一系列通过数据流(Stream Grouping)相互关联的 Spout 和 Bolt 组成的的拓扑结构。Spout 和 Bolt 称为拓扑的组件(Component)。

image.png
如上图所示,简单而言,Topology就是这样一个计算图,由计算节点(bolt)和边(stream)组成。

Streams(流)

阐述Streams概念前,先大致说一下Tuple(元组):tuple是拓扑中节点间传输数据的形式,本身是一个有序的数值序列,其中每个数值都会赋予一个命名(Note:并不是k-v,如果是k-v,那么k也是元组的一部分,但实际上一个元组只是一系列数值的列表,Storm本身提供一种机制来为列表中每个数值赋予命名)。

image.png
数据流(Streams)是 Storm 中最核心的抽象概念。一个数据流指的是在分布式环境中并行创建、处理的一组元组(tuple)的无界序列。数据流可以由一种能够表述数据流中元组的域(fields)的模式来定义。在默认情况下,元组(tuple)包含有整型(Integer)数字、长整型(Long)数字、短整型(Short)数字、字节(Byte)、双精度浮点数(Double)、单精度浮点数(Float)、布尔值以及字节数组等基本类型对象。当然,你也可以通过定义可序列化的对象来实现自定义的元组类型。

在声明数据流的时候需要给数据流定义一个有效的 id。不过,由于在实际应用中使用最多的还是单一数据流的 Spout 与 Bolt,这种场景下不需要使用 id 来区分数据流,因此可以直接使用OutputFieldsDeclarer来定义“无 id”的数据流。实际上,系统默认会给这种数据流定义一个名为“default”的 id。

Spouts(数据源)

在拓扑结构中,Spouts是数据的来源。通常Spouts会从外部源读取元组并将它们发送到拓扑中(例如Kestrel队列或Twitter API)。一个可靠的 Spout 能够在它发送的元组处理失败时重新发送该元组,以确保所有的元组都能得到正确的处理;相对应的,不可靠的 Spout 就不会在元组发送之后对元组进行任何其他的处理。

一个 Spout 可以发送多个数据流。为了实现这个功能,可以先通过OutputFieldsDeclarerOutputFieldsDeclarer方法声明多个流,然后在发送数据时使用SpoutOutputCollector上的emit方法时指定要发出的流。

Spouts的主要方法是nextTuplenextTuple要么向拓扑中发出一个新的元组,要么在没有新元组要发出时直接返回。值得注意的是:由于 Storm 是在同一个线程中调用所有的 Spout 方法,nextTuple 不能被 Spout 的任何其他功能方法所阻塞,否则会直接导致数据流的中断。

Spouts中其他的主要方法是ackfail方法。当Storm检测到从spout发出的元组通过拓扑成功完成处理或未完成处理时,将调用这些函数。Note: ackfail方法仅对可靠的spout有效。

Bolts(数据流处理组件)

拓扑中所有的数据处理均都是在 Bolt 中完成的。从数据过滤(filtering)、函数处理(functions)、聚合(aggregations)、联结(joins)、数据库交互等等,Bolt 能够完成任何一种数据处理需求。

单个Bolt可以进行简单的数据流转换。更复杂的数据流转换通常需要更多的步骤,因此需多个的bolt。例如,将推特中的推文流转换为趋势图至少需要两步:一个bolt用于对推文的每个图片转发做滚动计数,一个或多个bolt输出转发最多的x个图片。(相较于使用2个bolt,使用3个 Bolt 可以让这种转换具有更好的可扩展性)

Bolt可以发射多个数据流,为了实现这个功能,可以通过 OutputFieldsDeclarerdeclareStream方法声明多个数据流,然后在发送数据时通过OutputCollector中的emit方法将数据流 id 作为参数来实现此功能。

当你声明一个bolt的输入流时,你需要从其它组件中订阅指定的数据流。如果你想要订阅另一组件的所有流,则必须逐个订阅。对于声明为默认id的数据流,InputDeclarer有语法支持订阅它们。declarer.shuffleGrouping("1")订阅默认id为“1”的数据流,等价于 declarer.shuffleGrouping("1", DEFAULT_STREAM_ID)

Bolts中的主要方法为execute方法,它接收一个新的tuple作为输入。Bolts通过OutputCollector对象发射新的Tuple。对于它们处理过的每个tuple,Bolts必须调用OutputCollectorack方法,以便Storm了解tuple是否处理完成(并且最终决定是否可以响应最初的 Spout 元组)。一般情况下,对于每个输入tuple,在处理之后可以根据需要选择发送0或多个新元组,然后再响应(ack)输入元组,storm提供 IBasicBolt接口可以实现元组的自动应答。

Stream groupings(数据流分组)

定义Topology的一部分是为每个bolt指定哪一个流作为输入流。数据流分组定义了在 Bolt 的不同任务中划分数据流的方式。Storm内置了八个流分组方式,你也可以通过实现CustomStreamGrouping接口来自定义一个流分组方式。

  1. 随机分组(Shuffle grouping): tuple在bolt的任务中随机分布,以确保每个bolt尽可能地获得相同数量的元组。
  2. 字段分组(Fields grouping):流根据特定字段的值划分。例如,如果流按“user-id”字段分组,具有相同“user-id”的tuples将进入相同的任务,但具有不同“user-id”的tuple可能进入不同的任务。
  3. 部分关键字分组(Partial Key grouping): 流根据定义的特定字段来对数据流进行分组,类似于按字段分组,但不同的是,这种方式会考虑下游 Bolt 数据处理的均衡性问题,在输入数据源关键字不平衡时会有更好的性能。 This paper很好地解释了它是如何工作的以及它提供的优势。
  4. All grouping: 数据流会被同时发送到 Bolt 的所有任务中(也就是说同一个元组会被复制多份然后被所有的任务处理),使用这种分组方式要特别小心。
  5. 全局分组(Global grouping): 整个流都会发送到bolt的同一任务中,具体的说,是发送到任务id最小的任务中。
  6. None grouping: 这个分组指定说明不关心流是如何分组的。(大致等价于Shuffle grouping)
  7. Direct grouping: 这是一种特殊的分组方式。使用这种方式意味着tuple的发送者可以指定下游的哪个任务可以接收这个元组。只有在数据流被声明为直接数据流时才能够使用直接分组方式。Tuples发射到直接数据流 必须通过emitDirect 中的方法。Bolt可以通过TopologyContext 来获取它的下游消费者的任务 id,或者通过追踪OutputCollector中的emit方法来获取任务id(该方法返回它所发送元组的目标任务的 id)。
  8. Local or shuffle grouping: 如果在worker进程中目标bolt有一个或多个任务,tuple将随机分配到同一进程下的任务,类似与shuffle grouping。

Reliabilty(可靠性)

Storm保证每个spout元组都能被拓扑完全处理。通过跟踪由 Spout 发出的每个tuple构成的元组树可以确定元组是否已经完成处理。每个拓扑都有一个与之关联的“message timeout”参数。如果Storm无法检测到spout元组在超时时间内已经完成,那么它会将该tuple标记为失败,稍后重新发送该元组。

为了充分利用 Storm 的可靠性机制,你必须在元组树创建新边的时候以及tuple处理完成的时候通知 Storm。这个过程可以在 Bolt 发送元组时通过OutputCollector d对象来实现:在 emit 方法中实现元组的锚定(Anchoring),同时使用 ack 方法表明你已经完成了元组的处理。

更多详情,敬请访问 Guaranteeing message processing.

Tasks(任务)

Storm集群中每个Spout或Bolt都执行多个任务。每个任务都对应一个执行线程,数据流分组定义了如何由一组任务向另一组任务发送tuples,也可以通过TopologyBuilder中的setSpoutsetBolt方法设置Spout和Bolt的并行度。

Workers(工作进程)

拓扑是在一个或多个工作进程(worker processes)中运行的。每个工作进程都是一个实际的 JVM 进程,并且执行拓扑的一个子集。例如,如果拓扑的并行度定义为300,工作进程数定义为50,那么每个工作进程就会执行6个任务(进程内部的线程)。Storm 会在所有的 worker 中分散任务,以便实现集群的负载均衡。

各组件关系图

image.png

首先明确: worker是一个进程。executor是一个线程,是运行tasks的物理容器。task是对spout/bolt/acker等任务的逻辑抽象。

一个supervisor包含多个worker,一个worker包含多个executor,一个executor会处理一个或多个task,一个task就是一个spout或bolt实例。

一个worker处理topology的一个子集,同一个子集可被多个worker同时处理,一个worker有且仅为一个topology服务,不会存在一个worker既处理topology1的几个节点,又处理topology2的几个节点;一个executor处理一个节点,但这个节点可能会有多个实例对象,所以可通过配置并发度setNumTask来配置一个executor同时处理多少个task。默认情况下一个executor就处理一个task。如果处理多个task,executor会循环遍历执行task。

image.png

该博客仅为初学者自我学习的记录,粗浅之言,如有不对之处,恳请指正。

参考资料

Storm Document -> concepts
《Storm应用实践-实时事务处理之策略》
strom中worker、task、spout/bolt、executor、component的关系