一、Kafka 集群面临的挑战
目前公司现有 Kafka 集群,随着业务规模的扩大,集群体量不断增大,暴露出了以下问题:
大集群瓶颈:由于接入的业务越来越多,流量越来越大,加上没有很好的管理,很多集群已经变得特别的大,比如单集群 Topic 数好几千个,稳定性风险比较大。无集群级别的容灾能力:很多场景下如网络故障,硬件故障等预期外场景会导致单集群不可用,且没有很好的止损的手段。使用痛点: 比如很多用户一直在提的重置消费进度等需求,原生 Kafka 操作起来比较重,对用户不太友好。为了解决以上问题,我们通过设计一套 Kafka-Proxy 代理的方案来提高整个 Kafka 集群的可用性和流量治理能力,保证业务的稳定运行。
二、设计目标
整体的设计目标如下:
1、无缝切换集群:实现业务的平滑拆分和引流至新集群,特别是将核心业务与普通业务隔离,确保核心业务的不间断和高可用性。
2、跨中心流量监控:提供深入的流量监控功能,包括但不限于各 Topic 的流量走向和 Channel 数量、耗时、请求大小等,配备全面的报警机制以预防潜在问题。
3、在线重置消费进度:支持在无需停机的情况下,灵活调整消费进度,提高系统的适应性和恢复能力。
4、流量熔断机制:引入 Topic 级别的熔断机制,有效避免流量高峰时的系统过载,保护集群稳定运行。
5、双中心支持:实现就近生产与消费的能力,优化系统响应速度和资源利用率,特别适用于跨 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 之前或之后执行特定的处理操作,以优化数据处理流程和提高效率。
七、工作流程
1、启动和监听
Proxy 启动后,自动监听指定端口(默认为 19092),准备接收客户端的连接请求。
2、请求接收与解析
一旦客户端成功建立连接,Proxy 开始接收来自客户端的请求。通过解析 ByteBuf,Proxy 获取到 ApiKey 和 acks 标识,以决定如何处理这些请求。
3、基于 acks 值的处理逻辑
对于 acks=0 的请求:这类请求直接被放入 acks0Queue,由 Acks0SendWorker 线程处理,实现单向发送请求至 Broker 而不等待响应。
对于其他类型的请求:这类请求根据 channelId 不同被分配到对应的 dataQueue,由 SendWorker 线程处理。处理过程包括通过特定的 Processor 进行处理后发送至 Broker,并等待响应。
4、请求与响应匹配
对于需要返回响应的请求,Proxy 通过 requestId 确保发送至 Broker 的每个请求与其响应匹配。匹配成功的话,Proxy 将 Broker 返回的数据写回到发出请求的客户端 Channel 中,完成数据传输。
5、异常处理和系统稳定性
若发现请求与响应不匹配,或者遇到其他异常情况,则断开 客户端->Proxy 以及 Proxy->Broker 的连接,迫使客户端重新连接,断开连接并重新建立连接是一种保障系统稳定运行的机制,确保数据传输的连续性和准确性。
八、特性
无缝切换集群
无缝切换集群是一个高效而周到的过程,它允许 Topic 在同 Region 下不同 AZ 之间的 Kafka 集群进行平滑转移。然而,需要注意的是,在我们追求快速上线的初期版本中,对于在老集群上未被消费的数据,我们做出了一个取舍--它们可能会丢失。这个过程充分利用了元数据共享机制,以确保数据切换过程的连续性和一致性。在未来,我们正在考虑利用 Kafka 的工具和新版本的特性,以实现无数据丢失和无感知的切换。
流程:
1、初始设置:首先,在管理平台中选定需要切换的 Topic,并进行切换集群的操作。系统会自动检查目标集群是否存在该 Topic。如果不存在,会根据当前集群的分区和副本数量,在目标集群中创建相应的 Topic。
2、元数据同步:Proxy 会定时从数据库中拉取最新的 Topic 元数据信息,并将其缓存本地,以确保准确性和及时性。
3、流量切换:Proxy 根据配置的目标集群执行流量切换操作。如果目标是同一个集群,则不需要任何操作。若目标集群不同,则会进行 Channel 双断(客户端->Proxy,Proxy->Broker)。一旦中断,客户端会重新发起请求以获取元数据。此时,Proxy 根据 Topic 的元数据信息,向客户端返回目标集群的节点信息,使客户端能够向目标集群进行生产和消费操作。
就近生产消费
就近生产消费的核心在于显著降低数据传输延迟,进而提高整体处理效率。此机制允许 Topic 在遵守地域界限的同时,通过客户端的 IP 地址进行智能匹配,确保数据流向客户端所在 AZ 的集群。该机制主要分为固定模式和就近模式:
1、目标集群配置:一旦 Topic 的元数据明确指定了目标集群,系统便会将流量直接导向同 Region 下的该目标集群。
2、默认集群匹配:如果 Topic 未配置目标集群,流量则通过客户端 IP 的智能匹配,定向至同 Region 下同 AZ 内的默认集群。
3、兜底机制:若上述两种情况均不适用,为了维护服务连续性,流量将保留在当前集群。
流程:
1、初始设置:通过管理平台上为 Topic 设置必要的配置信息,如工作模式和目标集群等。
2、元数据同步:Proxy 会定时从数据库中拉取最新的 Topic 元数据信息,并将其缓存本地,以确保准确性和及时性。
3、请求处理:Proxy 根据客户端的 IP 地址识别 Region 和 AZ。依据 Topic 的配置信息,Proxy 判断是否需要进行流量转移。如果需要,将执行与无缝切换集群相同的流量转移流程,保证生产消费活动就近进行,从而优化了响应时间和系统效率。
不停机重置消费进度
在某些情况下,需要重置消费进度,比如:
1、当数据积压过多,会增加从 I/O 的拉取压力。
2、对于流量较大的业务,当其自身服务或其依赖出现问题时,可能会积压部分数据,此时,他们更关注的是实时的数据。
在引入 Proxy 之前,面临需要重置消费进度时,涉及的消费组下所有消费者必须全部暂停或者更换消费组。这不仅中断了数据流的连续处理,还要求维护团队迅速介入,减少停机时间。显然,这种情况对于数据处理效率和团队的响应能力是一个挑战。
引入 Proxy 后,情况有了极大的改善。即使在进行消费进度重置时,也可以保证消费者的持续运行,实现真正的无缝重置。
流程:
1、执行重置:通过管理平台为目标消费组(groupId)设置需要重置的 Offset,会同步等待 Proxy 加载到最新元数据且断开与 Broker 的连接后,触发重置的命令,一旦重置成功后,移除 groupId,Proxy 会重新加载到最新元数据。
2、元数据同步:Proxy 会定时从数据库中拉取标记为需要重置 Offset 的 groupId 列表。
3、请求处理:在处理心跳(Heartbeat)和偏移提交(OffsetCommit)的请求时,Proxy 的处理器(Processor)首先检查请求中的 groupId 是否位于待重置消费进度列表中。如果是,Proxy 层面直接给出一个默认响应,并断开与 Broker 的连接;否则,请求正常转发至 Broker。
注意:重置消费进度期间客户端正常消费,不能提交 Offset,重置完成后以服务端的 Offset 为准,未提交 Offset 这批数据会重复消费,客户端需要做好幂等。
生产消息熔断
关键目标:在处理海量数据时,我们的首要任务是保持系统的稳定运行。当面临由于某个流量较大的业务可能对系统稳定性产生影响时,我们采用了一项关键措施:对特定 Topic 实行生产消息熔断。
熔断机制的核心:这项措施的要点在于,一旦某个 Topic 的消息生产请求触发熔断条件时,Proxy 将不再将这些请求发送给 Broker,Proxy 层面直接给出一个默认响应。这样做的主要目的是为了预防系统在过载状态下的故障,以此确保整个 Kafka 集群的稳定性。
流程:
1、初始设置:通过管理平台为特定的 Topic 配置生产消息熔断设置。
2、元数据同步:Proxy 会定时从数据库中拉取标记为生产消息熔断 Topic 列表。
3、请求处理:在处理生产(Produce)请求时,Proxy 的处理器(Processor)首先检查请求中的 Topic 是否位于生产消息熔断名单中。如果是,Proxy 层面直接给出一个默认响应返回给客户端,从而避免了对 Broker 的请求压力;否则,请求正常转发至 Broker。
消费消息熔断
关键目标:在处理海量数据时,某些服务或其依赖出现问题可能会导致大量数据积压,从而造成较大的 I/O 压力,并可能影响系统的稳定性。为了应对这种情况,我们采用了一项关键措施:对特定的 Topic 执行消费消息熔断。
熔断机制的核心:这项措施的要点在于,一旦某个 Topic 的消息消费请求触发熔断条件时,Proxy 将不再将这些请求发送给 Broker,Proxy 层面直接给出一个默认响应。这样做的主要目的是为了预防系统在过载状态下的故障,以此确保整个 Kafka 集群的稳定性。
流程:
1、初始设置:通过管理平台为特定的 Topic 配置消费消息熔断设置。
2、元数据同步:Proxy 会定时从数据库中拉取标记为消费消息熔断 Topic 列表。
3、请求处理:在处理消费(Fetch)请求时,Proxy 的处理器(Processor)首先检查请求中的 Topic 是否位于消费消息熔断名单中。如果是,Proxy 层面直接给出一个默认响应返回给客户端,从而避免了对 Broker 的请求压力;否则,请求正常转发至 Broker。
九、落地收益
得益于这套高效的 Proxy,我们通过元数据的共享,在客户端和 Broker 不做任何改动的情况下,就成功实施了对超大规模 Kafka 集群的精细化管理。基于业务线的需求,成功拆分出了多个专用集群,并顺利完成了 Topic 的正确分流,实现了显著的落地收益,主要体现在集群资源层面和流量治理层面两个方面:
1、集群资源层面
集群精准定位服务和效率提升:通过将一个大型集群拆分成多个专用集群,我们实现了服务的精准定位,大幅提升了处理效率和资源利用率。这种策略不仅使得每个集群能够根据特定的业务需求进行优化,还提高了整体的运维效率。
更强的服务稳定性和容错能力:同样通过将一个大型集群拆分成多个专用集群,显著提高了服务的可用性和容错性。即使某个集群发生故障,我们的服务仍然可以通过其他正常运行的集群继续提供,这大大保证了业务的连续性。
2、流量治理层面
流量监控与行为识别:新增的流量监控功能有效地帮助我们识别出客户端在使用过程中的不当行为,例如未进行消息压缩、单条消息发送以及不恰当的参数配置等。这些不规范的操作不仅降低了消息处理的效率,还增加了系统的负担。
客户端参数最佳实践指南:为了帮助客户端更高效地传输消息,我们内部制定并输出了一版客户端参数设置的最佳实践指南。这份指南详细说明了如何根据不同的业务需求合理配置参数,包括优化消息压缩方式、合理设定消息批量发送大小以及选择适当的确认机制(acks)等,确保了消息传递的效率和稳定性。
故障隔离和系统稳定性:我们将不同业务线的流量隔离到各自的专用集群中,可以有效避免一个业务线的故障影响到其他业务线。这样的设计能够提高系统的稳定性和可靠性,减少故障的传播范围,通过增强的可观测性,我们可以更快地识别并隔离这些故障,进一步保证了各个集群能够独立地保持稳定运行。
以下是几个具体的优化案例,以及优化后的显著效果:
发送批量调整:(调整参数:linger.ms、batch.size)
消息压缩+批量调整:(compression.type、linger.ms、batch.size)
通过这两个优化案例,我们可以看出优化后交互次数减少,在一个较大的批次中发送更多的消息,对于同样数量的消息,整体网络开销会降低,同时压缩效率通常会更高。
通过采取这一系列的优化措施,我们不仅显著提升了系统的整体性能和稳定性,还实现了资源的最大化利用,为企业节省了大量的成本。这种全方位的改进,不仅从技术层面提升了系统的运行效率,而且从经济效益上为企业带来了实质性的收益。
十、未来展望
面向未来,我们致力于进一步提升 Proxy 的自动化能力和测试广度,以及实现无缝的版本升级,具体计划如下:
自动化故障转移机制:面对目前需要手动介入的双中心故障转移机制,我们正在开发高级自动化脚本,目标是通过简单的操作即可实现快速故障转移。这个改动可以显著提升我们应对突发事件的效率,保障业务连续性与系统稳定,以迅速响应并最小化任何潜在影响。
拓展测试的深度和广度:Proxy 的稳定性和兼容性对于提供优质服务的重要性。因此,我们计划扩大测试范围,特别是加强对各种客户端版本兼容性的测试。这样做将提高我们识别和解决问题的能力,确保向业务提供更稳定、更可靠的服务。
平滑升级 Kafka 版本:借助 Proxy 本身的强大功能,我们希望实现 Kafka 版本的无缝升级。这意味着业务可以在无感知的情况下顺利升级,极大降低了升级过程中的运维压力和风险。这标志着我们在简化运维操作和提高系统可维护性方面取得的关键进展。
通过这些策略和改进措施,我们希望 Proxy 不仅能够提供更为高效、稳定的消息传递服务,同时也能在应对未来挑战和满足业务需求方面展现出更大的灵活性和创新能力。
最后
公司内部使用的 Kibana+ClickHouse 搭建的日志解决方案已经开源,内部的 Nginx 日志,业务日志等多种 case 已全部切换到 CKibana,少量成本支撑着千万/s 的日志量。
CKibana链接:GitHub - TongchengOpenSource/ckibana: Visualizing data in ClickHouse using native Kibana.
欢迎 Star,欢迎体验。
作者丨YXiao
来源丨公众号:同程艺龙技术中心(ID:tcyanfa)
dbaplus社群欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn