storm坑之---传递对象
继之前遇到的那个同步问题的坑之后(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都要对此对象进行修改,在做修改之前一定要克隆一份,而不要直接修改!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?