BigData-Notes/notes/Storm核心概念详解.md

12 KiB
Raw Blame History

Storm 核心概念详解

一、Storm核心概念
    1.1 Topologies拓扑
    1.2 Streams
    1.3 Spouts
     1.4 Bolts
    1.5 Stream groupings分组策略
二、Storm架构详解
    2.1 nimbus进程
    2.2 supervisor进程
    2.3 zookeeper的作用
    2.4 worker进程
    2.5 executor线程
    2.6 并行度
## 一、Storm核心概念

下图为Storm为运行流程图

1.1 Topologies拓扑

Storm应用程序的逻辑被封装在 Storm topology拓扑一个拓扑是 Spout 和 Bolt 通过 stream groupings 连接起来的有向无环图Storm会一直保持Topologies运行直到你将其杀死kill为止。

1.2 Streams

stream 是 Storm 中的核心概念,一个 stream 是一个无界的、以分布式方式并行创建和处理的 Tuple 序列。

默认情况下 Tuple 可以包含 integers, longs, shorts, bytes, strings, doubles, floats, booleans, and byte arrays 等数据类型,当然你也可以实现自己的自定义类型。

1.3 Spouts

Spout 是一个 topology拓扑中 stream的源头 通常 Spout 会从外部数据源读取 Tuple。

Spout分为 可靠不可靠两种,可靠的 Spout 在 Storm 处理失败的时候能够重新发送 Tuple, 不可靠的 Spout一旦把Tuple 发送出去就不管了。

Spout 可以向不止一个流中发送数据,可以使用OutputFieldsDeclare 的 declareStream 方法定义多个流,并在 SpoutOutputCollector对象的 emit 方法中指定要发送到的stream 。

public class SpoutOutputCollector implements ISpoutOutputCollector {
    ISpoutOutputCollector _delegate;

    ...
    
    public List<Integer> emit(String streamId, List<Object> tuple, Object messageId) {
        return _delegate.emit(streamId, tuple, messageId);
    }


    public List<Integer> emit(List<Object> tuple, Object messageId) {
        return emit(Utils.DEFAULT_STREAM_ID, tuple, messageId);
    }


    public List<Integer> emit(List<Object> tuple) {
        return emit(tuple, null);
    }


    public List<Integer> emit(String streamId, List<Object> tuple) {
        return emit(streamId, tuple, null);
    }

    public void emitDirect(int taskId, String streamId, List<Object> tuple, Object messageId) {
        _delegate.emitDirect(taskId, streamId, tuple, messageId);
    }

    public void emitDirect(int taskId, List<Object> tuple, Object messageId) {
        emitDirect(taskId, Utils.DEFAULT_STREAM_ID, tuple, messageId);
    }
    

    public void emitDirect(int taskId, String streamId, List<Object> tuple) {
        emitDirect(taskId, streamId, tuple, null);
    }

    public void emitDirect(int taskId, List<Object> tuple) {
        emitDirect(taskId, tuple, null);
    }

}

Spout 中的最主要的方法是 nextTuplenextTuple 向 topology拓扑中发送一个新的 Tuple, 如果没有 Tuple 需要发送就直接返回。对于任何 Spout 实现 nextTuple 方法都必须非阻塞的,因为 Storm在一个线程中调用Spout 的所有方法。

Spout 的另外几个重要的方法是 ackfail。 这些方法在 Storm 检测到 Spout 发送出去的 Tuple 被成功处理或者处理失败的时候调用。 ackfail只会在可靠的 Spout 中会被调用。

IRichSpout: 创建 Spout 时必须实现的接口,其中定义了Spout 的主要方法。但是在通常情况下由于我们并不需要实现其中的全部方法所以我们并不会直接实现IRichSpout而是继承其抽象子类BaseRichSpout

public interface ISpout extends Serializable {
   
    void open(Map conf, TopologyContext context, SpoutOutputCollector collector);

    void close();
    
    void activate();
    
    void deactivate();

    void nextTuple();

    void ack(Object msgId);

    void fail(Object msgId);
}

BaseRichSpout继承自BaseComponent并空实现了ISpout中的部分方法这样我们在实现自定义Spout的时候就不需要实现其中不必要的方法。

BaseComponent是IComponent的抽象实现类IComponent 中定义了Topologies拓扑中所有基本组件如SpoutBolts的常用方法。

public abstract class BaseRichSpout extends BaseComponent implements IRichSpout {
    @Override
    public void close() {
    }

    @Override
    public void activate() {
    }

    @Override
    public void deactivate() {
    }

    @Override
    public void ack(Object msgId) {
    }

    @Override
    public void fail(Object msgId) {
    }
}

1.4 Bolts

Bolts是实际的stream处理单元它负责处理数据的处理。Bolts可以执行过滤filtering聚合aggregationsjoins与文件/数据库交互等操作。Bolts从spout/Bolts接收数据处理后再发射数据到一个或多个Bolts中。

Bolts是stream的的处理单元对于一个处理单元来说重要的就只有三点

  • 如何获取数据?
  • 怎样处理数据?
  • 怎样将处理好的数据发送出去?

1.获取数据

Spouts在从外部数据源获得数据后将数据发送到streamBolts想要获得对应的数据可以通过shuffleGrouping方法实现对组件Spouts/Bolts特定流的订阅。

2.处理数据

Bolt 中最主要的方法是 execute 方法, 当有一个新 Tuple 输入的时候就会进入这个方法,我们可以在这个方法中实现具体的处理逻辑。

3.发送数据

这个地方与Sprout是相同的Bolts 可以向不止一个流中发送数据,可以使用OutputFieldsDeclare 的 declareStream 方法定义多个流,并在 SpoutOutputCollector对象的 emit 方法中指定要发送到的stream 。

1.5 Stream groupings分组策略

spouts和bolts在集群上执行任务时是由多个Task并行执行如上图每一个圆圈代表一个Task。当一个tuple需要从Bolt A发送给Bolt B执行的时候我们怎么知道需要发送给Bolt B的哪一个Task执行这是由Stream groupings 分组策略来决定的。

Storm 中一共有8个内置的 Stream Grouping。也可以通过实现 CustomStreamGrouping接口来自定义 Stream groupings。

  1. Shuffle grouping: Tuple 随机的分发到 Bolt Task, 每个 Bolt 获取到等量的 Tuple。
  2. Fields grouping: streams 通过 grouping 指定的字段来分区. 例如流通过 "user-id" 字段分区, 具有相同 "user-id" 的 Tuple 会发送到同一个task, 不同 "user-id" 的 Tuple 可能会流入到不同的 tasks。
  3. Partial Key grouping: stream 通过 grouping 中指定的 field 来分组, 与 Fields Grouping 相似.。但是对于 2 个下游的 Bolt 来说是负载均衡的, 可以在输入数据不平均的情况下提供更好的优化。
  4. All grouping: stream 在所有的 Bolt Tasks之间复制. 这个 Grouping 需要谨慎使用。
  5. Global grouping: 整个 stream 会进入 Bolt 其中一个任务。特别指出, 它会进入 id 最小的 task。
  6. None grouping: 这个 grouping , 你不需要关心 stream 如何分组. 当前, None grouping 和 Shuffle grouping 等价。同时, Storm可能会将使用 None grouping 的 bolts 和上游订阅的 bolt/spout 运行在同一个线程。
  7. Direct grouping: 这是一种特殊的 grouping 方式. stream 用这个方式 group 意味着由这个 Tuple 的 生产者 来决定哪个消费者 来接收它。Direct grouping 只能被用于 direct streams 。
  8. Local or shuffle grouping: 如果目标 Bolt 有多个 task 和 streams源 在同一个 woker 进程中, Tuple 只会 shuffle 到相同 worker 的任务。否则, 就和 shuffle goruping 一样。

二、Storm架构详解

2.1 nimbus进程

也叫做 master node是storm集群工作的全局指挥官。

  1. 通过thrift接口监听并接收client对topology的submit 将topology代码保存到本地目录/nimbus/stormdist/下 ;
  2. 为client提交的topology计算任务分配根据集群worker资源情况计算出topology的spoult和bolt的task应该如何在worker间分配任务分配结果写入zookeeper ;
  3. 通过thrift接口监听supervisor的下载topology代码的请求并提供下载 ;
  4. 通过thrift接口监听ui对统计信息的读取从zookeeper上读取统计信息返回给ui ;
  5. 若进程退出后,立即在本机重启,则不影响集群运行。

2.2 supervisor进程

也叫做 worker node , storm集群的资源管理者按需启动worker进程。

  1. 定时从zookeeper 检查是否有代码未下载到本地的新topology 定时删除旧topology代码 ;
  2. 根据nimbus的任务分配结果在本机按需启动1个或多个worker进程监控守护所有的worker进程;
  3. 若进程退出,立即在本机重启,则不影响集群运行。

2.3 zookeeper的作用

Nimbus和Supervisor进程都被设计为快速失败(遇到任何意外情况时进程自毁)和无状态所有状态保存在Zookeeper或磁盘上。 因此如果Nimbus或Supervisor守护进程死亡它们会重新启动并从zookeeper上获取之前的状态数据就像什么都没发生一样。

2.4 worker进程

torm集群的任务构造者 构造spoult或bolt的task实例启动executor线程。

  1. 根据zookeeper上分配的task在本进程中启动1个或多个executor线程将构造好的task实例交给executor去运行死循环调用spoult.nextTuple()或bolt.execute()方法);
  2. 向zookeeper写入心跳
  3. 维持传输队列发送tuple到其他的worker
  4. 若进程退出,立即在本机重启,则不影响集群运行。

2.5 executor线程

storm集群的任务执行者 循环执行task代码。

  1. 执行1个或多个task每个task对应spout或bolt的1个并行度将输出加入到worker里的tuple队列
  2. 执行storm内部线程acker负责发送消息处理状态给对应spoult所在的worker。

2.6 并行度

1个worker进程执行的是1个topology的子集不会出现1个worker为多个topology服务。1个worker进程会启动1个或多个executor线程来执行1个topology的component(spout或bolt)。因此1个运行中的topology就是由集群中多台物理机上的多个worker进程组成的。

executor是1个被worker进程启动的单独线程。每个executor会运行1个component(spout或bolt)的中的一个或者多个task。

task是最终运行spout或bolt中代码的单元。topology启动后1个component(spout或bolt)的task数目是固定不变的但该component使用的executor线程数可以动态调整例如1个executor线程可以执行该component的1个或多个task实例。这意味着对于1个component#threads<=#tasks线程数小于等于task数目这样的情况是存在的。默认情况下task的数目等于executor线程数目即1个executor线程只运行1个task。

默认情况下

  • 每个worker进程默认启动一个executor线程
  • 每个executor默认启动一个task线程

参考资料

  1. storm documentation -> Concepts

  2. Internal Working of Apache Storm

  3. Understanding the Parallelism of a Storm Topology

  4. Storm nimbus 单节点宕机的处理