程序在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 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
在发送一个消息的时候,首先需要找到接收消息的接收方。而这个查找过程就是在函数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 sendSpoutMsg(String stream, List
在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 compTasks = grouper.chooseTasks(taskId, values);
outTasks.addAll(compTasks);
}
}
注意其中的grouper.chooseTasks(taskId, values); 其实现如下:
public List chooseTasks(int taskId, List
此时这个customStreamGrouping会调用FieldsGrouper::chooseTasks()函数
@Override
public List chooseTasks(int taskId, List
在此处,就可以很清楚的看到选择过程 。
整个消息发送过程可以看成如下过程:
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列表,其对应的代码就是: