在使用一个框架前,我们需要知道这个框架能用来做什么,可以解决我们什么问题;

Storm是一个流式数据处理框架。我的理解是我们通过某种手段实时地将数据“发送”给框架(或者是框架自己主动获取),数据在框架内被逐层处理,框架内的上下流程间以数据(称之为流)为衔接;最终按照我们自定义逻辑处理热数据或者离线数据(比如在线应用日志,用户行为记录等)的目标;

相关概念

Nimbus:即Storm的Master,负责资源分配和任务调度。一个Storm集群只有一个Nimbus。

Supervisor:即Storm的Slave,负责接收Nimbus分配的任务,管理所有Worker,一个Supervisor节点中包含多个Worker进程。

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

Stream:数据流(Streams)是 Storm 中最核心的抽象概念。

Spout:数据源(Spout)是拓扑中数据流的来源。一般 Spout 会从一个外部的数据源读取元组然后将他们发送到拓扑中。根据需求的不同,Spout 既可以定义为可靠的数据源,也可以定义为不可靠的数据源。一个可靠的 Spout能够在它发送的元组处理失败时重新发送该元组,以确保所有的元组都能得到正确的处理;相对应的,不可靠的 Spout 就不会在元组发送之后对元组进行任何其他的处理。一个 Spout可以发送多个数据流。

Bolt:拓扑中所有的数据处理均是由 Bolt 完成的。通过数据过滤(filtering)、函数处理(functions)、聚合(aggregations)、联结(joins)、数据库交互等功能,Bolt 几乎能够完成任何一种数据处理需求。一个 Bolt 可以实现简单的数据流转换,而更复杂的数据流变换通常需要使用多个 Bolt 并通过多个步骤完成。

附官网一张图

水龙头对应的Spout,其他节点对应的是Bolt,箭头代表的是数据;

我们需要完成的工作就是Spout和Bolt的编写,以及这张网(Topology)逻辑的编织

storm动态设置拓扑日志 简述storm的拓扑结构_storm

环境搭建

我们使用1.2.3版本

<!-- https://mvnrepository.com/artifact/org.apache.storm/storm-core -->
<dependency>
    <groupId>org.apache.storm</groupId>
    <artifactId>storm-core</artifactId>
    <version>1.2.3</version>
</dependency>

示例

个人觉得storm的接口风格和spring batch类似,将用户自定义的操作抽象成一系列接口,这些接口在框架运行期间相互关联(说得直接一点就是调用有先后顺序,一些接口自定义读取数据,另一些接口负责数据处理),所以知道这种模式对我们快速入门有很大的帮助;

编写Spout

Spout主要逻辑是数据源的获得,并将获得的数据发送给下一级的Blot,

编写Spout可以实现 IRichSpout 或继承BaseRichSpout类,这里我们采用继承

package com.uu;

import org.apache.storm.spout.SpoutOutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichSpout;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Values;

import java.util.Map;
import java.util.Random;

public class DemoSpout extends BaseRichSpout {

    private SpoutOutputCollector spoutOutputCollector;
    int count = 0;

    /***
     * 框架开始时会被执行,代码模式一般会把“SpoutOutputCollector”进行缓存到当前类,以便下一步“nextTuple”方法中调用
     * “SpoutOutputCollector”的emit 方法主要作用是将数据向下一级的“Blot”发送数据
     * @param map
     * @param topologyContext
     * @param spoutOutputCollector
     */
    @Override
    public void open(Map map, TopologyContext topologyContext, SpoutOutputCollector spoutOutputCollector) {
        this.spoutOutputCollector = spoutOutputCollector;
    }

    /**
     * 该方法会在框架死循环中一直执行,这个方法里可以读取我们的资源(比如主动读取一个网页源码,数据库里的增量数据等),
     * 并执行“SpoutOutputCollector”的'emit"方法将数据(封装成”Tuple“)发送给下一个”Blot“<br/>
     *
     * 该示例是”发射“4次字符串给下一级的”Blot“
     */
    @Override
    public void nextTuple() {
        if(count < 4){
            spoutOutputCollector.emit(new Values("test-" + new Random(666).nextDouble()));
        }
        count ++;
    }

    /**
     * 声明将数据绑定的key,以便下一个Blot通过”Tuple“获得
     *
     * @param outputFieldsDeclarer
     */
    @Override
    public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
        outputFieldsDeclarer.declare(new Fields("log"));
    }
}

编写Bolt

Blot主要就是处理Spout发送来的数据了,如果数据还需要下一级处理,则可以继续发送给下一级Blot;

或者是输出到文件?数据库?缓存?这里感觉没有Hadoop那个reduce过程,数据都分散了(可能是我理解的不到位)

编写Blot这里我们还是采用继承的方式来

package com.uu;

import org.apache.storm.topology.BasicOutputCollector;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseBasicBolt;
import org.apache.storm.tuple.Tuple;

public class DemoBlot extends BaseBasicBolt {

    /**
     * 该方法主要逻辑是接收上一级的数据,做自定义的处理<br/>
     *
     * 通过Tuple对象以及上一级声明的field获得数据,我们这里只做打印(也可以做数据库写入等)
     *
     * @param tuple
     * @param basicOutputCollector
     */
    @Override
    public void execute(Tuple tuple, BasicOutputCollector basicOutputCollector) {
        String log = tuple.getStringByField("log");
        System.out.println("blot:" + log);
    }

    /***
     * 如果还有下一级处理逻辑,则处理方式跟 DemoSpout 一样,声明向下一级数据绑定的field
     *
     * @param outputFieldsDeclarer
     */
    @Override
    public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {

    }
}

组织Topology

Topology主要作用就是组织Spout与Blot的关系,比如这一次任务需要用哪一个Spout来获得数据,并且指定好用哪些Blot来处理数据,并且各个Blot之间执行先后顺序是怎样的

还有一个就是这里可以指定是本地单机执行任务还是提交集群执行任务

package com.uu;

import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.topology.TopologyBuilder;

public class DemoMain {
    public static void main(String[] args) throws Exception {
        //创建 Topology
        TopologyBuilder topologyBuilder = new TopologyBuilder();

        //创建 spout
        DemoSpout spout = new DemoSpout();

        //创建 Bolt
        DemoBlot blot = new DemoBlot();

        topologyBuilder.setSpout("WebLogSpout", spout, 1);
        topologyBuilder.setBolt("WebLogBolt", blot, 2).shuffleGrouping("WebLogSpout");

        //    获取配置
        Config config = new Config();
        //    设置workers
        config.setNumWorkers(2);

//demo里我们采用本地集群方式启动程序
//如果程序是线上跑的话,需要安装storm集群,并以集群的方式启动
//集群的部署,我们后面再用例子讲解
        LocalCluster localCluster = new LocalCluster();
        localCluster.submitTopology("WebLogTopology", config, topologyBuilder.createTopology());
    }
}

如果是线环境,我们需要将我们编写的应用打包成jar,上传并提交storm集群

输出结果

blot:test-0.6974643684847707
blot:test-0.6974643684847707
blot:test-0.6974643684847707
blot:test-0.6974643684847707

这是一个最简单的例子,有助于入门