Storm2.0源码分析 -- 消息分发

主函数的处理

程序在main函数中,会设置spout, bolt的分发方式。 其代码如下:

builder.setBolt("word-normalizer", new WordNormalizerBolt(), 1)
                .shuffleGrouping("word-reader");

查看shuffleGrouping的代码,可以跟踪到:

public BoltDeclarer shuffleGrouping(String componentId) {
    return shuffleGrouping(componentId, Utils.DEFAULT_STREAM_ID);
}

public BoltDeclarer shuffleGrouping(String componentId, String streamId) {
    return grouping(componentId, streamId, Grouping.shuffle(new NullStruct()));
}

private BoltDeclarer grouping(String componentId, String streamId, Grouping grouping) {
    commons.get(_boltId).put_to_inputs(new GlobalStreamId(componentId, streamId), grouping);
            return this;
        }

而查看Commons的定义:

    private final Map<String, ComponentCommon> commons = new HashMap<>();

从这里可以看到commons是一个map, 而这个map的key值为bolt的id, 而value,则是包含了传入的参数”word-reader”与streamId

消息分发的处理

Spout会调用emit将消息发送给下一个bolt进行处理,其代码片断如下:

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

其最终代码如进入函数SpoutOutputCollectorImpl::sendSpoutMsg(…)函数,下面对于这个函数进行解析。在这个函数中会根据streams获取对应的List outTasks,这个变量是一个很重要的信息,它关联到我们在第一步讲的内容。我们先分析这个outTask

根据streamID获取task

在发送一个消息的时候,首先需要找到接收消息的接收方。而这个查找过程就是在函数Task::etOutgoingTasks(stream, values)中完成的。
在Task对象中有两个成员变量需要注意:

    private Map<String, Map<String, LoadAwareCustomStreamGrouping>> streamComponentToGrouper;
    private HashMap<String, ArrayList<LoadAwareCustomStreamGrouping>> streamToGroupers;

这两个成员变量是在Task初始化的时候,完成的。

    public Task(Executor executor, Integer taskId) throws IOException {
        this.taskId = taskId;
        this.executor = executor;
        this.workerData = executor.getWorkerData();
        this.topoConf = executor.getTopoConf();
        this.componentId = executor.getComponentId();
        this.streamComponentToGrouper = executor.getStreamToComponentToGrouper();
        this.streamToGroupers = getGroupersPerStream(streamComponentToGrouper);

        ... ... 
    }

我们知道Executor可以分为spout与bolt,executor对象是在创建任务创建的,在创建的任务的时候,初始化的(可以阅读Executor::mkExecutor()函数),程序的函数名写得很清楚,是将Component转成Grouper,而component就是前面我们说的map对象。

消息的分发

当程序通过task获取到对应的outTask对象列表后,就开始发送,其核心代码:

 private List<Integer> sendSpoutMsg(String stream, List<Object> values, Object messageId, Integer outTaskId) throws
        InterruptedException {
        emittedCount.increment();

        List<Integer> outTasks;
        if (outTaskId != null) {
            outTasks = taskData.getOutgoingTasks(outTaskId, stream, values);
        } else {
            outTasks = taskData.getOutgoingTasks(stream, values);
        }

        ... ... 

        for (int i = 0; i < outTasks.size(); i++) { // perf critical path. don't use iterators.
            Integer t = outTasks.get(i);
            MessageId msgId;
            if (needAck) {
                long as = MessageId.generateId(random);
                msgId = MessageId.makeRootId(rootId, as);
                ackSeq.add(as);
            } else {
                msgId = MessageId.makeUnanchored();
            }

            final TupleImpl tuple =
                new TupleImpl(executor.getWorkerTopologyContext(), values, executor.getComponentId(), this.taskId, stream, msgId);
            AddressedTuple adrTuple = new AddressedTuple(t, tuple);
            executor.getExecutorTransfer().tryTransfer(adrTuple, executor.getPendingEmits());
        }
        ... ... 
    }

关于fieldgrouping的分发

在getOutgoingTasks()函数中有会有一个判断:

        if (null != groupers) {
            for (int i = 0; i < groupers.size(); ++i) {
                LoadAwareCustomStreamGrouping grouper = groupers.get(i);
                if (grouper == GrouperFactory.DIRECT) {
                    throw new IllegalArgumentException("Cannot do regular emit to direct stream");
                }
                List<Integer> compTasks = grouper.chooseTasks(taskId, values);
                outTasks.addAll(compTasks);
            }
        }

注意其中的grouper.chooseTasks(taskId, values); 其实现如下:

        public List<Integer> chooseTasks(int taskId, List<Object> values) {
            return customStreamGrouping.chooseTasks(taskId, values);
        }

此时这个customStreamGrouping会调用FieldsGrouper::chooseTasks()函数

        @Override
        public List<Integer> chooseTasks(int taskId, List<Object> values) {
            int targetTaskIndex = TupleUtils.chooseTaskIndex(outFields.select(groupFields, values), numTasks);
            return targetTasks.get(targetTaskIndex);
        }

在此处,就可以很清楚的看到选择过程 。

总结

整个消息发送过程可以看成如下过程:
1. 每个spout, bolt在创建的时候,会设定相应的name。 每个bolt都会通过name与它的消息发送者联系起来。
2. 每个消息都会设定需要发送的位置(如果用户没有给定,就发送到默认的default中)
3. 当任务运行起来后,任务通过streamId找到相应的bolt信息,然后将这个消息放到对应的bolt消息队列中

补充:

消息的过程:
1. spout 在决定发送消息之后,首先需要这个消息发给哪个队列,如果没有,就放到默认的default中
2. 消息放在defalut队列中,还需要决定发给哪些个task。 系统就会获取所有包含有default队列的task列表。
3. 基本上所有的task列表(所有的bolt)都有default队列,因此,我们就会获取所有的bolt, 此时就需要找到具体的bolt信息。

   builder.setSpout("spout", new RandomSentenceSpout(), 5);
   builder.setBolt("split", new SplitSentence(), 8).shuffleGrouping("spout");       

此时我们在topology创建之前设置的信息就开始起作用了。 如上面,spout对应的taskId的名称为”spout”, 它是与SplitSentence这个Bolt对应的,因此,我们就可以找到SplitSentence这个Bolt对应的task列表,其对应的代码就是:

  1. 4.

猜你喜欢

转载自blog.csdn.net/eyoulc123/article/details/81459817