Apache Kafka 数据流中的消息队列模式
在我开始这篇文章之前,我想让你知道这篇文章是关于“JMS、消息队列和 Apache Kafka”的博客系列的一部分:
JMS 消息代理与 Apache Kafka 数据流的10 个比较标准
这篇文章——通过Apache Kafka 中的死信队列 (DQL)进行错误处理的替代方案
使用 Apache Kafka实现请求-回复模式
即将到来——选择正确消息系统的决策树(JMS 与 Apache Kafka)
即将到来——从 JMS 消息代理到 Apache Kafka:集成、迁移和/或替换
什么是死信队列集成模式(在 Apache Kafka 中)?
死信队列 (DLQ)是消息系统或数据流平台中的一种服务实现,用于存储未成功处理的消息。系统不是被动地转储消息,而是将其移至死信队列。
企业集成模式 (EIP)改为调用设计模式死信通道。我们可以将两者用作同义词。
企业集成模式 (EIP)
本文重点介绍数据流平台 Apache Kafka。在 Kafka 中将消息放入 DLQ 的主要原因通常是消息格式错误或消息内容无效/丢失。例如,如果预期值是 Integer,但生产者发送的是 String,则会发生应用程序错误。在更加动态的环境中,“主题不存在”异常可能是消息无法传递的另一个错误。
因此,与往常一样,不要使用现有中间件经验中的知识。Message Queue 中间件(例如符合 JMS 的 IBM MQ、TIBCO EMS 或 RabbitMQ)的工作方式不同于 Kafka 等分布式提交日志。消息队列中的 DLQ 用于消息队列系统的原因有很多,这些原因不能一对一地映射到 Kafka。例如,MQ 系统中的消息由于每条消息的 TTL(生存时间)而过期。
因此,在 Kafka 中将消息放入 DLQ 的主要原因是消息格式错误或消息内容无效/丢失。
Apache Kafka 中死信队列的替代方案
Kafka 中的死信队列是一个或多个 Kafka 主题,它们接收和存储由于错误而无法在另一个流式管道中处理的消息。此概念允许使用以下传入消息继续消息流,而不会因无效消息的错误而停止工作流。
Kafka Broker 是愚蠢的——智能端点提供错误处理
Kafka 架构不支持在 broker 中进行DLQ 。有意地,Kafka 建立在与现代微服务相同的原则之上,使用“哑管道和智能端点”原则。这就是为什么与传统消息代理相比,Kafka 的扩展性如此之好。过滤和错误处理发生在客户端应用程序中。
数据流平台的真正解耦使领域驱动设计更加简洁。每个微服务或应用程序都通过自己选择的技术、通信范式和错误处理来实现其逻辑。
在传统的中间件和消息队列中,代理提供了这种逻辑。结果是域中的可扩展性和灵活性更差,因为只有中间件团队才能实现集成逻辑。
使用任何编程语言自定义实现 Kafka 死信队列
Kafka 中的死信队列独立于您使用的框架。一些组件为错误处理和死信队列提供开箱即用的功能。但是,使用任何编程语言(如 Java、Go、C++、Python 等)为 Kafka 应用程序编写死信队列逻辑也很容易。
死信队列实现的源代码包含一个 try-catch 块来处理预期或意外的异常。如果没有发生错误,则处理该消息。如果发生任何异常,将消息发送到专用的 DLQ Kafka 主题。
失败原因应该添加到 Kafka 消息的头部。不应更改键和值,以便将来对历史事件进行重新处理和故障分析是直截了当的。
开箱即用的 Kafka 死信队列实现
您并不总是需要实施死信队列。许多组件和框架已经提供了它们的 DLQ 实现。
对于您自己的应用程序,您通常可以控制错误或在出现错误时修复代码。但是,与第 3 方应用程序的集成不一定能让您处理可能跨过集成障碍引入的错误。因此,DLQ 变得更加重要,并作为一些框架的一部分包含在内。
Kafka Connect 内置死信队列
Kafka Connect 是 Kafka 的集成框架。它包含在开源 Kafka 下载中。不需要额外的依赖项(除了您部署到 Connect 集群中的连接器本身)。
默认情况下,如果由于使用无效消息而发生错误(例如使用错误的 JSON 转换器而不是正确的 AVRO 转换器),Kafka Connect 任务将停止。删除无效消息是另一种选择。后者容忍错误。
Kafka Connect 中 DLQ 的配置很简单。只需将两个配置选项 ' errors.tolerance' 和 ' errors.deadletterqueue.topic.name' 的值设置为正确的值:
DLQ
博文“ Kafka Connect Deep Dive – Error Handling and Dead Letter Queues ”展示了使用 DLQ 的详细实践代码示例。
Kafka Connect 甚至可以用来处理 DLQ 中的错误信息。只需部署另一个使用 DLQ 主题的连接器。例如,如果您的应用程序处理 Avro 消息并且传入消息为 JSON 格式。然后连接器使用 JSON 消息并将其转换为 AVRO 消息以便成功重新处理:
在 Kafka connect 中重新处理 DLQ请注意,Kafka Connect 没有用于源连接器的死信队列。
Kafka Streams 应用程序中的错误处理
Kafka Streams 是 Kafka 的流处理库。它可与其他流式处理框架相媲美,例如 Apache Flink、Storm、Beam 和类似工具。但是,它是 Kafka 原生的。这意味着您可以在一个可扩展且可靠的基础架构中构建完整的端到端数据流。
如果您分别使用 Java 和 JVM 生态系统来构建 Kafka 应用程序,建议几乎总是使用 Kafka Streams 而不是 Kafka 的标准 Java 客户端。为什么?
Kafka Streams“只是”一个围绕常规 Java 生产者和消费者 API 的包装器,外加大量内置的附加功能。
两者都只是一个嵌入到您的 Java 应用程序中的库(JAR 文件)。
两者都是开源 Kafka 下载的一部分——没有额外的依赖项或许可证更改。
许多问题已经开箱即用地解决了,以构建成熟的流处理服务(流功能、有状态嵌入式存储、滑动窗口、交互式查询、错误处理等等)。
Kafka Streams的内置功能之一是默认的反序列化异常处理程序。它允许您管理无法反序列化的记录异常。损坏的数据、不正确的序列化逻辑或未处理的记录类型都可能导致错误。该功能不称为死信队列,但开箱即用地解决了同样的问题。
使用 Spring Kafka 和 Spring Cloud Stream 进行错误处理
Spring 框架对 Apache Kafka 有很好的支持。它提供了许多模板以避免自己编写样板代码。Spring-Kafka 和 Spring Cloud Stream Kafka 支持各种重试和错误处理选项,包括基于时间/计数的重试、死信队列等。
尽管 Spring 框架功能非常丰富,但它有点笨重并且有学习曲线。因此,它非常适合新建项目,或者如果您已经在其他场景的项目中使用 Spring。
有大量很棒的博客文章展示了不同的示例和配置选项。还有死信队列的官方 Spring Cloud Stream 示例。Spring 允许使用简单的注释构建逻辑,例如 DLQ。这种编程方法是一些开发人员钟爱的范例,而另一些开发人员则不喜欢它。只需了解选项并为自己选择正确的选项即可。
使用 Apache Kafka 的并行使用者进行可扩展处理和错误处理
在许多客户对话中,事实证明要求死信队列的主要原因通常是处理连接到外部 Web 服务或数据库的失败。超时或 Kafka 无法并行发送各种请求会导致某些应用程序崩溃。这个问题有一个很好的解决方案:
Apache Kafka的并行消费者是Apache 2.0 许可下的开源项目。它提供了一个具有客户端队列的并行 Apache Kafka 客户端包装器、一个具有密钥并发性的更简单的消费者/生产者 API,以及可扩展的非阻塞 IO处理。
该库允许您通过单个 Kafka Consumer 并行处理消息,这意味着您可以提高 Kafka consumer 的并行性,而无需增加您打算处理的主题中的分区数量。对于许多用例,这通过减少 Kafka 代理的负载来提高吞吐量和延迟。它还开辟了新的用例,例如极端并行性、外部数据丰富和排队。
一个关键特性是在单个 Kafka 消费者应用程序中处理/重复 Web 服务和数据库调用。并行化避免了一次发送单个 Web 请求的需要:
Parallel Consumer 客户端具有强大的重试逻辑。这包括可配置的延迟和动态错误或处理。错误也可以发送到死信队列。
使用死信队列中的消息
将错误发送到死信队列后,您还没有完成!需要处理或至少监视不良消息!
死信队列是从事件处理中带外处理数据错误的绝佳方法,这意味着错误处理程序可以从事件处理代码中单独创建或演化。
存在大量使用死信队列的错误处理策略。做和不做探索最佳实践和经验教训。
错误处理策略
有几个选项可用于处理存储在死信队列中的消息:
重新处理:DLQ 中的某些消息需要重新处理。但是,首先需要解决这个问题。解决方案可以是自动脚本、人工交互来编辑消息,或者向生产者返回错误,要求重新发送(更正的)消息。
删除错误消息(经过进一步分析):根据您的设置,可能会出现错误消息。但是,在删除它们之前,业务流程应该检查它们。例如,仪表板应用程序可以使用错误消息并将其可视化。
高级分析:不是处理 DLQ 中的每条消息,另一种选择是分析传入数据以获取实时见解或问题。例如,一个简单的 ksqlDB 应用程序可以应用流处理进行计算,例如每小时平均错误消息数或任何其他有助于确定 Kafka 应用程序中的错误的见解。
停止工作流:如果很少收到坏消息,则结果可能是停止整个业务流程。该动作可以是自动的,也可以由人决定。当然,停止工作流也可以在抛出错误的 Kafka 应用程序中完成。如果需要,DLQ 将问题和决策具体化。
忽略:这听起来可能是最糟糕的选择。让死信队列填满什么也不做。然而,即使在某些用例中这也很好,比如监控 Kafka 应用程序的整体行为。请记住,Kafka 主题有保留时间,并且消息会在该时间之后从主题中删除。只需为您设置正确的方式即可。并监视 DQL 主题是否有意外行为(例如填充速度过快)。
Apache Kafka 中死信队列的最佳实践
以下是在 Kafka 应用程序中使用死信队列进行错误处理的一些最佳实践和经验教训:
定义处理无效消息的业务流程(自动与人工)
现实:通常根本没有人处理 DLQ 消息
备选方案 1:数据所有者需要接收警报,而不仅仅是基础设施团队
备选方案 2:警报应通知记录系统团队数据已损坏,他们将需要从记录系统重新发送/修复数据。
如果没有人关心或抱怨,请考虑质疑和审查 DLQ 存在的必要性。相反,这些消息也可以在初始 Kafka 应用程序中被忽略。这节省了大量网络负载、基础设施和资金。
构建具有适当警报的仪表板并整合相关团队(例如,通过电子邮件或 Slack 警报)
定义每个 Kafka 主题的错误处理优先级(停止、删除和重新处理)
仅将不可重试的错误消息推送到 DLQ - 连接问题由消费者应用程序负责。
保留原始消息并将它们存储在 DLQ 中(带有附加标头,例如错误消息、错误时间、发生错误的应用程序名称等)——这使得重新处理和故障排除变得更加容易。
想想你需要多少个 Dead Letter Queue Kafka 主题。总是需要权衡取舍。但是将所有错误存储在单个 DLQ 中对于进一步分析和重新处理可能没有意义。