Kafka 是什么?
作为一个软件工程师,我常常遇到一些需要独特解决方案的挑战。举个例子,我负责维护两个服务,A 和 B。其中,服务 A 的输出能力极强,每秒钟能发出 200 个消息,而服务 B 则相对较弱,每秒钟只能处理 100 个消息。这就导致了一个问题,那就是服务 B 无法承受服务 A 的高负载,很容易被压垮。
那么,怎样才能在保证服务 B 稳定运行的同时,让其能够有效处理服务 A 的消息呢?答案是,引入一个消息队列 Kafka 来作为中间层。
消息队列 Kafka 能够承接服务 A 的高负载输出,将其转化为服务 B 能够接受的负载量,这样一来,服务 B 就能在不被压垮的情况下,稳定地处理掉所有服务 A 的消息。
这就是我经常说的一句话,“没有什么问题是加一层中间层解决不了的。如果有,那就再加一层。”在这个案例中,引入消息队列 Kafka,就是我们加的这一层中间层,将高负载的服务 A 和容易被压垮的服务 B 有效地连接了起来。
什么是消息队列
为了保护 B 服务,我们很容易想到可以在 B 服务的内存中加入一个队列。
简单来说,它实际上就是一个链表,每个链表节点都代表一个消息。每个节点都有一个序号,我们将其称为 Offset,用以标记这个消息在链表中的位置。B 服务会根据其处理能力,逐一消费链表中的消息。处理多少算多少,同时会不断地更新已处理的 Offset 值。
然而,这里存在一个问题。那些未能及时处理的消息会在内存中积压,如果 B 服务需要更新或重新启动,这些消息就会全部丢失。但解决这个问题其实并不复杂,只需将队列独立出来,变为一个单独的进程即可。这样,即使 B 服务需要重启,也不会对队列中的消息产生任何影响。
这种简易的独立队列进程,实际上就是被称为消息队列的服务。像 A 服务这样的角色,负责发送数据至消息队列,我们称之为生产者。而像 B 服务这样的角色,负责处理这些消息,我们称之为消费者。
然而,这个消息队列的设计实在过于基础,对于高性能、高扩展性以及高可用性等要求,它并未达标。接下来,让我们研究如何对其进行优化和提升。
高性能
由于B服务的性能表现不佳,导致消息队列中数据持续积压。为了提升处理效率,我们可以增加更多的消费者,这样就能提升消费速度。与此同时,我们也可以增加更多的生产者,以此提升消息队列的处理能力,从而提高整体的吞吐量。
随着生产者和消费者数量的增加,我们会发现它们会同时争抢同一个消息队列,导致未能抢到队列的一方必须等待,这显然是在浪费时间!有解决方案吗?当然有!
首先,我们可以对消息进行分类,每一类消息对应一个topic。然后,根据不同的topic新增相应数量的队列。生产者将数据按topic投递到不同的队列中,而消费者则根据需要订阅相应的topic。这样就能大大降低每个topic队列的压力,从而提升整体效率。
然而,即使经过分类,单个topic的消息量仍然可能过多。为了解决这一问题,我们可以将单个队列拆分成多个分区(partitions),每个分区由一个消费者负责处理。
这种方法大大降低了消费者之间的争抢,从而显著提升了消息队列的性能。
高扩展性
然而,随着partition数量的增加,如果所有partition都位于同一台机器上,单机的CPU和内存负载会显著增加,从而影响整体系统性能。
因此,我们可以申请更多的机器,将partition分散部署在多台机器上。每台机器代表一个broker。通过增加broker的数量,可以缓解单台机器CPU过高导致的性能问题。
高可用
在这里,我们可能会遇到一个问题。如果broker中的某一个partition挂掉,那么这个broker中所有partition的消息就都丢失了。那么,如何确保我们的系统仍然具有高可用性呢?
这个问题的解决方案其实很简单。就像你的女神在聊天软件上与多个好友保持联系一样,我们也可以为partition创建多个副本,也就是replicas。这些replicas被分为Leader和Follower。Leader负责处理与生产者和消费者的所有读写请求,而Follower的职责是同步Leader的消息。这样就可以保证即使其中一个partition发生故障,也可以通过其副本来恢复数据,从而实现高可用性。
为了进一步提高系统的稳定性,我们可以将Leader和Follower分布在不同的broker上。这样即使Leader所在的broker出现故障,也不会影响到Follower所在的broker。更重要的是,我们可以从这些Follower中选举出一个新的Leader partition来接替原来的Leader,保持消息队列的连续性和完整性。这种分布式系统设计,可以有效地保证消息队列的高可用性。
持久化和过期策略
在面对所有broker都出现故障的极端情况时,我们需要确保数据的安全。因此,我们不能仅仅依赖于内存进行数据存储,而需要将数据持久化到磁盘中。这样一来,即使所有的broker都出现故障,我们也能保证数据不会全部丢失。一旦服务重启,我们便能够从磁盘中读取数据,恢复并继续工作。这种数据持久化的方法能够大大提高我们系统的稳定性和可靠性。
然而,磁盘空间总是有限的,如果一直将数据写入磁盘,最终会导致磁盘空间不足的问题。因此,我们可以为数据设置保留策略(retention policy)。具体来说,可以根据磁盘数据的大小或消息存储的时间来清理旧数据。例如,当磁盘上的数据超过设定的大小限制,或者消息存放超过了一定的时间后,系统会自动删除这些数据。这种方式不仅可以防止磁盘空间被耗尽,还能保证系统的高效运行。
consumer group
到这里,这个消息队列似乎已经很完美了。但其实还有一个问题。按照目前的消费方式,每次新增的消费者只能从最新的消费偏移量(Offset)开始消费。如果我想让新增的消费者从某个特定的偏移量开始消费呢?这个需求听起来可能有点刁钻,但举个例子你就明白了。
即使 B 服务有多个实例,但本质上,它只有一个消费业务方,新增实例通常也是接着之前的偏移量继续消费。假设现在来了一个新的业务方,C 服务,它想从头开始消费消息队列里的数据,这时候就不能跟在 B 服务的偏移量后面继续消费了。
为了解决这个问题,我们可以为消息队列加入消费者组(consumer group)的概念。B 和 C 服务各自属于独立的消费者组,不同的消费者组维护各自的消费进度,互不干扰。这样,无论是新增的消费者实例还是新的业务方,都可以从指定的偏移量开始消费,满足不同的消费需求。
ZooKeeper
确实,组件越来越多,每个组件都有自己的数据和状态,因此需要一个统一维护这些组件状态信息的组件。于是,我们引入了 ZooKeeper 组件。ZooKeeper 会定期与 broker 通信,获取整个 Kafka 集群的状态。这使得我们能够判断某些 broker 是否宕机,以及各个消费组的消费进度。
ZooKeeper 的引入使得 Kafka 集群的管理更加高效和可靠。它不仅监控 broker 的健康状况,还负责管理消费者组的消费偏移量,确保各个组件之间的协调和同步。通过这种方式,Kafka 集群能够在复杂的分布式环境中保持稳定运行,并且可以更好地应对各种故障和状态变化。
Kafka 是什么
在这个阶段,最初那个简陋的消息队列已经发展成为一个具有高性能、高扩展性、高可用性以及持久化特性的强大消息队列系统。对,你猜对了,它就是我们经常提到的消息队列——Kafka。在此过程中,我们提到了各种概念,如 partition 和 broker 等,这些都是 Kafka 系统的重要组成部分。
如今的 Kafka,不仅仅是一个消息队列,更是一种解决分布式系统中,数据一致性、可靠传输和流量削峰填谷等问题的有效工具。它的应用已经深入到了各个系统和业务中,为我们的大数据处理、实时计算以及服务解耦等工作提供了强有力的支持。
kafka 的应用场景
消息队列作为架构中最常用的中间件之一,其使用场景广泛且功能强大,可说是架构中的"瑞士军刀"。
例如,在面对流量波动较大的上游时,我们可以使用它来进行削峰填谷,这样可以优化 cpu/gpu 的使用效率。当系统过度庞大,消息流向复杂,我们也可以使用它来解构组件,从而降低系统的耦合度。在进行如秒杀活动这样的高并发请求场景时,我们还可以借助它来保护我们的服务不被压垮,同时尽可能减少对用户体验的影响。
当然,任何事情都没有绝对的标准答案,选择哪种方案应根据实际情况而定。在进行架构设计时,我们最终所做的往往是在各种因素之间找到一个最佳的折中点。
总结
Kafka是一种消息队列系统,其中发送消息的称为生产者,接收消息的称为消费者。通过增加生产者和消费者的实例数量,我们可以提高系统的处理能力。多个消费者可以组成一个消费者组,每个消费者组都维护自己的消费进度,彼此之间不会受到影响。
Kafka将消息分为多个主题(topic),每个主题又被拆分为多个分区(partition)。每个分区都有自己的副本,并分布在不同的代理(broker)上。这种设计不仅提高了系统的处理性能,而且增强了系统的可用性和可扩展性。