我们正在使用Storm和Kafka Spout。当我们对消息失败时,我们想重播它们,但在某些情况下,错误的数据或代码错误会导致消息始终失败,因此我们将进入无限的重放周期。显然我们在找到错误时会修复错误,但希望我们的拓扑通常具有容错能力。在重放N次以后,我们怎么能得到一个元组?
查看Kafka Spout的代码,我发现它设计为使用指数退避计时器和comments on the PR状态重试:
“spout不会终止重试周期(我确信它不应该这样做,因为它不能报告关于中止请求的失败的上下文),它只处理延迟重试。拓扑仍然期望最终调用ack()而不是fail()来停止循环。“
我已经看到StackOverflow的回复建议编写一个自定义的喷口,但是如果有一种推荐的方法在Bolt中执行此操作,我宁可不要坚持维护Kafka Spout内部的自定义补丁。
在博尔特中这样做的正确方法是什么?我没有看到元组中的任何状态暴露了它被重播的次数。
答案 0 :(得分:5)
Storm本身并不为您的问题提供任何支持。因此,定制解决方案是唯一的出路。即使您不想修补KafkaSpout
,我认为,引入计数器并打破其中的重播周期,将是最好的方法。作为替代方案,您也可以从KafkaSpout
继承并在您的子类中放置一个计数器。这当然有点类似于补丁,但可能不那么具有侵入性并且更容易实现。
如果你想使用Bolt,你可以执行以下操作(这也需要对KafkaSpout
或其子类进行一些更改。)
KafkaSpout
之后通过fieldsGrouping
在ID上插入一个螺栓(以确保重播的元组流式传输到同一个螺栓实例)。HashMap<ID,Counter>
并计算(重新)尝试的次数。如果计数器小于阈值,则转发输入元组,使其由后面的实际拓扑处理(当然,您需要适当地锚定元组)。如果计数大于您的阈值,请确认元组打破周期并从HashMap
中删除其条目(您可能还想记录所有失败的元组)。HashMap
中删除成功处理的元组,每次在KafkaSpout
中执行元组时,您需要将元组ID转发给螺栓,以便它可以从{{0}}中删除元组。 {1}}。只需为HashMap
子类声明第二个输出流并覆盖KafkaSpout
(当然,您需要调用Spout.ack(...)
以确保super.ack(...)
获得确认。这种方法可能会占用大量内存。作为KafkaSpout
中每个元组的条目的替代方法,您还可以使用第三个流(与另外两个连接到螺栓),并在元组失败时转发元组ID(即, HashMap
)。每次,螺栓从该第三流接收“失败”消息,计数器增加。只要Spout.fail(...)
中没有条目(或未达到阈值),螺栓就会转发元组进行处理。这应该减少使用的内存,但需要在你的喷口和螺栓中实现更多的逻辑。
这两种方法都有缺点,每个acked元组都会向新引入的螺栓产生额外的消息(从而增加网络流量)。对于第二种方法,似乎您只需要向螺栓发送“确认”消息,以获取之前失败的元组。但是,您不知道哪些元组确实失败了哪些元组失败了。如果您想摆脱这种网络开销,可以在HashMap
中引入第二个HashMap
来缓冲失败消息的ID。因此,如果成功重播失败的元组,则只能发送“确认”消息。当然,第三种方法使逻辑实现更加复杂。
如果不在某种程度上修改KafkaSpout
,我认为没有解决方案。我个人会修补KafkaSpout
或将第三种方法与KafkaSpout
子类中的HashMap
和螺栓一起使用(因为它消耗的内存很少,并且不会给网络带来很多额外的负担与前两个解决方案相比)。
答案 1 :(得分:0)
基本上它的工作原理如下:
ack
,你永远无法修复这样的事情或将其插入数据库。答案 2 :(得分:0)
我们也面临类似的数据,我们有不良数据导致螺栓无限失效。
为了在运行时解决这个问题,我们又引入了一个将其命名为&#34; DebugBolt&#34;以供参考。因此,喷嘴首先将消息发送到此螺栓,然后此螺栓为坏消息执行所需的数据修复,然后将它们发送到所需的螺栓。这样就可以动态修复数据错误。
另外,如果你需要删除一些消息,你实际上可以将一个来自DebugBolt的ignoreFlag传递给你的原始Bolt,如果ignoreFlag为True,原始的bolt应该只发送一个ack到spout而不进行处理。
答案 3 :(得分:0)
我们只是让我们的螺栓在错误流上发出坏元组并对其进行处理。另一个螺栓通过将其写回专门针对错误的Kafka主题来处理错误。这使我们可以轻松地将正常与错误数据流引导通过拓扑。
我们使元组失败的唯一情况是因为某些必需资源处于脱机状态,例如网络连接,数据库,......这些都是可重试的错误。其他任何内容都将指向错误流,以便根据需要进行修复或处理。
当然,这一切都假定您不希望招致任何数据丢失。如果您只想尝试尽力而且在几次重试后忽略,那么我会考虑其他选项。
答案 4 :(得分:0)
据我所知,Storm对此不提供内置支持。
我已经应用了以下提到的实现:
public class AuditMessageWriter extends BaseBolt {
private static final long serialVersionUID = 1L;
Map<Object, Integer> failedTuple = new HashMap<>();
public AuditMessageWriter() {
}
/**
* {@inheritDoc}
*/
@Override
public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
this.collector = collector;
//any initialization if u want
}
/**
* {@inheritDoc}
*/
@Override
public void execute(Tuple input) {
try {
//Write your processing logic
collector.ack(input);
} catch (Exception e2) {
//In case of any exception save the tuple in failedTuple map with a count 1
//Before adding the tuple in failedTuple map check the count and increase it and fail the tuple
//if failure count reaches the limit (message reprocess limit) log that and remove from map and acknowledge the tuple
log(input);
ExceptionHandler.LogError(e2, "Message IO Exception");
}
}
void log(Tuple input) {
try {
//Here u can pass result to dead queue or log that
//And ack the tuple
} catch (Exception e) {
ExceptionHandler.LogError(e, "Exception while logging");
}
}
@Override
public void cleanup() {
// To declare output fields.Not required in this alert.
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
// To declare output fields.Not required in this alert.
}
@Override
public Map<String, Object> getComponentConfiguration() {
return null;
}
}