你可能用错了 kafka 的重试机制( 二 )



你可能用错了 kafka 的重试机制
文章图片
我们将这种用例称为跨边界事件发布 。
在执行跨边界事件发布时 , 我们应该发布聚合(Aggregate) 。 聚合是自包含的实体组 , 每个实体都被视为一个单独的原子实体 。 每个聚合都有一个“根”实体 , 以及一些提供附加数据的从属实体 。
当管理聚合的服务发布一条消息时 , 该消息的负载将是一个聚合的某种表示形式(例如JSON或Avro) 。 重要的是 , 该服务将指定聚合的唯一标识符作为分区键 。 这将确保对任何给定聚合实体的更改都将发布到同一分区 。
3出问题的时候怎么办?尽管Kafka的跨边界事件发布机制显得相当优雅 , 但毕竟这是一个分布式系统 , 因此系统可能会有很多错误 。 我们将关注也许是最常见的恼人问题:消费者可能无法成功处理其消费的消息 。

你可能用错了 kafka 的重试机制
文章图片
我们现在该怎么办?
确定这是一个问题团队做错的第一件事就是根本没有意识到这是一个潜在的问题 。 消息失败时有发生 , 我们需要制定一种策略来处理它……要未雨绸缪 , 而非亡羊补牢 。
因此 , 了解这是一种迟早会发生的问题并设计针对性的解决方案是我们要做的第一步 。 如果我们做到了这一点 , 就应该向自己表示一点祝贺 。 现在最大的问题仍然存在:我们该如何处理这种情况?
我们不能一直重试那条消息吗?默认情况下 , 如果消费者没有成功消费一条消息(也就是说消费者无法提交当前偏移量) , 它将重试同一条消息 。 那么 , 难道我们不能简单地让这种默认行为接管一切 , 然后重试消息直到成功吗?
问题是这条消息可能永远不会成功 。 至少 , 没有某种形式的手动干预它是不会成功的 。 于是乎 , 消费者就永远不会继续处理后续的任何消息 , 并且我们的消息处理将陷入困境 。
好吧 , 我们不能简单地跳过那条消息吗?我们通常允许同步请求失败 。 例如 , 对我们的UserAccount服务所做的一个“create-user”POST可能包含错误或丢失的数据 。 在这种情况下 , 我们可以简单地返回一个错误代码(例如HTTP400) , 然后要求调用方重试 。
虽然这种办法并不不理想 , 但这不会对我们的数据完整性造成任何长期问题 。 那个POST代表一条命令 , 是还没有发生的事情 。 即使我们让它失败 , 我们的数据也将保持一致状态 。
当我们丢弃消息时情况并非如此 。 消息表示已经发生的事件 。 任何忽略这些事件的消费者都将与生成事件的上游服务不再同步 。
所有这些都表明 , 我们不想丢弃消息 。
4那么我们如何解决这个问题呢?对我们来说这不是什么容易解决的问题 。 因此 , 一旦我们认识到它需要解决 , 就可以向互联网咨询解决方案 。 但这引出了我们的第二个问题:网上有一些我们可能不应该遵循的建议 。
重试主题:流行的解决方案你会发现最受欢迎的一种解决方案就是重试主题(retrytopics)的概念 。 具体细节因实现而异 , 但总体概念是这样的:
消费者尝试消费主要主题中的一条消息 。
如果未能正确消费该消息 , 则消费者将消息发布到第一个重试主题 , 然后提交消息的偏移量 , 以便继续处理下一条消息 。
订阅重试主题的是重试消费者 , 它包含与主消费者相同的逻辑 。 该消费者在消息消费尝试之间引入了短暂的延迟 。 如果这个消费者也无法消费该消息 , 则会将该消息发布到另一个重试主题 , 并提交该消息的偏移量 。
这一过程继续 , 并增加了一些重试主题和重试消费者 , 每个重试的延迟越来越多(用作退避策略) 。 最后 , 在最终重试消费者无法处理某条消息后 , 该消息将发布到一个死信队列(DeadLetterQueue , DLQ)中 , 工程团队将在该队列中对其进行手动分类 。

你可能用错了 kafka 的重试机制
文章图片
概念上讲 , 重试主题模式定义了失败的消息将被分流到的多个主题 。 如果主要主题的消费者消费了它无法处理的消息 , 它会将该消息发布到重试主题1并提交当前偏移量 , 从而将自身释放给下一条消息 。 重试主题的消费者将是主消费者的副本 , 但如果它无法处理该消息 , 它将发布到一个新的重试主题 。 最终 , 如果最后一个重试消费者也无法处理该消息 , 它将把该消息发布到一个死信队列(DLQ) 。
问题出在哪里?看起来这种方法似乎很合理 。 实际上 , 它在许多用例中都能正常工作 。 问题在于它不能充当一种通用解决方案 。 现实中存在一些特殊用例(例如我们的跨边界事件发布) , 对于这些用例来说 , 这种方法实际上是危险的 。