2021SC@SDUSC
bolt源码分析(一)
2021SC@SDUSC
bolt:
核心概念介绍
消息处理者
Bolt在Storm中是一个被动的角色,它把元组作为输入,然后产生新的元组作为输出。
Bolt可以执行过滤、函数操作、合并、写数据库等操作(还可以简单地传递消息流,复杂的消息流往往需要很多步骤,因此需要很多Bolt来处理)。
生命周期
1、客户端创建Bolt,然后将其序列化为拓扑,并提交给集群中的主机。
2、集群启动Worker进程,反序列化Bolt,调用prepare方法开始处理元组。
3、Bolt处理Tuple,Bolt处理一个输入Tuple,发射0个或者多个Tuple,然后调用ack通知Storm自己已经处理过这个Tuple了(Storm提供了一个IBasicBolt自动调用ack)。Bolt类接收由Spout或者其他上游Bolt类发来的Tuple,对其进行处理。
输入阶段
Spout的输出消息到达Bolt,作为Bolt的输入会经过这么几个阶段。
- spout的输出通过该spout所处worker的消息输出线程,将tuple输入到Bolt所属的worker。它们之间的通路是socket连接,用ZeroMQ实现。
- bolt所处的worker有一个专门处理socket消息的receive thread 接收到spout发送来的tuple
- receive thread将接收到的消息传送给对应的bolt所在的executor。 在worker内部(即同一process内部),消息传递使用的是Lmax Disruptor pattern.
- executor接收到tuple之后,由event-handler进行处理
JoinBolt.java
JoinBolt继承了BaseWindowedBolt,定义了Selector selectorType、LinkedHashMap<String, JoinInfo> joinCriteria、FieldSelector[] outputFields等属性,用于记录关联类型及关联关系
这里介绍其中四个重要基本方法:
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
String[] outputFieldNames = new String[outputFields.length];
for (int i = 0; i < outputFields.length; ++i) {
outputFieldNames[i] = outputFields[i].getOutputName();
}
if (outputStreamName != null) {
declarer.declareStream(outputStreamName, new Fields(outputFieldNames));
} else {
declarer.declare(new Fields(outputFieldNames));
}
}
@Override
public void prepare(Map<String, Object> topoConf, TopologyContext context, OutputCollector collector) {
this.collector = collector;
// initialize the hashedInputs data structure
int i = 0;
for (String stream : joinCriteria.keySet()) {
if (i > 0) {
hashedInputs.put(stream, new HashMap<Object, ArrayList<Tuple>>());
}
++i;
}
if (outputFields == null) {
throw new IllegalArgumentException("Must specify output fields via .select() method.");
}
}
@Override
public void execute(TupleWindow inputWindow) {
// 1) Perform Join
List<Tuple> currentWindow = inputWindow.get();
JoinAccumulator joinResult = hashJoin(currentWindow);
// 2) Emit results
for (ResultRecord resultRecord : joinResult.getRecords()) {
ArrayList<Object> outputTuple = resultRecord.getOutputFields();
if (outputStreamName == null) {
// explicit anchoring emits to corresponding input tuples only, as default window anchoring will anchor them to all
// tuples in window
collector.emit(resultRecord.tupleList, outputTuple);
} else {
// explicitly anchor emits to corresponding input tuples only, as default window anchoring will anchor them to all tuples
// in window
collector.emit(outputStreamName, resultRecord.tupleList, outputTuple);
}
}
}
private void clearHashedInputs() {
for (HashMap<Object, ArrayList<Tuple>> mappings : hashedInputs.values()) {
mappings.clear();
}
}
declareOutputFields(OutputFieldsDeclarer declarer):
来源于 IComponent,声明发送的 tuples 的名称,这样下一个组件才能知道如何接收。即为为bolt声明输出模式。
prepare(Map<String, Object> topoConf, TopologyContext context, OutputCollector collector):
仅在bolt开始处理元组之前调用。和Spout的open函数的作用类似,在Bolt组件初始化的时候调用,提供Bolt所必需的环境。
execute(TupleWindow inputWindow):
这是Bolt中最关键的一个方法,对于Tuple的处理都可以放到此方法中进行。具体的发送也是通过emit方法来完成的。此时,有两种情况,一种是emit方法中有两个参数,另一个种是有一个参数。
(1)emit有一个参数:此唯一的参数是发送到下游Bolt的Tuple,此时,由上游发来的旧的Tuple在此隔断,新的Tuple和旧的Tuple不再属于同一棵Tuple树。新的Tuple另起一个新的Tuple树。
(2)emit有两个参数:第一个参数是旧的Tuple的输入流,第二个参数是发往下游Bolt的新的Tuple流。此时,新的Tuple和旧的Tuple是仍然属于同一棵Tuple树,即,如果下游的Bolt处理Tuple失败,则会向上传递到当前Bolt,当前Bolt根据旧的Tuple流继续往上游传递,申请重发失败的Tuple。保证Tuple处理的可靠性。
参考链接:
https://blog.youkuaiyun.com/wdasdaw/article/details/48896321
https://www.cnblogs.com/hseagle/p/3333768.html