Spring-Kafka —— 消费重试机制实现
消息处理问题
在从Kafka主题接收消息之后立即处理消息的消费者的实现非常简单。不幸的是,现实要复杂得多,并且由于各种原因,消息处理可能会失败。其中一些原因是永久性问题,例如数据库约束失败或消息格式无效。其他,如消息处理中涉及的依赖系统的临时不可用,可以在将来解决。在这些情况下,重试消息处理可能是一种有效的解决方案。
非阻塞重试逻辑
在像Kafka这样的流媒体系统中,我们不能跳过消息并在以后回复它们。一旦我们移动当前消息中指向Kafka的指针,我们就无法返回。为简单起见,我们假设消息偏移在成功的消息处理之后就被记住了。在这种情况下,除非我们成功处理当前消息,否则我们无法接收下一条消息。如果处理单个消息不断失败,则会阻止系统处理下一条消息。很明显,我们希望避免这种情况,因为通常一次消息处理失败并不意味着下一次消息处理失败。此外,在较长时间(例如一小时)之后,由于各种原因,失败消息的处理可能成功。对他们来说,我们所依赖的系统可以再次出现。
在消息处理失败时,我们可以将消息的副本发布到另一个主题并等待下一条消息。让我们将新主题称为'retry_topic'。'retry_topic'的消费者将从Kafka接收消息,然后在开始消息处理之前等待一些预定义的时间,例如一小时。通过这种方式,我们可以推迟下一次消息处理尝试,而不会对'main_topic'消费者产生任何影响。如果'retry_topic'消费者中的处理失败,我们只需放弃并将消息存储在'failed_topic'中,以便进一步手动处理此问题。
业务重试场景
现在让我们考虑以下场景。一条新消息被写入主题'main_topic'。如果此消息的处理失败,那么我们应该在5分钟内再次尝试。我们怎么做?我们应该向'retry_topic'写一条新消息,它包装失败的消息并添加2个字段:
- 'retry_number',值为1
- 'retry_timestamp',其值计算为现在+ 5分钟
这意味着'main_topic'使用者将失败的消息处理的责任委托给另一个组件。'main_topic'消费者未被阻止,可以接收下一条消息。'retry_topic'消费者将立即收到'main_topic'消费者发布的消息。它必须从消息中读取'retry_timestamp'值并等到那一刻,阻塞线程。线程唤醒后,它将再次尝试处理该消息。如果成功,那么我们可以获取下一个可用消息。否则我们必须再次尝试。我们要做的是克隆消息,递增'attempt_number'值(它将为2)并将'retry_timestamp'值设置为now + 5分钟。消息克隆将再次发布到'retry__topic。
如果我们到达重试最高次数。现在是时候说“停止”了。我们将消息写入'failed_topic'并将此消息视为未处理。有人必须手动处理它。
下面的图片可以帮助您理解消息流:
总结
正如您所注意到的,在发生某些故障时实施推迟消息处理并不是一件容易的事情。请记住:
- 可以仅按顺序从主题分区中使用消息
- 您不能跳过消费并稍后再处理此消息
- 如果要推迟处理某些消息,可以将它们重新发布到单独的主题,每个延迟值一个
- 处理失败的消息可以通过克隆消息并将其重新发布到重试主题之一来实现,其中包含有关尝试次数和下次重试时间戳的更新信息
- 除非是时候处理消息,否则重试主题的消费者应该阻止该线程
- 重试主题中的消息按时间顺序自然组织,必须按顺序处理
问题