Storm快速指南

什么是Apache Storm

Apache Storm是一个分布式实时大数据处理系统,通过zookeeper来管理分布式集群。

Apache Storm 核心概念

Storm读取实时数据流,并传递给处理单元,最终输出处理后的数据。
下图描述了storm的处理数据的主要结构。

这里写图片描述

元组(Tuple) : 元组是Storm提供的一个轻量级的数据格式,可以用来包装你需要实际处理的数据。元组是一次消息传递的基本单元。一个元组是一个命名的值列表,其中的每个值都可以是任意类型的。元组是动态地进行类型转化的(字段的类型不需要事先声明)。在Storm中编程时,就是在操作和转换由元组组成的流。通常,元组包含整数,字节,字符串,浮点数,布尔值和字节数组等类型。要想在元组中使用自定义类型,就需要实现自己的序列化方式。

流(Stream) :一个流由无限的元组序列组成,这些元组会被分布式并行地创建和处理。通过流中元组包含的字段名称来定义这个流。
每个流声明时都被赋予了一个ID。只有一个流的Spout和Bolt非常常见,所以OutputFieldsDeclarer提供了不需要指定ID来声明一个流的函数(Spout和Bolt都需要声明输出的流)。这种情况下,流的ID是默认的“default”。

Spouts :Spout(喷嘴)是Storm中流的来源。通常Spout从外部数据源,如消息队列中读取元组数据并吐到拓扑里。Spout可以是可靠的(reliable)或者不可靠(unreliable)的。可靠的Spout能够在一个元组被Storm处理失败时重新进行处理,而非可靠的Spout只是吐数据到拓扑里,不关心处理成功还是失败了。

Spout可以一次给多个流吐数据。此时需要通过OutputFieldsDeclarer的declareStream函数来声明多个流并在调用SpoutOutputCollector提供的emit方法时指定元组吐给哪个流。

Spout中最主要的函数是nextTuple,Storm框架会不断调用它去做元组的轮询。如果没有新的元组过来,就直接返回,否则把新元组吐到拓扑里。nextTuple必须是非阻塞的,因为Storm在同一个线程里执行Spout的函数。

Spout中另外两个主要的函数是ack和fail。当Storm检测到一个从Spout吐出的元组在拓扑中成功处理完时调用ack,没有成功处理完时调用fail。只有可靠型的Spout会调用ack和fail函数。

Bolts :storm是一种分布式实时计算系统,而storm topology中,所有的实时计算的业务逻辑都是定义在bolt中的。bolt中可以做任何计算逻辑,比如过滤、执行自定义的函数、聚合、join、访问数据库,等等。简而言之,bolt实际上就是我们实现或者继承了storm提供的接口或基类,自己开发的类。

接着看一个实例,如何通过Apache Storm来构建Twitter Analysis。结构如下图所示。

这里写图片描述

通过Twitter Streaming API为Twitter Analysis提供输入数据。Spout通过Twitter Streaming API读取数据,并以tuple流的形式输出。随后tuple将转发给bolt,bolt将会对tuple进行处理。

Topology(拓扑):storm topology和mapreduce job是有些类似的。唯一关键的区别就在于,mapreduce job是肯定会结束运行的;但是storm topology是永远会运行的,除非你自己手动杀了它。

使用storm开发的实时计算应用程序,所有的计算逻辑都在topology中。一个topology,其实就是逻辑上的计算流向图,由spout和bolt组成。一个topology可以包含一个或者多个spout和bolt。而spout和bolt,就是topology这个计算流向图种的一个一个的计算节点,其中包含了我们自己编写的计算代码。spout和bolt之间的关系和联系,其实就定义了实时计算的数据流向。可以想象成,数据从外部读入spout,然后传输到后面一个一个的bolt;而bolt之间的数据流向,可能是交叉层叠的,看起来整个topology就像一个DAG(有向无环图)一样。 简而言之,topology,就是逻辑上的实时计算拓扑图。

Tasks(任务):Spout 和 bolt是topology中的最小逻辑单元。topology是通过一个spout和一组bolt构建。逻辑单元需要按特定的顺序来执行。Storm所执行的每个spout和bolt称为task。简而言之,spout或bolt的执行称为task。每个spout和bolt都可以有多个不同的实例运行在不同的线程中。(每一个task对应到一个线程)。

Workers:toplogy是在分布式环境下,多个worker节点上运行。storm将任务均匀分配在所有worker节点上。work节点的作用是监听任务(jobs),当有新任务来时,启动或停止任务的处理。每个worker是一个物理JVM并且执行整个topology的一部分。

Stream Grouping:流分组,是拓扑定义中的一部分,为每个bolt指定应该接收哪个流作为输入。流分组定义流/元组如何在bolt的任务之间进行分发。

Apache Storm集群结构

接着看一下storm集群是如何设计的,以及它的内部结构,下图描绘了集群的结构。

这里写图片描述

storm有两类节点,Nimbus(主节点)和Supervisor(工作节点)。Nimbus主要的任务是运行storm拓扑。Nimbus分析拓扑结构,并收集任务来执行。随后,Nimbus将分配任务给可用的Supervisor。一个supervisor有一个或多个工作进程。Supervisor分派任务给工作进程。worker process会按需生成多个executor并且执行任务(task)。Nimbus和supervisors间通过storm内部的分布式消息系统来进行通信。

Nimbus : Nimbus负责在集群中分发代码,对节点分配任务,并监视主机故障。

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

Work process : worker process 会执行与具体 topology相关的任务。worker process通过创建executor并通过executor来执行具体的任务。一个worker process会有多个executor。

Executor:在Storm0.8以后,Task不再与物理线程对应,同一个Spout/Bolt的Task可能会共享一个物理线程,该线程称为执行器(Executor)。

Task:task执行实际的数据处理,可以是spout或bolt。

ZooKeeper framework:Zookeeper是完成Supervisor和Nimbus之间协调的服务。Nimbus的守护进程和Supervisors守护进程是无法连接和无状态的;所有的状态维持在Zookeeper中 或保存在本地磁盘上。这意味着你可以 kill -9 Nimbus或Supervisors 进程,所以他们不需要做备份。这种设计导致Storm集群具有令人难以置信的稳定性。而应用程序实现实时的逻辑则被封装进Storm中的“topology”。topology则是一组由Spouts(数据源)和Bolts(数据操作)通过Stream Groupings进行连接的图。

Apache storm 工作流程

storm集群有一个nimbus及一个或多个supervisor。此外还需要zookeeper来协调nimbus与supervisor。

  1. nimbus首先会等待 topoloy提交。
  2. 一旦topology被提交后,nimbus会处理topology并收集所有要执行的task及确定执行顺序。
  3. nimbus将这些task平均分配到所有可用的supervisor上。supervisor通过发送心跳包的形式通知nimbus他们还“存活”。
  4. 如果supervisor不可用,那么nimbus将分配任务(task)给其它的supervisor。
  5. nimubs如果挂掉,supervisor会继续处理已分派的任务(task)。
  6. 一旦任务执行完毕,supervisor将会等待新的任务到来。于此同时,挂掉的nimbus会由服务监控工具来重新启动。重启后,nimbus继续工作。类似的supervisor也可以自动的重启。由于重启机制,storm会保证所有任务至少被处理一次。
  7. 所有的topology处理完毕后,nimbus将等待新的topology,同样supervisor也会等待新的任务。

Storm集群中默认有两种模式:

本地模式 : 这种模式用于开发,测试与调试。在这种模式下我们可以通过调整参数来看到topoloy如何在不同的storm配置环境下运行。在本地模式下,storm topology将运行在本地机器的一个JVM上。

生产模式:这种模式下,我们提交我们的topology给storm集群,集群由运行在不同机器上的多个进程组成。

分布式消息系统

storm处理的实时数据来自消息系统。外部分布式消息系统会提交数据来进行实时计算。spout将会读取消息系统的数据,并将这些数据转换为tuple并输入到storm中。

storm内部广泛使用thrift协议来进行通信与数据定义。storm topology是thrift struct。storm nimbus是一个thrift 服务。

环境搭建

安装Java
安装zookeeper
安装storm
- 官网下载storm
- 修改配置文件

当前storm发行版包含一个配置storm守护进行的配置文件,其位置在 conf/storm.yaml。将下列信息添加到配置文件中。

$ vi conf/storm.yaml storm.zookeeper.servers: - "localhost" storm.local.dir: “/path/to/storm/data(any path)” nimbus.host: "localhost" supervisor.slots.ports: 
- 6700 
- 6701 
- 6702 
- 6703

启动nimbus

$ bin/storm nimbus

启动supervisor

$ bin/storm supervisor

启动UI

$ bin/storm ui

执行命令后访问http://localhost:8080将会看到集群信息。
这里写图片描述

一个实例

电话拨打日志分析:电话拨打及其时间将作为storm的输入,storm将会处理这些数据,并按拨打者和接听者进行分组并记录拨打次数。

创建spout:spout用来生产数据。一个spout需要实现IRichSpout接口。

在这个例子中,我们需要收集电话拨打日志详情。这包括: 拨号人电话号、 接听人电话号、 拨打电话时间。

因为我们没有这些日志信息,所以需要生成虚拟拨打日志。下面的代码用来生成日志。

引入依赖

    <dependency>
      <groupId>org.apache.storm</groupId>
      <artifactId>storm-core</artifactId>
      <version>1.1.0</version>
      <!--<scope>provided</scope>-->
    </dependency>

FakeCallLogReaderSpout

package storm;

/**
 * Created by on 2017/7/9.
 */
import java.util.*;
//import storm tuple packages
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Values;

//import Spout interface packages
import org.apache.storm.topology.IRichSpout;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.spout.SpoutOutputCollector;
import org.apache.storm.task.TopologyContext;

//Create a class FakeLogReaderSpout which implement IRichSpout interface
//   to access functionalities

public class FakeCallLogReaderSpout implements IRichSpout {
  //Create instance for SpoutOutputCollector which passes tuples to bolt.
  private SpoutOutputCollector collector;
  private boolean completed = false;

  //Create instance for TopologyContext which contains topology data.
  private TopologyContext context;

  //Create instance for Random class.
  private Random randomGenerator = new Random();
  private Integer idx = 0;

  @Override
  public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
    this.context = context;
    this.collector = collector;
  }

  @Override
  public void nextTuple() {
    if(this.idx <= 1000) {
      List<String> mobileNumbers = new ArrayList<String>();
      mobileNumbers.add("1234123401");
      mobileNumbers.add("1234123402");
      mobileNumbers.add("1234123403");
      mobileNumbers.add("1234123404");

      Integer localIdx = 0;
      while(localIdx++ < 100 && this.idx++ < 1000) {
        String fromMobileNumber = mobileNumbers.get(randomGenerator.nextInt(4));
        String toMobileNumber = mobileNumbers.get(randomGenerator.nextInt(4));

        while(fromMobileNumber == toMobileNumber) {
          toMobileNumber = mobileNumbers.get(randomGenerator.nextInt(4));
        }

        Integer duration = randomGenerator.nextInt(60);
        this.collector.emit(new Values(fromMobileNumber, toMobileNumber, duration));
      }
    }
  }

  @Override
  public void declareOutputFields(OutputFieldsDeclarer declarer) {
    declarer.declare(new Fields("from", "to", "duration"));
  }

  //Override all the interface methods
  @Override
  public void close() {}

  public boolean isDistributed() {
    return false;
  }

  @Override
  public void activate() {}

  @Override
  public void deactivate() {}

  @Override
  public void ack(Object msgId) {}

  @Override
  public void fail(Object msgId) {}

  @Override
  public Map<String, Object> getComponentConfiguration() {
    return null;
  }
}

创建bolt:Call log creator bolt接收调用日志. 拨打日志由拨打号码,接收号码,拨打时间组成。这个bolt仅仅是将信息组合在一起。

Call log counter bolt 接收拨打信息及拨打时常并作为tuple。这个类初在prepare方法中始化一个Map。在execute方法中更新拨打计数。

CallLogCreatorBolt

import java.util.Map;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.IRichBolt;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Tuple;
import org.apache.storm.tuple.Values;

//import Storm IRichBolt package

//Create a class CallLogCreatorBolt which implement IRichBolt interface
public class CallLogCreatorBolt implements IRichBolt {
  //Create instance for OutputCollector which collects and emits tuples to produce output
  private OutputCollector collector;

  @Override
  public void prepare(Map conf, TopologyContext context, OutputCollector collector) {
    this.collector = collector;
  }

  @Override
  public void execute(Tuple tuple) {
    String from = tuple.getString(0);
    String to = tuple.getString(1);
    Integer duration = tuple.getInteger(2);
    collector.emit(new Values(from + " - " + to, duration));
  }

  @Override
  public void cleanup() {}

  @Override
  public void declareOutputFields(OutputFieldsDeclarer declarer) {
    declarer.declare(new Fields("call", "duration"));
  }

  @Override
  public Map<String, Object> getComponentConfiguration() {
    return null;
  }
}

CallLogCounterBolt

import java.util.HashMap;
import java.util.Map;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.IRichBolt;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Tuple;

public class CallLogCounterBolt implements IRichBolt {
  Map<String, Integer> counterMap;
  private OutputCollector collector;

  @Override
  public void prepare(Map conf, TopologyContext context, OutputCollector collector) {
    this.counterMap = new HashMap<String, Integer>();
    this.collector = collector;
  }

  @Override
  public void execute(Tuple tuple) {
    String call = tuple.getString(0);
    Integer duration = tuple.getInteger(1);

    if(!counterMap.containsKey(call)){
      counterMap.put(call, 1);
    }else{
      Integer c = counterMap.get(call) + 1;
      counterMap.put(call, c);
    }

    collector.ack(tuple);
  }

  @Override
  public void cleanup() {
    for(Map.Entry<String, Integer> entry:counterMap.entrySet()){
      System.out.println(entry.getKey()+" : " + entry.getValue());
    }
  }

  @Override
  public void declareOutputFields(OutputFieldsDeclarer declarer) {
    declarer.declare(new Fields("call"));
  }

  @Override
  public Map<String, Object> getComponentConfiguration() {
    return null;
  }
}

Creating Topology 创建拓扑

Storm topology是一个Thrift structure。TopologyBuilder类提供了一些方法可以创建复杂的Topology。TopologyBuilder类有setSpout,setBolt来设置spout和bolt。最后createTopology来创建topology。

TopologyBuilder builder = new TopologyBuilder(); 

builder.setSpout("call-log-reader-spout", new FakeCallLogReaderSpout()); builder.setBolt("call-log-creator-bolt", new CallLogCreatorBolt()) .shuffleGrouping("call-log-reader-spout"); 

builder.setBolt("call-log-counter-bolt", new CallLogCounterBolt()) .fieldsGrouping("call-log-creator-bolt", new Fields("call"));

shuffleGrouping 和 fieldsGrouping 方法 用来设置spout和bolt的流分组。

Local Cluster 本地集群

我们通过LocalCluster类的submitTopology方法来提交topology。submitTopology方法的一个参数是Config类实例。Config类用来在提交topology前进行一些配置。这个配置信息将会与集群配置在运行时合并,并通过prepare方法发送给所有的task(spout和bolt)。一旦topology提交到集群,我们会等待集群完成对topology的计算,随后使用LocalCluster的shutdown方法关闭集群。

import org.apache.storm.Config;

import org.apache.storm.LocalCluster;
import org.apache.storm.topology.TopologyBuilder;
import org.apache.storm.tuple.Fields;

//import storm configuration packages

//Create main class LogAnalyserStorm submit topology.
public class LogAnalyserStorm {
  public static void main(String[] args) throws Exception{

    //Create Config instance for cluster configuration
    Config config = new Config();
    config.setDebug(true);

    //
    TopologyBuilder builder = new TopologyBuilder();
    builder.setSpout("call-log-reader-spout", new FakeCallLogReaderSpout());

    builder.setBolt("call-log-creator-bolt", new CallLogCreatorBolt())
        .shuffleGrouping("call-log-reader-spout");

    builder.setBolt("call-log-counter-bolt", new CallLogCounterBolt())
        .fieldsGrouping("call-log-creator-bolt", new Fields("call"));

    LocalCluster cluster = new LocalCluster();
    System.out.println("after localCluster");
    cluster.submitTopology("LogAnalyserStorm", config, builder.createTopology());
    System.out.println("after submitTopology");

    //Stop the topology
    Thread.sleep(15000);

    cluster.shutdown();
  }
}

原文地址

https://www.tutorialspoint.com/apache_storm/index.htm

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值