Jstorm

Jstorm

一、基本术语

Stream

  在JStorm当中,有对Stream的抽象,它是一个不间断的无界的连续Tuple,而JStorm在建模事件流时,把流中的事件抽象成Tuple。

Spout和Bolt

  在JStorm中,它认为每个Stream都有一个Stream的来源,即Tuple的源头,所以它将这个源头抽象为Spout,而Spout可能是一个消息中间件,如:MQ,Kafka等。并不断的发出消息,也可能是从某个队列中不断读取队列的元数据。

![截屏2020-08-10 上午10.23.21](/Users/ganxinming/Library/Application Support/typora-user-images/截屏2020-08-10 上午10.23.21.png)

  在有了Spout后,接下来如何去处理相关内容,以类似的思想,将JStorm的处理过程抽象为Bolt,Bolt可以消费任意数量的输入流, 只要将流方向导到该Bolt即可,同时,它也可以发送新的流给其他的Bolt使用,因而,我们只需要开启特定的Spout,将Spout流出的Tuple 导向特定的Bolt,然后Bolt对导入的流做处理后再导向其它的Bolt等。

![截屏2020-08-10 上午10.23.05](/Users/ganxinming/Library/Application Support/typora-user-images/截屏2020-08-10 上午10.23.05.png)

其实,我们可以用一个形象的比喻来理解这个流程。我们可以认为Spout就是一个个的水龙头,并且每个水龙头中的水是不同 的,我们想要消费那种水就去开启对应的水龙头,然后使用管道将水龙头中的水导向一个水处理器,即Bolt,水处理器处理完后会再使用管道导向到另外的处理 器或者落地到存储介质

Topology

![image-20200810102607645](/Users/ganxinming/Library/Application Support/typora-user-images/image-20200810102607645.png)

如图所示,这是一个有向无环图,JStorm将这个图抽象为Topology,它是JStorm中最高层次的一个抽象概念,它可以处理代码层面 当中直接于JStorm打交道的,可以被提交到JStorm集群执行对应的任务,一个Topology即为一个数据流转换图,图中的每个节点是一个 Spout或者Bolt,当Spout或Bolt发送Tuple到流时,它就发送Tuple到每个订阅了该流的Bolt上。

Tuple

  JStorm当中将Stream中数据抽象为了Tuple,一个Tuple包 含 了 一 个 或 者 多 个 键 值 对 的 列 表,List值的每个Value都有一个Name,并且该Value可以是基本类型,字符类型,字节数组等,当然也可以是其它可序列化的类型。 Topology的每个节点都要说明它所发射出的Tuple的字段的Name,其它节点只需要订阅该Name就可以接收处理相应的内容。

Worker和Task

  Work和Task在JStorm中的职责是一个执行单元,一个Worker表示一个进程,一个Task表示一个线程,一个Worker可以运 行多个Task。而Worker可以通过setNumWorkers(int workers)方法来设置对应的数目,表示这个Topology运行在多个JVM(PS:一个JVM为一个进程,即一个Worker);另外 setSpout(String id, IRichSpout spout, Number parallelism_hint)和setBolt(String id, IRichBolt bolt,Number parallelism_hint)方法中的参数parallelism_hint代表这样一个Spout或Bolt有多少个实例,即对应多少个线程,一 个实例对应一个线程。

Slot

  在JStorm当中,Slot的类型分为四种,他们分别是:CPU,Memory,Disk,Port;与Storm有所区别(Storm局限 于Port)。一个Supervisor可以提供的对象有:CPU Slot、Memory Slot、Disk Slot以及Port Slot。

  • 在JStorm中,一个Worker消耗一个Port Slot,默认一个Task会消耗一个CPU Slot和一个Memory Slot
  • 在Task执行较多的任务时,可以申请更多的CPU Slot
  • 在Task需要更多的内存时,可以申请更多的额Memory Slot
  • 在Task磁盘IO较多时,可以申请Disk Slot

JStorm架构

  从设计层面来说,JStorm是一个典型的调度系统。在这个系统中,有以下内容:

角色 作用
Nimbus 调度器
Supervisor Worker的代理角色,负责Kill掉Worker和运行Worker
Worker Task的容器
Task 任务的执行者
ZooKeeper 系统的协调者

使用:

1.自定义Spout必须实现IRichSpout或者继承其他两个类。

Spout里面主要的方法是nextTuple,它里面可以发射新的tuple到拓扑,或者当没有消息的时候就return,需要注意,这个方法里面不能阻塞,因为storm调用spout方法是单线程的,其他的主要方法是ack和fail,如果使用了可靠的spout,可以使用ack和fail来确定消息发送状态 。(即BaseRichSpout,可以无需我们进行ack确认)

IRichSpout:spout类必须实现的接口
BaseRichSpout :可靠的spout有ack确保
BaseBasicSpout :不可靠的spout

ISpout:

/**
 * 向后端发射tuple数据流
 * @author soul
 *
 */
public class SentenceSpout extends BaseRichSpout {

    //BaseRichSpout是ISpout接口和IComponent接口的简单实现,接口对用不到的方法提供了默认的实现

		//对象提供发射tuple的方法。
    private SpoutOutputCollector collector;
    
    private String[] sentences = {
            "my name is soul",
            "im a boy",
            "i have a dog",
            "my dog has fleas",
            "my girl friend is beautiful"
    };

    private int index=0;

    /**
     * open()方法中是ISpout接口中定义,在Spout组件初始化时被调用。
     * open()接受三个参数:一个包含Storm配置的Map,一个TopologyContext对象,提供了topology中组件的信息,SpoutOutputCollector对象提供发射tuple的方法。
     * 在这个例子中,我们不需要执行初始化,只是简单的存储在一个SpoutOutputCollector实例变量。
     */
    public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
        // TODO Auto-generated method stub
        this.collector = collector;
    }

    /**
     * nextTuple()方法是任何Spout实现的核心。
     * Storm调用这个方法,向输出的collector发出tuple。
     * 在这里,我们只是发出当前索引的句子,并增加该索引准备发射下一个句子。
     */
    public void nextTuple() {
        //collector.emit(new Values("hello world this is a test"));

        // TODO Auto-generated method stub
        this.collector.emit(new Values(sentences[index]));
        index++;
        if (index>=sentences.length) {
            index=0;
        }
        Utils.sleep(1);
    }

    /**
     * declareOutputFields是在IComponent接口中定义的,所有Storm的组件(spout和bolt)都必须实现这个接口
     * 用于告诉Storm流组件将会发出那些数据流,每个流的tuple将包含的字段
     */
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
        // TODO Auto-generated method stub

        declarer.declare(new Fields("sentence"));//告诉组件发出数据流包含sentence字段

    }

}

2.自定义逻辑处理Bolt

IRichBolt:bolts的通用接口
IBasicBolt:扩展的bolt接口,可以自动处理ack
OutputCollector:bolt发射tuple到下游bolt里面

所有的拓扑处理都会在bolt中进行,bolt里面可以做任何etl,比如过滤,函数,聚合,连接,写入数据库系统或缓存等,一个bolt可以做简单的事件流转换,如果是复杂的流转化,往往需要多个bolt参与,这就是流计算,每个bolt都进行一个业务逻辑处理,bolt也可以emit多个流到下游,通过declareStream方法声明输出的schema(类似于标记名词,下个Bolt通过这个名称来拿数据)。

Bolt里面主要的方法是execute方法,每次处理一个输入的tuple,bolt里面也可以发射新的tuple使用OutputCollector类,bolt里面每处理一个tuple必须调用ack方法以便于storm知道某个tuple何时处理完成。Strom里面的IBasicBolt接口可以自动 调用ack。

public class WordCountSplitBolt extends BaseRichBolt{
 
    //bolt组件的收集器 用于将数据发送给下一个bolt
    private OutputCollector collector;
 
 
    //初始化
     /**
     * prepare()方法类似于ISpout 的open()方法。
     * 这个方法在blot初始化时调用,可以用来准备bolt用到的资源,比如数据库连接。
     * 本例子和SentenceSpout类一样,SplitSentenceBolt类不需要太多额外的初始化,
     * 所以prepare()方法只保存OutputCollector对象的引用。
     */
    @Override
    public void prepare(Map map, TopologyContext topologyContext, OutputCollector collector) {
        this.collector = collector;
    }
    
 		/**
     * SplitSentenceBolt核心功能是在类IBolt定义execute()方法,这个方法是IBolt接口中定义。
     * 每次Bolt从流接收一个订阅的tuple,都会调用这个方法。
     * 本例中,收到的元组中查找“sentence”的值,
     * 并将该值拆分成单个的词,然后按单词发出新的tuple。
     */
    @Override
    public void execute(Tuple tuple) {
        //处理上一级发来的数据
        String value = tuple.getStringByField("sentence");
        String[] data= value.split(" ");
        //输出
        for (String word : data){
            collector.emit(new Values(word,1));
        }
    }
 		/**
 		* 发送schema结构,下个bolt也需要这样接受。
 		*/
    @Override
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
        //申明发送给下一个组件的tuple schema结构
        declarer.declare(new Fields("word","count"));
    }
}

3.自定义Topology

就是个普通的类,用于指定Spout,bolt。封装完成后开始创建任务执行。并配置运行模式:本地/集群。

一般来说与spring整合,使用@componet注解就可以了,可以直接定义一个main方法,获取applicationcontext对象获取这个类,然后进行执行。

@Component
public class WordCountTopology {
    public static void main(String[] args) {
        TopologyBuilder builder = new TopologyBuilder();
 
        //1 指定任务的spout组件
        builder.setSpout("1",new WordCountSpout());
 
        //2 指定任务的第一个bolt组件
        builder.setBolt("2",new WordCountSplitBolt()).shuffleGrouping("1");
 
        //3 指定任务的第二个bolt组件
        builder.setBolt("3",new WordCountTotalBolt()).fieldsGrouping("2",new Fields("word"));
 
        //创建任务
        StormTopology job = builder.createTopology();
 
        Config config = new Config();
 
        //运行任务有两种模式
        //1 本地模式   2 集群模式
 
        //1、本地模式
        LocalCluster localCluster = new LocalCluster();
        localCluster.submitTopology("MyWordCount",config,job);
 
        //2、集群模式:用于打包jar,并放到storm运行
//        StormSubmitter.submitTopology(args[0], conf, job);
    }
}

过程:自定义Spout,先执行open方法设置初始配置,循环执行nextTuple从数据源获取数据,然后发射到collector到下个新的tuple(拓扑),并调用declareOutputFields定义好schema。后面的Bolt先执行prepare方法进行初始化配置,后进行处理调用execute方法,如果还需要下一个Bolt处理。则继续用collector发射到新的tuple。collector.emit(new Values(record.value()));发射的类是一个Values。否则就无需发射,并且不用定义schmea。之后通过自定义Topology完成spout和bolt以及运行模式的配置,提交任务。

使用:直接拿到自定义Topology类调用方法。并根据标记判断是本地模式还是分布式模式。

public void startIndicatorTopology(boolean isRemote) {
    try {
        TopologyBuilder topologyBuilder = buildTopology();
        if (isRemote) {
            StormSubmitter.submitTopology(TOPOLOGY_NAME, getWorkerConfig(isRemote), topologyBuilder.createTopology());
        } else {
            LocalCluster cluster = new LocalCluster();
            cluster.submitTopology(TOPOLOGY_NAME, getWorkerConfig(isRemote), topologyBuilder.createTopology());
            //保证任务执行完毕
            Utils.sleep(10000000);
            cluster.shutdown();
        }
    } catch (Exception e) {
        log.error("启动IndicatorTopology失败:[{}]", e);
    }
}

作 为storm的使用者,有两件事情要做以更好的利用storm的可靠性特征。 首先,在你生成一个新的tuple的时候要通知storm; 其次,完成处理一个tuple之后要通知storm。 这样storm就可以检测整个tuple树有没有完成处理,并且通知源spout处理结果。storm提供了一些简洁的api来做这些事情。

由一个tuple产生一个新的tuple称为: anchoring。你发射一个新tuple的同时也就完成了一次anchoring。

我 们通过anchoring来构造这个tuple树,最后一件要做的事情是在你处理完当个tuple的时候告诉storm, 通过OutputCollector类的ack和fail方法来做,如果你回过头来看看SplitSentence的例子, 你可以看到“句子tuple”在所有“单词tuple”被发出之后调用了ack。

每个你处理的tuple, 必须被ack或者fail。因为storm追踪每个tuple要占用内存。所以如果你不ack/fail每一个tuple, 那么最终你会看到OutOfMemory错误。

(发送ack/fail的过程,由于实现的接口默认帮忙实现了,锁不需要手动写)

微信公众号也会更新哦!!

posted @ 2020-09-19 21:39  badribbit123  阅读(247)  评论(0编辑  收藏  举报