对storm1.2.3 并行度的理解

storm的主要组件

topology是对storm进行程序开发的主要组件,一个topology通常由spout和bolt组成,通过数据流,构成一张有向无环图。


对storm1.2.3 并行度的理解_第1张图片
image.png

storm在运行过程中,主要实体有三个,分别是:

1.Worker processes
2.Executors (threads)
3.Tasks

Worker process 指supervisor节点中开启的工作进程, Supervisor节点运行过程中,通过supervisor.slots.ports参数配置启动的workers,由于storm集群在运行的过程中采用的是多进程方式,这个进程实际上就是workers的工作进程。可以理解为supervisor是每个服务器的实际管理者,根据slots的配置,启动一定数量的worker来运行topology的spouth或者bolt节点。
Executors (threads) 可以理解为executor就是workers节点中工作的线程,每一个线程都是一个executor.
Tasks 则是运行在线程上的spout或者bolt实例,执行真正的数据处理。
三者的关系可以参见官网的一张图:


对storm1.2.3 并行度的理解_第2张图片
image.png

实际上下面这张图更加形象:


对storm1.2.3 并行度的理解_第3张图片
image.png

一个supervisor中有多个slots,根据slots的配置可以启动多个worker进程,之后通过executor线程运行多个task。task则是spout或者bolt的实例,然后进行数据处理。在0.8以后的版本,executor才被明确的定义为线程。因此在使用storm时要确认每个不通的storm版本文档描述可能不一样。
对于storm上述三个实体,分别可以通过如下参数设置:

Config conf = new Config();
conf.setNumWorkers(2); // 设置worker进程

topologyBuilder.setSpout("blue-spout", new BlueSpout(), 2); // 设置executer线程

topologyBuilder.setBolt("green-bolt", new GreenBolt(), 2)
              .setNumTasks(4)
              .shuffleGrouping("blue-spout");//通过setNumTasks设置task 的数量。

topologyBuilder.setBolt("yellow-bolt", new YellowBolt(), 6)
              .shuffleGrouping("green-bolt");

StormSubmitter.submitTopology(
       "mytopology",
       conf,
       topologyBuilder.createTopology()
   );

上述代码是官方文档中的一段代码。根据文档描述,上述代码最终的并行度计算为5


对storm1.2.3 并行度的理解_第4张图片
image.png

可以推导出如下公式:

并行度 =  sum(实际的executers总数)/workers总数

为什么要用实际的executers总数 而不是 parallelism 之和呢?实际上上述代码过于简单,掩盖了不少问题。下面通过一段代码,进行实验验证。

代码

SimpleSpout

package com.dhb.storm.example.parallel;

import lombok.extern.slf4j.Slf4j;
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.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import static java.lang.Thread.currentThread;

@Slf4j
public class SimpleSpout extends BaseRichSpout {

    private  SpoutOutputCollector collector;

    private TopologyContext context;

    private AtomicInteger atomicInteger;

    @Override
    public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
        this.collector = collector;
        this.context = context;
        this.atomicInteger = new AtomicInteger(0);
        log.warn("SimpleSpout->open:hashcode:{}->thread:{},taskID->{}",
                this.hashCode(),currentThread(),context.getThisTaskId());
    }

    @Override
    public void nextTuple() {
        int i = this.atomicInteger.incrementAndGet();
        if(i <= 10) {
            log.warn("SimpleSpout->nextTuple:hashcode:{}->thread:{},taskID:{},Value:{}",
                    this.hashCode(),currentThread(),context.getThisTaskId(),i);
            this.collector.emit(new Values(i));
        }
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {

        }
    }

    @Override
    public void declareOutputFields(OutputFieldsDeclarer declarer) {

        declarer.declare(new Fields("i"));

    }

    @Override
    public void close() {
        log.warn("SimpleSpout->close:hashcode:{}->thread:{},taskID->{}",
                this.hashCode(),currentThread(),context.getThisTaskId());
    }
}


SimpleBolt

package com.dhb.storm.example.parallel;

import lombok.extern.slf4j.Slf4j;
import org.apache.storm.task.TopologyContext;
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;

import java.util.Map;

import static java.lang.Thread.currentThread;

@Slf4j
public class SimpleBolt extends BaseBasicBolt {

    private TopologyContext context;

    @Override
    public void prepare(Map stormConf, TopologyContext context) {
    this.context = context;
        log.warn("SimpleBolt->prepare:hashcode:{},thread:{},taskID:{}",
                this.hashCode(),currentThread(),context.getThisTaskId());
    }

    @Override
    public void cleanup() {
        log.warn("SimpleBolt->cleanup:hashcode:{},thread:{},taskID:{}",
                this.hashCode(),currentThread(),context.getThisTaskId());
    }

    @Override
    public void execute(Tuple input, BasicOutputCollector collector) {
        Integer i = input.getIntegerByField("i");
        log.warn("SimpleBolt->execute:hashcode:{},thread:{},taskID:{},value:{}",
                this.hashCode(),currentThread(),context.getThisTaskId(),i);
    }

    @Override
    public void declareOutputFields(OutputFieldsDeclarer declarer) {

    }
}

SimpleToplogy

package com.dhb.storm.example.parallel;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.storm.Config;
import org.apache.storm.StormSubmitter;
import org.apache.storm.generated.AlreadyAliveException;
import org.apache.storm.generated.AuthorizationException;
import org.apache.storm.generated.InvalidTopologyException;
import org.apache.storm.topology.TopologyBuilder;

@Slf4j
public class SimpleToplogy {

    /**
     * @param args
     *  toplogly name
     *  component prefix
     *  workers
     *  spout executor size (parallel hint)
     *  spout task size
     *  bolt executor size (parallel hint)
     *  bplt task size
     */
    public static void main(String[] args) {
        if (7 > args.length) {
            throw new IllegalArgumentException("The arguement is Illegal.");
        }
        final Options options = Options.build(args);
        log.warn("Toplogly-Options",options);
        final TopologyBuilder builder = new TopologyBuilder();
        String spoutName = options.getPrefex()+"SimpleSpout";
        builder.setSpout(spoutName, new SimpleSpout(), options.getSpoutPallelHint())
                .setNumTasks(options.getSpoutTasks());

        builder.setBolt(options.getPrefex() + "-SimpleBolt", new SimpleBolt(), options.getBoltPallelHint())
                .setNumTasks(options.getBoltTasls()).shuffleGrouping(spoutName);

        final Config config = new Config();
        config.setNumWorkers(options.getWorkers());
        try {
            log.warn("==================================================");
            log.warn("******** The toplogly {} is submitted.**********",options.getTopploglyName());
            log.warn("==================================================");
            StormSubmitter.submitTopology(options.getTopploglyName(),config,builder.createTopology());
        } catch (AlreadyAliveException |InvalidTopologyException |AuthorizationException  e) {
            e.printStackTrace();
        }

    }

    @Data
    private static class Options {
        private final String topploglyName;
        private final String prefex;
        private final int workers;
        private final int spoutPallelHint;
        private final int spoutTasks;
        private final int boltPallelHint;
        private final int boltTasls;

        static Options build(String[] args) {
            return new Options(args[0],args[1],Integer.parseInt(args[2]),
                    Integer.parseInt(args[3]),Integer.parseInt(args[4]),
                    Integer.parseInt(args[5]),Integer.parseInt(args[6]));
        }
    }
}

注:上述代码需要依赖lombok
现在对上述代码,打包之后,部署到storm集群上进行测试。

用例1

 storm jar storm-in-action-trunk-jar-with-dependencies.jar com.dhb.storm.example.parallel.SimpleToplogy tp1 tp1 1 2 1 2 1

1个worker,对于spout和bolt,分别启动2个executor和1个task,此时的执行结果见下图:


对storm1.2.3 并行度的理解_第5张图片
image.png

可以发现,虽然指定了2个executer线程,但是spout和bolt都只启动了一个executer。难道是因为worker只有1个的缘故吗,因此改变worker的数量,进行第二组测试。

用例2

 storm jar storm-in-action-trunk-jar-with-dependencies.jar com.dhb.storm.example.parallel.SimpleToplogy tp2 tp2 2 2 1 2 1

2个worker,对于spout和bolt,分别启动2个executor和1个task,此时的执行结果见下图:


对storm1.2.3 并行度的理解_第6张图片
image.png

此时,由于增加了2个worker,storm只是分别在不同的wokrer上启动了spout和bolt,而spout和bolt的executer还是1。由此可以发现,executer的数量与task的数量有关系,与worker没有关系。当executer的数量大于worker的数量时,系统对于空闲的executer不会启动,只会根据task的数量,启动有用的executer。实际上这也能理解,就是storm集群在启动topology时做了优化,一部分无用的线程就不会被启动,以节约系统开销。

用例3

 storm jar storm-in-action-trunk-jar-with-dependencies.jar com.dhb.storm.example.parallel.SimpleToplogy tp3 tp3 2 2 2 2 2

2个worker,对于spout和bolt,分别启动2个executor和2个task,此时的执行结果见下图:


对storm1.2.3 并行度的理解_第7张图片
image.png

根据结果可以发现,此时对于spout和bolt的executer均是2,也进一步说明,只有当executer的数量小于等于task时才有意义。

另外,如果不对task进行设置,系统将默认按1个executer分配1个task来启动。
对于storm并行度及配置参数的影响,可以参考这篇文章:
https://www.cnblogs.com/quchunhui/p/8271349.html

对storm1.2.3 并行度的理解_第8张图片
image.png

上图中很好的说明了storm各参数设置的结果。

结论

我们可以得到如下结论:
1.有3个参数可以对storm对topology的task数量产生影响。
2.当executer的数量大于task的数量时,没有意义,系统会优化掉(不启动)分不到task的executer。
3.task数量一经设置不可改变,executer的数量则可以通过rebalance来改变。
4.如果不设置executer的数量,只通过设置task的数量,并不能提高并发度,反而会造成大量的任务串行,降低效率。

你可能感兴趣的:(对storm1.2.3 并行度的理解)