提到Apache Kafka,相信很多人再熟悉不过,从2011年问世以来,它就一直稳稳地占据分布式事件流平台事实标准的地位。卡夫卡(Kafka)的忠实粉丝——蒂姆·伯格伦(Tim Berglund)更是形象地将其赞誉为是“通用数据基底”。
Kafka之所以备受关注,很大程度上得益于Kafka强大的生态系统:一方面,Kafka Connect为Kafka与外部系统搭建起了高效连接的桥梁;另一方面,Kafka Streams这一Java流处理库,为数据处理提供了强大支持。
另外, Kafka的持续迭代能力也是加分项,最新版本升级成功实现了点对点消息传递。经过严谨的开发与全面测试,这一关键功能在Kafka 4.2版本中正式面向广大用户全面开放。
接下来,让我们先睹为快,全面剖析下事件流与消息队列这两种模式。
事件流聚焦于对无界连续数据流展开大规模、实时性的处理,其独特优势在于允许消费者根据实际需求,随时重放旧事件。具体而言,消费者应用程序会记录下Kafka成功处理的最后一条事件的偏移量(即每个主题分区中的序数位置)。一旦消费者出现终止或重启情况,它能够从最后一个提交的偏移量位置迅速恢复,继续处理所分配的分区。这种模式在众多实际应用场景中大放异彩,例如互联网广告归因、实时更新网约车状态以及信用卡欺诈监控等。而Kafka正是在事件流这一领域展现出强大的生命力,目前已有超过80%的财富100强公司选择采用Kafka来应对相关业务需求。
与之形成鲜明对比的是消息队列模式,它主要用于实现点对点通信。在这种模式下,消息通常在被消费一次后便会从队列中移除。与事件流不同,消费型应用程序能够对每条消息进行确认。这种消息模式通过确保一次性处理任务,例如向移动设备发送应用内通知、生成工资记录或调用AI模型等,有效实现了应用和服务之间的解耦。在该领域,RabbitMQ、ActiveMQ和IBM MQ等平台备受青睐。
然而,对于Apache Kafka而言,若将消息队列的使用场景强行套用,就如同“方钉子进圆孔”,存在诸多不匹配之处。究其原因,首先,“传统”的Kafka消费者组在扩展性方面受到主题分区数量的限制。更为关键的是,Kafka用户缺乏消息级别的确认语义,而这一特性对于消息队列系统协同操作队列中的消息至关重要。
正是基于上述痛点,KIP - 932《Kafka 排队》应运而生。接下来,让我们一同深入探究Kafka的消息队列实现,看看它如何成为你构建事件驱动架构过程中的得力“工具”。
扩展 Kafka 消费任务应用
传统模式下,Kafka 主题数据的并行处理能力受限于主题的分区数量。Broker(代理)会将每个分区的消费任务分配给消费者组中的单个成员。一旦消费者组的成员数量达到主题的分区数量,任何新加入的消费者都将处于空闲状态。
图 01:Kafka 的共享队列

该图示展示了订阅主题的三个消费者组实例——这意味着我们已在该主题上达到了并行处理的极限。
KIP-932 引入了一种称为“共享组”的新组类型。生产者应用写入 Kafka 数据的方式,以及 Kafka 存储数据的方式均保持不变,你的事件流用例依然可以围绕相同的主题展开。
共享组引入了一种新的协作消费模式,组内的消费者工作方式类似于消息队列系统中的消费者/订阅者。在 Broker 端,每个主题分区都有对应的共享分区,用于跟踪每条消息相对于共享组的生命周期。这使得共享消费者的规模可以突破主题分区数量的限制。
图 02:Kafka 的共享队列

该图展示了新的协作消费模型——消费者组的多个成员从单一主题分区处理数据。
这种跨分区的协作消费也意味着我们失去了“传统”Kafka 消费者的分区级处理顺序保证。这是为了扩展性所做出的权衡,但协作消费非常适用于那些吞吐量和规模优先于处理顺序的场景。
消息级确认
对于熟悉 Kafka 的开发者来说,KIP-932 的 API 会让人感到非常亲切。首先,向 Kafka 主题生产(写入)事件的方式没有任何变化。在消费端,其接口也与现有的非常相似:消费者应用程序轮询可用消息,并处理每一个生成的记录实例。这里涉及的核心类包括 KafkaShareConsumer、KafkaConsumer 和 ConsumerRecord。
现在的消费者能够逐一确认每条记录的送达情况。默认情况下,每条消息会被隐式确认为已成功处理。然而,在某些场景下(尤其是涉及错误处理或长时间运行的任务时),开发者需要更细粒度的控制权。
通过配置,代码将负责指定每条消息应如何被确认。
图 03:Kafka 的共享队列

该图详细展示了控制基于这些确认类型的消息生命周期的状态机。
只有处于某种状态的消息才能被消费者获取。当消息被取用时,其状态会切换,同时该消息的投递次数也会增加。这实际上“锁定”了该消息,防止共享组内的其他成员再次获取它。
一旦消息预计将在有限时间内被处理。如果这个“锁定”或“租约”到期,消息要么返回该状态(以便重试),要么根据消息的投递次数限制被移入该状态(不再重试)。每个消息的状态和投递次数都会在共享分区中被跟踪。这为开发者提供了内置的重试机制,如果消息处理进程需要重试,可以通过类型进行确认。
如果消息处理成功完成,该消息会以类型进行确认。这会将消息状态流转返回。
有些情况下,处理时间是不确定的。例如,消费者可能需要调用第三方或合作伙伴的 API,或者利用大型语言模型(LLM)的调用来增强消息内容。这些情况并不代表“失败”,只是处理代码需要更多时间来完成。在这种情况下,请使用类型确认消息,以重置锁定(租约)时间。
统一消息协议与基础设施
许多企业既需要事件流处理,也需要消息队列功能。这通常意味着运维团队必须同时维护和支持 Apache Kafka 以及较旧的消息队列系统。开发者也不得不在同一个应用代码库中集成不同的消息库和协议。而与此同时,高管层往往还会质疑:为什么我们要为多种消息解决方案付费?
将这些消息应用场景整合到 Apache Kafka 上,将使应用程序的开发、部署、升级和维护变得更加简单。它还能帮助消费者应用进行扩展,以满足处理消息时的需求和 SLA(服务等级协议)。
与传统的消息队列系统不同,这些“队列”中的事件依然享有我们在 Apache Kafka 中依赖的持久性和存储保证。消费者应用的开发者可以自主决定将事件作为事件流处理还是作为队列处理。
运维人员和站点可靠性工程师(SRE)通常偏爱简洁的架构(这可能是因为系统越简单,生产事故就越少)。统一这些消息平台意味着需要配置、部署和修补的系统数量减少了。这也回应了高管层的关切——降低了整体应用基础设施的总拥有成本。
Kafka 点对点消息传递功能更新,对开发者来说意味着什么?
KIP-932 为 Apache Kafka 带来了期待已久的点对点语义。该实现将队列式消费和消息级确认功能叠加在持久性、可扩展性和高吞吐量之上,使 Kafka 成为从初创企业到大型企业的关键基础设施。
对于开发团队:这意味着只需针对单一消息 API 编写应用程序,而无需同时处理多个协议。
对于运维团队:这意味着基础设施的整合与复杂度的降低。
对于整个企业:这意味着在不牺牲各个使用场景所需的具体语义的前提下,降低了总拥有成本。
KIP-932 已在 Apache Kafka 4.2 和 Confluent Cloud 中得到支持,并适用于 Confluent Platform 8.2 版本。开发者现在就可以去探索实现并开始测试基于队列的消费模式。