继之前遇到的那个同步问题的坑之后(storm坑之---同步问题),最近对代码又做了调整和重构,并且又遇到了另一个storm开发中应该值得警惕的坑。接下来说说这个坑的大体情况。
在我的storm程序中,Abolt需要将数据封装成一个对象同时发送给Bbolt和Cbolt各一份,Bbolt和Cbolt分别对对象做一定的处理后,更新到数据库。在查看日志时,意外的发现有些数据是不正确的诡异的,我先是怀疑算法问题,但又发现有部分数据又是正确的。算法应该没啥问题。纠结之下之后打印了更详细的日志,通过观察诡异数据的规律最后恍然大悟:肯定是Bbolt收到对象后对对象的修改影响到了Cbolt。在这里笔者几乎可以肯定的是:当Bbolt和Cbolt运行在同一个进程中时。发送给Bbolt和Cbolt的对象他们是公用的。Bbolt的修改会影响到Cbolt,反之亦然。如果Bbolt和Cbolt不是同一进程,则没有此影响。这就解释了为什么有的数据正常有的异常。
下面举一个例子代码测试一下:
拓扑构建类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public
class
Main {
public
static
void
main(String[] args) {
TopologyBuilder builder =
new
TopologyBuilder();
builder.setSpout(
"test"
,
new
TestWordSpout());
builder.setBolt(
"print1"
,
new
PrintBolt(
"PrintBolt1"
)).shuffleGrouping(
"test"
);
builder.setBolt(
"print2"
,
new
PrintBolt(
"PrintBolt2"
)).shuffleGrouping(
"test"
);
Config conf =
new
Config();
conf.setDebug(
false
);
conf.setNumWorkers(
1
);
LocalCluster cluster =
new
LocalCluster();
cluster.submitTopology(
"test-kafka-1"
, conf, builder.createTopology());
}
}
|
spout类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public
class
TestWordSpout
extends
BaseRichSpout {
private
static
final
long
serialVersionUID = 1L;
SpoutOutputCollector _collector;
public
void
open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
_collector = collector;
}
public
void
close() {
}
public
void
nextTuple() {
Utils.sleep(
1000
);
Name name =
new
Name();
name.setName(
"123"
);
_collector.emit(
new
Values(name));
}
public
void
declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(
new
Fields(
"word"
));
}
}
|
bolt类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
public
class
PrintBolt
extends
BaseRichBolt {
private
static
final
long
serialVersionUID = 1L;
private
String name;
int
taskid;
public
PrintBolt(String name){
this
.name = name;
}
@Override
public
void
prepare(Map stormConf, TopologyContext context,
OutputCollector collector) {
this
.taskid = context.getThisTaskId();
}
@Override
public
void
execute(Tuple input) {
Name name = (Name) input.getValueByField(
"word"
);
System.out.println(logPrefix()+name.getName());
name.setName(
this
.name);
}
private
String logPrefix(){
return
this
.name+
":"
;
}
@Override
public
void
declareOutputFields(OutputFieldsDeclarer declarer) {
}
}
|
可能发生的执行结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
PrintBolt2:
123
PrintBolt1:
123
PrintBolt2:
123
PrintBolt1:
123
PrintBolt2:
123
PrintBolt1:
123
PrintBolt2:PrintBolt1
PrintBolt2:
123
PrintBolt1:
123
PrintBolt1:
123
PrintBolt2:
123
PrintBolt1:
123
PrintBolt2:
123
|
从上边结果可以看到,PrintBolt2打印了PrintBolt1的修改。
了解了这个情况,以后写代码就得要考虑到这种意外。如果一个对象会同时发送给两个bolt来处理,切bolt都要对此对象进行修改,在做修改之前一定要克隆一份,而不要直接修改!