原理
ACK 是storm一大亮点. 主要由ack bolt 完成.
每个spout/bolt emit一个tuple (包含此消息的rootId, tupleId, 用户发送的消息内容)出去下游bolt 的同时,也会发一个ack tuple(只包含此消息的rootId, tupleId) 给ack bolt .
a) spout将
<rootId, tuple1Id> 发送到ackBolt, 也将<rootId, tuple1Id, tuple1>发到bolt1.
b) bolt1 收到了spout 发过来的tuple1, 在execute方法处理完成后,对tuple1 处理后, 产生了tuple2, tuple3 发送到bolt2. bolt1 会把新产生的tuple2 和tuple3 的
<rootId , tuple2Id> ,
<rootId , tuple3Id> 发到ackBolt. 同时也会把收到的tuple1 的
<rootId, tuple1Id> 发到ackBolt.
注: tuple1, tuple2, tuple3, 都有相同的rootId, 因为他们都来自spout 发射的同一条信息.(rootId 是spout 内部生成的, 对每一条消息唯一).
c) ackBolt 不停的收到<rootId,tupleId>, 会对rootId 相同的tupleId 做异或. 因为只有tupleId 成双成对出现时, 才说明消息在两个compponent 间 成功被传递被处理了.
如果每个rootId 的tupleId异或结果不为0(有timeout 限制, 不会一直等下去), ackBolt认为此消息失败. 会发送失败反馈给此rootId 对应的spout.(用户可重写spout中的failed(messageId) 来重发数据)
关于ACK 导致消息重复消费
虽然storm的亮点是消息不丢失(ACK). 但也有其弊端.
比如说消息
a1 在 bolt3 执行时消息超时没有处理完, 那么storm(ack bolt)会认为其failed, 并发送failed反馈 给spout--> spout 重发数据 (记为消息
a1')
此时,
a1在bolt3 处理完成(只是超时一点点),也还是会流转到下游bolt, 然后成功结束. 而消息
a1' 也同样被各个bolt 执行. 发生消息重复处理.
解决办法: 如果是做数据分析, 少量消息重发应该无伤大雅. 如果做对重发敏感的业务逻辑(比如处理订单). 可以在每个bolt 的execute最开始 加一段判断此订单是否被处理的小逻辑(比如去query redis, 看此订单编号是否存在)