公众号文章:同程旅行基于Proxy的Kafka最佳实践
Apache Kafka,作为当前企业级数据流处理的首选平台,由于其高吞吐量和可扩展性而深受欢迎。
然而,随着企业数据量的爆炸性增长和业务需求的多样化,Kafka 集群面临着各种挑战,特别是在集群可用性和流量治理方面。
为了解决这些挑战,我们引入了 Kafka-Proxy 作为一个优雅地解决方案,旨在提高 Kafka 集群的稳定性和扩展性。
本文将介绍同程旅行 Kafka-Proxy 的设计、核心功能以及实施效果,展示了其在提升 Kafka 集群稳定性和扩展性方面的强大能力。
Kafka 集群面临的挑战
目前公司现有 Kafka 集群,随着业务规模的扩大,集群体量不断增大,暴露出了以下问题:
- 大集群瓶颈:由于接入的业务越来越多,流量越来越大,加上没有很好的管理,很多集群已经变得特别的大,比如单集群 Topic 数好几千个,稳定性风险比较大。
- 无集群级别的容灾能力:很多场景下如网络故障,硬件故障等预期外场景会导致单集群不可用,且没有很好的止损的手段。
- 使用痛点: 比如很多用户一直在提的重置消费进度等需求,原生 Kafka 操作起来比较重,对用户不太友好。
为了解决以上问题,我们通过设计一套 Kafka-Proxy 代理的方案来提高整个 Kafka 集群的可用性和流量治理能力,保证业务的稳定运行。
设计目标
整体的设计目标如下:
- 无缝切换集群:实现业务的平滑拆分和引流至新集群,特别是将核心业务与普通业务隔离,确保核心业务的不间断和高可用性。
- 跨中心流量监控:提供深入的流量监控功能,包括但不限于各 Topic 的流量走向和 Channel 数量、耗时、请求大小等,配备全面的报警机制以预防潜在问题。
- 在线重置消费进度:支持在无需停机的情况下,灵活调整消费进度,提高系统的适应性和恢复能力。
- 流量熔断机制:引入 Topic 级别的熔断机制,有效避免流量高峰时的系统过载,保护集群稳定运行。
- 双中心支持:实现就近生产与消费的能力,优化系统响应速度和资源利用率,特别适用于跨 Region 部署的场景。
系统设计
整体的系统设计采用外挂的方式提供服务,通过在客户端和 Broker 之间植入一个轻量级的代理层。代理层的协议与 Kafka 保持完全兼容,实现了对客户端和 Broker 的无缝对接。这种设计规避了对客户端和 Broker 的任何改动需求,既保留现有系统的完整性,又增加了新的功能支持。
代理层致力于处理大量并发连接和大量数据传输,所有这些都是为了确保我们系统的核心能力——极高的吞吐量和最小化的通信延迟,考虑到性能最大化、减少网络通信的耗时损耗,我们采用了与 Broker 同机部署的策略。
系统架构
整体架构
从图中可以看到我们的一个独特设计–Metadata 共享。通过这种设计,我们的后端可以被拆分为多个集群,而对于业务来说,他们只需要使用一个公共地址就可以访问我们的服务,无需关心他们的请求在哪个集群中被处理。每个集群都可以独立运行,处理一部分 Topic,并且通过元数据共享,各个集群之间能够互相无缝切换。这种能力在需要对某个集群进行维护或升级时尤其有用。我们可以将该集群的 Topic 无缝切换到其他集群进行处理,而业务层面完全感知不到这个过程,从而实现了真正意义上的无缝切换。
内部结构
名词解释
- Netty Server:作为服务端,负责接收客户端发出的各种请求。
- Netty Client:作为客户端,向 Broker 转发各种请求。
- Key Queue:一个关键的队列,用于存储所有活跃状态下客户端连接的 channelId,作为后续处理的索引。
DataTable:一个关键的数据结构,记录了 channelId 与其对应的 Channel 上下文信息以及请求队列,便于管理和操作。 - SendWorker:一个专门的工作线程,负责处理请求。它从 Key Queue 中获取一个 channelId,随后从 DataTable 中找到对应的请求队列,并从中取出请求进行处理。
- Acks0SendWorker:一个专门的工作线程,负责处理生产者设置 acks=0 的请求。它从请求队列中取出相应的请求数据进行处理。
- ChannelManager:负责维护 客户端->Proxy 以及 Proxy->Broker 之间的 Channel 映射关系,保证数据传输的正确性和高效性。
- Cache Manager:负责定期拉取集群信息、Topic 信息、全局配置等重要的元数据信息缓存到本地,以保持系统数据的最新状态和高效运行。
- Processor:一个统一的请求处理策略处理器,它在请求抵达 Broker 之前或之后执行特定的处理操作,以优化数据处理流程和提高效率。
工作流程
- 启动和监听
Proxy 启动后,自动监听指定端口,准备接收客户端的连接请求。 - 请求接收与解析
一旦客户端成功建立连接,Proxy 开始接收来自客户端的请求。通过解析 ByteBuf,Proxy 获取到 ApiKey 和 acks 标识,以决定如何处理这些请求。 - 基于 acks 值的处理逻辑
对于 acks=0 的请求:这类请求直接被放入 acks0Queue,由 Acks0SendWorker 线程处理,实现单向发送请求至 Broker 而不等待响应。
对于其他类型的请求:这类请求根据 channelId 不同被分配到对应的 dataQueue,由 SendWorker 线程处理。处理过程包括通过特定的 Processor 进行处理后发送至 Broker,并等待响应。 - 请求与响应匹配
对于需要返回响应的请求,Proxy 通过 requestId 确保发送至 Broker 的每个请求与其响应匹配。匹配成功的话,Proxy 将 Broker 返回的数据写回到发出请求的客户端 Channel 中,完成数据传输。 - 异常处理和系统稳定性
若发现请求与响应不匹配,或者遇到其他异常情况,则断开 客户端->Proxy 以及 Proxy->Broker 的连接,迫使客户端重新连接,断开连接并重新建立连接是一种保障系统稳定运行的机制,确保数据传输的连续性和准确性。