【译】Apache Storm系列 之一(核心概念)

310 阅读7分钟

本文列出 Storm 的几个主要概念,并会给出相关资源的链接以便你获取更多信息,概念主要如下:

  • Topologies
  • Streams
  • Spouts
  • Bolts
  • Stream groupings
  • Reliability
  • Tasks
  • Workers

拓扑(Topologies)

实时应用程序的逻辑被打包到 Storm 拓扑中。一个 Storm 拓扑类似于一个 MapReduce 任务。关键的区别在于 MapReduce 任务最终会结束,而拓扑会一直运行(当然,除非你强制 kill 掉拓扑相关的进程)。拓扑可以理解为通过数据流(Stream Grouping)将 Spout 和 Bolt 相互连接而组成的图状结构的程序。spouts 和 bolts 的概念会在下文介绍。

相关资源:

流(Streams)

流是 Storm 的核心抽象。Storm中,一个流指的是在分布式环境中被并行创建以及处理的元组(tuple)序列集。流是无限的元组(tuple)序列,以分布式方式并行创建和处理。流往往有固定的模式(我们称之为“fields”),不同模式由不同的元组(tuple)类型以一定的方式组成。通常,元组(tuple)可以包含 integers, longs, shorts, bytes, strings, doubles, floats, booleans, 以及 byte arrays。当然,你也可以通过定义可序列化的对象来实现自定义的元组类型。

相关资源:

数据源(Spouts)

在拓扑中, spout 是流的来源。通常情况,Spouts 会从外部源(例如消息队列或者 Twitter API)读取数据并将数据发送到拓扑中。Spouts 既可以是可靠的,也可以是不可靠的。可靠的情况是如果数据流没有被 Storm 处理,Spouts 将重新发送数据。不可靠的情况则是对发送过的数据不予确认。

Spouts 一次可以发送多个流。为了实现多流发送,我们可以使用(实现) OutputFieldsDeclarer 接口中的 declareStream 方法来指定多个流,并使用(实现) SpoutOutputCollector 接口中的 emit 方法进行发送。

nextTuple 是 Spouts 中的主要方法。nextTuple 方法要么发送一个新的元组到 topology 中,要么直接返回(如果没有新的元组需要发送)。需要注意的是,nextTuple 不应该被 Spout 的任何其他方法所阻塞,否则会导致数据流的停止接入,这是因为 Spout 的所有方法是在一个线程中执行。

ack 和 fail 是 Spouts 中另外两个重要的方法。Spouts 为可靠模式时,Storm 会检测每一个从 Spouts 发送出去的元组是否成功,成功调用 ack,失败调用 fail。当然,在不可靠模式下,是不会调用这两个方法的。

相关资源:

处理组件(Bolts)

topologies 所有的处理都是在 bolts 中进行。bolts 可以做很多事情,例如:过滤流、逻辑处理、聚合、连接、数据库交互等等。

bolts 可以从事简单的数据流转换。处理复杂的数据流转换通常需要将流程分成多步,这也就意味着我们可以使用多类(个) bolt。例如,从微博数据流中得出一个趋势图,实现这个需求我们至少需要两步:第一个 bolt 计算每个图片的点击数,第二个 bolt 在第一个基础上得出 TOP X 的图片(当然为了流程可扩展,我们可以使用更多的 bolt,不仅限于两个)。

bolts 一次可以发送多个流。为了实现多流发送,我们可以使用(实现) OutputFieldsDeclarer 接口中的 declareStream 方法来指定多个流,并使用(实现) OutputCollector 接口中的 emit 方法进行发送(跟 spout 类似)。

在定义 Bolt 的输入数据流时,你需要从其他的 Storm 组件中订阅指定的数据流。如果你需要从其他所有的组件中订阅数据流,你就必须要在定义 Bolt 时分别注册每一个组件。对于声明为默认 id 的数据流,InputDeclarer 接口有订阅此类数据流的语法糖。调用 declarer.shuffleGrouping("1") 将订阅来自 id 为“1” 的组件(spout/bolt)产生的数据流,其等价于调用 declarer.shuffleGrouping("1", DEFAULT_STREAM_ID)

execute 是 bolt 的主要方法,它接收新的元组作为输入。bolt 使用 OutputCollector 对象来发送新的元组。bolt 必须为每个经由它处理的元组调用 OutputCollector 中的 ack 方法,这样以便 Storm 知道这些元组什么时候被处理完成(最终判断对原始 spout 元组的响应是否合适)。处理元组的一般情况是,我们可以发送多个元组或者直接不发送,然后响应下一个输入元组,我们可以实现 IBasicBolt 接口来完成 bolt 操作。

我们可以在 bolt 任务中开启一个新的线程来完成异步操作。OutputCollector 线程安全并且可以随时被调用。

相关资源:

流分组(Stream groupings)

定义一个 topology 的重要一部分是指定每个 bolt 应该接收哪些流作为输入。流分组(stream grouping)定义了流如何分发到各个 bolt 中。

Storm 提供了 8 种流分组策略。当然,你也可以通过实现 CustomStreamGrouping 接口来实现一个用户自定义的流分组:

  • Shuffle grouping : 元组被随机分发到各个 bolt 任务中,也就是说每个 bolt 接收到大致相同数目的元组。
  • Fields grouping : 根据指定的 field 进行分组 ,同一个 field 的值一定会被发送到同一个 task 上。例如,如果流按照 "user-id" 这个 field 进行分组,那么相同的 "user-id" 值会进入相同的任务(task),如果不同,则进入不同的任务。
  • Partial Key grouping : 与 Fields grouping 类似,根据指定的 field 的一部分进行分组分发,能够很好地实现 load balance,将元组发送给下游的 bolt 对应的 task,特别是在存在数据倾斜的场景,使用 Partial Key grouping 能够更好地提高资源利用率
  • All grouping : 流复制到所有 bolt task 上。
  • Global grouping: 所有的流都指向一个 bolt 的同一个 task,也就是Task ID最小的。
  • None grouping : 使用这个分组,用户不用关心流是如何进行分组的。目前,这个分组类似于 Shuffle grouping。不过未来 Storm 可能会考虑通过这种分组来让 Bolt 和它所订阅的 Spout 或 Bolt 在同一个线程中执行。
  • Direct grouping : 由 tupe 的生产者来决定发送给下游的哪一个 bolt 的 task ,这个要在实际开发编写 bolt 代码的逻辑中进行精确控制。
  • Local or shuffle grouping : 如果目标 bolt 有1个或多个 task 都在同一个 worker 进程对应的 JVM 实例中,则 tuple 只发送给这些 task。

可靠性(Reliability)

Storm 保证每个 spout 元组都能在拓扑中被处理。通过跟踪由 Spout 发出的每个元组构成的元组树可以确定元组是否已经完成处理。每个拓扑都有与之相关的消息超时。如果在超时时间内没有检测到元组是否被完整处理,该原则将会被标记并重新发送。

想要使用 Storm 这个可靠性功能,你必须在元组创建以及处理完成时告诉 Storm。你可以使用用于发送数据流的 OutputCollector 对象,并使用 ack 方法表明你已经完成了元组的处理。

任务(Tasks)

集群中,每一个 spout 和 bolt 运行了多个任务。每个任务对应一个执行线程,流分组定义如何将元组从一组任务发送到另一组任务。你可以使用 TopologyBuilder 中的 setSpout 和 setBolt 方法来设置任务并行度。

Workers

一个拓扑中运行了一个或多个 worker 进程。每个进程都是一个物理 JVM,并且拓扑中的所有 task 都在这些进程中执行。例如,如果并行度为 300,我们有 50 个worker 进程,那么每个进程将处理 6 个 task。Storm 有其机制致力于将所有任务尽量平均地分配到每个进程中。

相关资源: Config.TOPOLOGY_WORKERS: 设置 worker 数量的配置


扫码关注微信公众号
扫码关注微信公众号"Kooola大数据",聊人生|聊技术