东南亚Grab如何降低Kafka流量成本?


Grab 是东南亚领先的超级应用平台,提供对消费者重要的日常服务。Grab 不仅仅是一款叫车和送餐应用程序,还在该地区提供广泛的按需服务,包括移动、食品、包裹和杂货配送服务、移动支付以及遍及 8 个国家 428 个城市的金融服务。

Grab 的实时数据流平台团队 Coban 一直围绕Kafka构建生态系统,服务于所有 Grab 垂直领域。除了稳定性和性能之外,我们的首要任务之一也是成本效率。

在本文中,我们将解释 Coban 团队如何通过让 Kafka 消费者能够从最近的复制副本获取数据,从而大幅降低 Grab 的年度数据流成本。

问题陈述
Grab 平台主要托管在 AWS 云上,位于一个区域,跨越三个可用区 (AZ)。在数据流方面,Kafka 代理和 Kafka 客户端都运行在这三个可用区。

默认情况下,Kafka 客户端仅与分区领导者通信,而分区领导者有 67% 的概率位于不同的可用区。

这是一个问题,因为我们根据AWS 的网络流量定价模型对跨可用区流量进行收费。因为这种设计,我们的跨可用区流量达到了 Kafka 平台总成本的一半。

解决方案
Kafka 2.3引入了消费者从分区副本中获取数据的能力。这为更具成本效益的设计打开了大门。

实现此功能需要 Kafka 代理和消费者具有机架意识和额外配置。我们将在以下部分中对此进行描述。

我们的旅程从升级旧版 Kafka 集群开始。我们决定将它们直接升级到 3.1 版本,以利于捕获 2.3 版本的错误修复和优化。这是一个安全的举措,因为 3.1 版本被认为稳定了近一年,而且我们预计此次升级不会产生额外的运营成本。
为了在不中断用户的情况下执行在线升级,我们将该过程分为三个阶段。

  • 第一阶段:升级 Zookeeper。所有版本的 Kafka 均由社区使用特定版本的 Zookeeper 进行测试。为了确保稳定性,我们遵循了相同的流程。升级后的 Zookeeper 将向后兼容 Kafka 的升级前版本,该版本在操作的早期阶段仍在使用。
  • 第 2 阶段:将 Kafka 升级到 3.1 版,并具有明确的向后兼容代理间协议版本 ( inter.broker.protocol.version)。在此渐进式部署过程中,Kafka 集群暂时由具有异构 Kafka 版本的代理组成,但它们可以相互通信,因为它们被明确设置为使用相同的代理间协议版本。在此阶段,我们还将 Cruise Control 升级到兼容版本,并配置 Kafka 在启动时导入更新的cruise-control-metrics-reporterJAR 文件。
  • 第三阶段:升级经纪商间协议版本。最后一个阶段使所有经纪商都使用最新版本的经纪商间协议。在逐步推出此更改的过程中,使用新协议版本的代理仍然可以与使用旧协议版本的代理进行通信。

配置
要使 Kafka 消费者能够从最近的副本获取数据,需要对 Kafka 代理和 Kafka 消费者进行配置更改。他们还需要了解自己的可用区,这是通过利用 Kafka 机架感知(1 个“机架”= 1 个可用区)来完成的。

在我们的 Kafka 代理配置中,我们已经broker.rack设置将副本分布在不同的可用区以实现弹性。我们的 Kafka Ansible 角色会自动使用在部署时从 EC2 实例的元数据中动态检索的 AZ ID 来设置它。

- name: Get availability zone ID
  uri:
    url: http://169.254.169.254/latest/meta-data/placement/availability-zone-id
    method: GET
    return_content: yes
  register: ec2_instance_az_id

请注意,我们使用 AWS AZ ID(后缀为az1、az2、az3)而不是典型的 AWS AZ 名称(后缀为1a、1b、1c),因为后者的映射在 AWS 账户之间不一致

此外,我们添加了新replica.selector.class参数,设置其值为org.apache.kafka.common.replica.RackAwareReplicaSelector,以在服务器端启用新功能。

在 Kafka 消费者方面,我们主要依赖 Coban 内部的 Golang Kafka SDK,它简化了所有 Grab 垂直行业的服务团队利用 Coban Kafka 集群的方式。我们已经更新了 SDK,以支持从最近的副本中获取数据。

我们的用户只需导出一个环境变量即可启用这一新功能。然后,SDK 会在启动时从主机的元数据中动态检索底层主机的 AZ ID,并用该信息设置一个新的 client.rack 参数。这与 Kafka 代理在部署时的做法类似。

我们还为非 SDK 消费者(即 Flink 管道和 Kafka Connect 连接器)实施了相同的逻辑。

下一步是什么?
我们已经实现了从所有 Kafka 集群和我们控制的所有 Kafka 消费者的最近副本中获取数据的功能。这包括内部 Coban 管道,以及作为我们数据流产品一部分的用户可自助服务的托管管道。

我们现在正在宣传和倡导更多用户采用这一功能。

除 Coban 外,Grab 的其他团队也在努力减少跨新西兰的流量,特别是负责 Grab 服务网格的 Sentry 团队。

注意事项:

1、端到端延迟增加
启用从最近的副本获取后,我们观察到从生产者到消费者的端到端延迟最多增加了 500 毫秒。虽然这在设计上是意料之中的,但它使得这项新功能不适合 Grab 对延迟最敏感的使用案例。对于这些使用案例,我们保留了传统的设计,即消费者直接从分区领导者处获取,即使他们位于不同的区域中心。

2、无法优雅地隔离代理
我们还验证了 Kafka 客户端在代理轮换期间的行为;代理轮换是 Kafka 的常见维护操作。我们的相应运行手册的早期步骤之一就是将要轮换的代理降级,这样它的所有分区领导者都会被排空并转移到其他代理。

在传统的架构设计中,Kafka 客户端只与分区领导者通信,因此降级代理可以优雅地将其与所有 Kafka 客户端隔离开来。这就确保了对它们的无缝维护。不过,通过从最近的副本中获取,Kafka 消费者仍能从降级的代理中消费,因为它仍在为分区跟随者提供服务。当代理因维护而有效停机时,这些消费者就会突然断开连接。为了解决这个问题,他们必须正确处理连接错误并实施重试机制。

3、潜在的负荷倾斜
我们观察到的另一个注意事项是,broker服务器的负载直接取决于消费者的位置。如果消费者在所有三个 AZ 上的分布不均衡,那么代理服务器的负载也会出现类似的偏差。有时,可以增加新的代理服务器,以支持不断增加的 AZ 负载。但是,从负载较轻的 AZ 中移除任何broker都是不可取的,因为随时都可能有更多的消费者突然迁移到那里。在其他 AZ 上使用这些额外的broker和未充分利用现有broker也会影响成本效率。