
Agoda 每天通过 Apache Kafka 发送约 1.8 万亿条事件。
自 2015 年以来,Agoda 的 Kafka 使用量大幅增长,年均增长率达 2 倍。
Kafka 在 Agoda 支持多种用例:
- 分析数据管理
- 为数据湖提供数据
- 近实时监控和告警解决方案
- 构建异步 API
- 数据中心之间的数据复制
- 为机器学习管道提供数据
随着规模和 Kafka 使用量的增长,多个挑战迫使 Agoda 的工程团队开发解决方案。
在本文中,我们将探讨 Agoda 面临的一些关键挑战以及他们实施的解决方案。
简化开发人员向 Kafka 发送数据的方式
Agoda 所做的最早改变之一是关于向 Kafka 发送数据的方式。
Agoda 构建了一个两步日志架构:
- 客户端库将事件写入磁盘。它处理文件轮转并管理写入文件位置。
- 一个独立的守护进程(Forwarder)读取事件并将其转发到 Kafka。它负责读取文件、将事件发送到 Kafka、跟踪文件偏移量以及管理已完成文件的删除。
见下图:

该架构将运维关注点从开发团队中分离出来,使 Kafka 团队能够独立执行动态配置、优化和升级等任务。客户端库为生产者提供了简化的 API,强制执行序列化标准,并增加了一层弹性。
这种权衡是用增加的延迟换取更好的弹性和灵活性,分析工作负载的 99 百分位延迟为 10 秒。对于需要亚秒级延迟的关键和 time-sensitive 用例,应用程序可以绕过两步日志架构,直接写入 Kafka。
根据用例拆分 Kafka 集群
Agoda 很早就做出了一个战略决策:根据用例拆分 Kafka 集群,而不是在每个数据中心建立一个大型 Kafka 集群。
这意味着,Agoda 没有用一个庞大的 Kafka 集群服务所有类型的工作负载,而是有多个较小的 Kafka 集群,每个集群专用于特定的用例或一组用例。
采用这种方法的主要原因是:
- 通过为不同的用例建立独立的集群,任何一个集群中出现的问题都将被限制在该集群内,不会影响其他集群。
- 不同的用例可能在性能、可靠性和数据保留方面有不同的要求。
例如,用于实时数据处理的集群可能会配置较低的数据保留周期和较高的网络吞吐量,以处理大量数据。

除了根据用例拆分 Kafka 集群外,Agoda 还为 Zookeeper 配置了专用的物理节点,与 Kafka broker 节点分开。Zookeeper 是 Kafka 集群中的关键组件,负责管理集群的元数据、协调 broker 领导者选举和维护配置信息。
Kafka 监控和审计
从监控的角度来看,Agoda 使用 JMXTrans 收集 Kafka broker 指标。
JMXTrans 是一种连接到 JMX(Java Management Extensions)端点并收集指标的工具。然后将这些指标发送到 Graphite,这是一个存储数字时间序列数据的时间序列数据库。
收集的指标包括 broker 吞吐量、分区计数、消费者延迟和各种其他 Kafka 特定的性能指标。
存储在 Graphite 中的指标使用 Grafana 可视化,Grafana 是一个流行的开源监控和可观测性平台。Grafana 允许创建自定义仪表板,显示来自 Graphite 的实时和历史数据。

对于审计,Agoda 实现了一个自定义的 Kafka 审计系统。该审计系统的主要目标是确保整个 Kafka 管道的数据完整性、可靠性、准确性和及时性。
工作原理如下:
- 在管道的各个关键点生成审计计数。
- 一个独立的线程在我们之前讨论过的两步日志架构中的 Agoda 客户端库后台运行。该线程异步聚合跨时间桶的消息计数以生成审计。
- 生成的审计数据存储在一个专门用于审计信息的独立 Kafka 集群中。这确保了审计数据不会干扰主数据管道。
审计信息最终会出现在两个地方:
- Whitefalcon:Agoda 的内部近实时分析平台
- Hadoop:用于更长期的存储和分析
认证和 ACL
最初,Agoda 的 Kafka 集群主要用于应用程序遥测数据,认证被认为是不必要的。
随着 Kafka 使用量呈指数级增长,人们开始担心无法识别和管理可能滥用或对 Kafka 集群性能产生负面影响的用户。Agoda 于 2021 年完成并发布了 Kafka 认证和授权系统。
认证和授权系统包括以下组件:
- 核心 Kafka 认证:可能使用 Kafka 支持的 SASL(Simple Authentication and Security Layer)机制。
- ACL:用于细粒度权限管理的访问控制列表。
- 凭据生成:用于创建和管理用户凭据的自定义组件。
- 凭据分配:将凭据与特定用户或团队关联的系统。
- 自助服务门户:允许团队请求 Kafka 凭据和 ACL 而无需 Kafka 团队直接干预的界面。

Kafka 负载均衡
Agoda 作为一个在线旅游预订平台,旨在为其客户提供来自各种外部供应商(包括酒店、餐厅和交通服务提供商)的最具竞争力和最新的住宿和服务价格。
为了实现这一目标,Agoda 的供应系统旨在高效处理和整合从这些供应商接收到的大量实时价格更新。单个供应商可以在短短一分钟内提供 150 万条价格更新和优惠详情。任何反映这些更新的延迟或失败都可能导致定价错误和预订失败。
Agoda 使用 Kafka 来处理这些传入的价格更新。Kafka 分区通过将工作负载分配到多个分区和消费者来帮助他们实现并行化。
见下图:

Partitioner 和 Assignor 策略
Apache Kafka 的消息分发和消费受到两个关键策略的严重影响:partitioner 和 assignor。
Partitioner 策略决定了在消息生产期间如何将传入消息分配到各个分区。常见的方法包括轮询分发和粘性分区。
在消费者方面,Assignor 策略决定了分区如何在消费者组内的消费者之间分配。示例包括范围分配和轮询分配。

传统上,这些策略的设计基于这样的假设:所有消费者都具有相似的处理能力,并且所有消息都需要大致相同的处理时间。
然而,Agoda 的现实情况偏离了这些假设,导致 Kafka 实现中的重大负载均衡挑战。
主要有两个挑战:
- 硬件异构性:Agoda 使用带有 Kubernetes 的私有云基础设施,导致 pod 被部署在不同硬件规格的服务器上。基准测试显示,不同硬件代之间的性能差异很大。
- 消息工作负载不一致:不同消息的处理要求差异很大。一些消息需要额外的步骤,例如第三方 API 调用或数据库查询,导致处理时间和延迟波动不可预测。
这些挑战最终导致过度配置问题,即资源被低效分配以补偿由硬件差异和不同消息处理需求引起的负载不平衡。
Agoda 的过度配置问题
过度配置涉及分配比有效处理预期峰值工作负载所需更多的资源。
为了说明这一点,让我们考虑一个场景,其中 Agoda 的处理器服务在异构硬件上运行 Kafka 消费者:
- 他们有两个高性能工作节点,每个节点每秒能够处理 20 条消息。
- 另外,他们有一个较慢的工作节点,每秒只能处理 10 条消息。
理论上,这个设置应该能够处理总共每秒 50 条消息(20 + 20 + 10)。然而,当使用轮询分发策略时,每个工作节点接收相同份额的消息,而不管它们的处理能力如何。如果传入消息速率持续达到每秒 50 条消息,则会出现以下问题:
- 两个较快的工作节点可以舒适地处理它们分配的约每秒 16.7 条消息的份额。
- 另一方面,较慢的工作节点难以跟上分配的每秒 16.7 条消息,导致延迟随时间增长。
见下图:

为了保持可接受的延迟并满足处理 SLA,Agoda 需要为此设置分配更多资源。
在这个例子中,他们将不得不扩展到五台机器以有效地处理每秒 50 条消息。这意味着由于低效的分发逻辑无法考虑硬件处理能力的差异,他们过度配置了两台额外的机器。
当每条消息的处理工作负载不同时,即使硬件是同构的,也会出现类似的场景。
在这两种情况下,这都会导致一些负面后果:
- 由于需要额外资源而导致更高的硬件成本。
- 资源利用率低下,一些消费者利用不足而另一些消费者负担过重。
- 管理过度配置的基础设施会增加维护开销。
轮询分发策略虽然确保消息在消费者之间平等分发,但未能考虑硬件性能和消息处理工作负载的异构性。
Agoda 的动态 Lag-Aware 解决方案
为了解决这个问题,Agoda 采用了一种动态的、lag-aware 方法来解决 Kafka 负载均衡挑战。由于消息具有非均匀工作负载,他们没有选择像加权负载均衡这样的静态平衡解决方案。
他们实施了两个主要策略:
- Lag-aware Producer
- Lag-aware Consumer
Lag-Aware Producer
Lag-aware Producer 是 Apache Kafka 中负载均衡的一种动态方法,它根据目标分区的当前 lag 信息调整消息分区。
它的工作原理如下:
- 生产者维护分区 lag 数据的缓存副本,以最小化向 Kafka broker 请求此信息的频率。
- 生产者使用 lag 数据通过自定义算法智能地将消息分发到各个分区。该算法旨在向高 lag 分区发送较少的消息,向低 lag 分区发送较多的消息。他们使用像 same-queue length 算法和 outlier detection 算法这样的算法。
- 当各分区的 lag 平衡且稳定时,lag-aware 生产者确保消息的均匀分布。
让我们考虑 Agoda 供应系统中的一个示例场景,其中内部生产者向处理器发布任务消息。
目标分区有 6 个分区,lag 分布如下:
- 分区 1:110 条消息
- 分区 2:150 条消息
- 分区 3:80 条消息
- 分区 4:400 条消息
- 分区 5:120 条消息
- 分区 6:380 条消息
在这种情况下,lag-aware 生产者会识别出分区 4 和 6 的 lag 明显高于其他分区。因此,它将调整其分区策略,向分区 4 和 6 发送较少的消息,同时向 lag 较低的分区(分区 1、2、3 和 5)发送更多消息。
通过根据当前 lag 状态动态调整消息分布,lag-aware 生产者有助于重新平衡各分区的工作负载,防止已经过载的分区进一步 lag 累积。
Lag-Aware Consumer
当多个消费者组订阅同一 Kafka 分区时,lag-aware 消费者是一种被采用的解决方案,使得 lag-aware 生产者效果降低。
处理过程如下:
- 在下游服务中(例如 Agoda 的 Processor),如果特定消费者实例检测到它在处理消息方面明显落后(即,它有高 lag),它可以自愿取消订阅该分区。此操作会触发重新平衡操作。
- 在重新平衡期间,Agoda 开发的自定义 Partition Assigner 在所有剩余的消费者实例之间重新分配分区。重新分配会考虑每个消费者的当前 lag 和处理能力,确保更平衡的工作负载。
- 为了最小化重新平衡的性能影响,Agoda 利用 Kafka 2.4 的增量协作重新平衡协议。此协议允许更频繁的分区重新分配,而不会对整体处理流程造成重大中断。
让我们用一个例子来说明这一点。
假设 Agoda 的 Processor 服务有三个消费者实例(工作节点)从分区的 6 个分区消费消息:
- 工作节点 1 负责处理来自分区 1 和 2 的消息
- 工作节点 2 处理分区 3 和 4
- 工作节点 3 处理分区 5 和 6
如果工作节点 3 碰巧在比其他工作节点更旧、更慢的硬件上运行,它可能难以跟上分区 5 和 6 中的消息涌入,导致更高的 lag。在这种情况下,工作节点 3 可以主动取消订阅该分区,触发重新平衡事件。
在重新平衡期间,自定义 Assigner 评估每个工作节点的当前 lag 和处理能力,并相应地重新分配分区。例如,它可以将分区 5 分配给工作节点 1,将分区 6 分配给工作节点 2,从而有效地减轻工作节点 3 的工作负载,直到 lag 降低到可接受的水平。

结论
总之,Agoda 与 Apache Kafka 的旅程是一个持续增长、学习和适应的过程。
通过实施诸如两步日志架构、根据用例拆分 Kafka 集群、开发健壮的监控和审计系统以及 Kafka 负载均衡等策略,Agoda 成功地管理了处理每天 1.8 万亿条事件所带来的挑战。
随着 Agoda 继续发展和壮大,其 Kafka 设置无疑将在支持公司不断扩展的需求方面发挥关键作用。这些解决方案也为其他软件开发人员在使 Kafka 适应其组织需求方面提供了很好的学习经验。
本文为学习目的的个人翻译,译文仅供参考。
原文链接:1.8 Trillion Events Per Day with Kafka: How Agoda Handles it。
版权归原作者或原刊登方所有。本文为非官方译本;如有不妥,请联系删除。