消息模型

default

Kafka 消息模型

Kafka 中的 Producer 和 consumer 采用的是 push-and-pull 模式,即 Producer 只管向 broker push 消息,consumer 只管从 broker pull 消息,两者对消息的生产和消费是异步的。

消息分发

消息存储

消息消费

Components

Kafka 中几个基本的组成可以理解为:

  • Kafka 在分类中维护一些消息源,这些被称为“主题(topic)”;

  • 我们将把消息发布到 Kafka 中的处理程序称为“生产者(producer)”;

  • 我们将订阅主题,并且处理公布出来的消息的处理程序称为“消费者(comsumer)”;

  • Kafka 可以运行起来像由一个或者多个服务器组成的集群,每个集群称为“代理(broker)”

所以,从一个比较高视角来看,生产者通过网络将消息发送给 Kafka 集群,再由 Kafka 集群将消息转发给消费者。处理过程如下图 所示: 客户端与服务端的通讯是通过简单的、高性能的、语言无关的TCP 协议来完成的。我们为 Kafka 提供了一个 Java 客户端,关于更多语言的客户端请点击这里:更多语言支持

主题与日志

首先让我们深入了解一下 Kafka 提供的高度抽象概念:主题。

主题是一个分类或者是一个可以向其发布消息的源。对于每一个主题,Kafka 集群都维护一个分区(partitioned)的日志,如下图所示:

主题剖析

每一个分区(partition)是一个排好序的,不可变的消息序列,这些信息就是那些不断追加的提交日志。每一个经过分区的消息,都被赋予一个被称为“下标(offset)”的序列 ID,这个标识 ID 被用于唯一标识在分区中的每一个消息。

Kafka 集群在在一段时间之内,将保留所有发布过的消息,无论这个消息是否已经被处理过,这个时间周期也是可配置的。例如,如果日志保留的时间被设置为两天,那么在一个消息发布之后的两天时间之内,都可以被用于进行处理,在此之后,消息将被废弃,然后释放内存。在应对不同的数据量时,Kafka 的性能非常好,几乎是一个常数,所以,即使保留非常多的数据也不是问题。

事实上,在每一个基本消费者中保留的仅有的元数据是消费者所在日志的分区,这个被称为“下标(offset)”。这个下标由消费者进行控制,通常,消费者在阅读消息时,线性增长其下标;但事实上,这个下标确实由消费者控制的,这样消费者就能以任意顺序消费消息。例如,消费者可以重置下标来再次处理一个消息。

这些特新的组合意味着 Kafka 的消费者非常独立,他们的来来去去对集群以及其他消费者不产生任何影响。例如,我们甚至可以使用命令行工具来跟踪任何主题的内容,并且可以不对其做任何改变,即使有消费者消费。

在日志服务器上的分区有几个目的。首先,他们允许将其日志做适当的切分以使其可以容纳在单台服务器上。每一个独立的分区必须适应其宿主服务器,但是一个主题可以有很多分区,这样就可以处理任意数量的数据。第二,他们可以作为并行单元来运行,及时应对一个比特的数据。

分布式

日志的分区通过 Kafak 集群上的服务器实现了分布式,通过共享的分区,每一台服务器都可以处理数据和请求。每一个分区都可以在一些可配置的服务器之间,实现无差错复制。

每一个分区都有一台主服务器,以及零台或者多台附属服务器。主服务器为分区处理所有的读取和写入请求,而附属服务器只是被动地从主服务器上复制。如果主服务器宕机,附属服务器中的一个则自动升级为新的主服务器。每台服务器都可以作为一些分区的主服务器和其他一些分区的附属服务器。所以,在一个集群之内可以很好实现负载均衡。

生产者

生产者发布数据到它所选择的主题上。生产者负责将消息指派到主题内的哪个分区上。这些可以通过简单的轮询(round-robin)来实现负载均衡,或者通过语义化的分区来实现(简单说,就是根据在消息中的一些键(key))。稍后,再重点讲解关于分区的应用。

消费者

传统上,消息有两种模型:“排队(queuing)”和“发布-订阅(publish-subscribe)”。在队列中,消费者池从服务器上读取消息,而消息则一个一个接踵而来。在“发布-订阅”中,消息被广播给所有的消费者。Kafka 提供了一个独特的消费者抽象,它可以概括前面两者直接的特性,称之为“消费者组(the consumer group)”。

消费者使用一个消费者组的名称来标注他们。每一条发布到主题上的消息都被分发到每个订阅消费者组中的一个消费者实例上。消费者实例可以在不同进程中,甚至不同的机器上。

如果所有的消费者有同一个消费者组,那么他们的工作方式就像是“队列”,通过消费者来实现负载均衡。

如果所有的消费者拥有不同的消费者组,那么他们的工作方式就像是“发布-订阅”,所有的消息都广播给所有的消费者。

通常,我们发现,主题有少数的消费者组,每一个都是一个“逻辑上的订阅(logical subscriber)”。每组都有多个消费者实例组成,这样更有利于可扩展性和容错。用消费者集群代替单一进程的用意就是在此。

相比传统的消息系统,Kafka 还可以更好地保证消息的序列。

传统队列在服务器端保持消息的有序性,当有多个消费者从消息队列中处理消息时,服务器以存储的顺序,有序地分发消息。尽管服务器是有序地分发消息,但是消息是异步地分发给消费者。所以,消息也许不是按照原来的顺序到达不同消费者。这实际上意味着,消息在并发处理时,失去了原有的有序性。消息系统经常使用一种“独家消费者(exclusive consumer)”的概念来解决这个问题,其实就是只需要一个进程来处理队列中的消息,但是,这样也就意味着不能并发处理消息。

Kafka 在这方面处理的更好。它在主题内使用分区,这个类似并发的概念,来处理这个问题。通过一个消费者进程池,Kafka 即可以提供序列上的保障又可以实现负载均衡。这是通过将主题中分区分派给消费者组中的消费者来达到这个效果,这样每一个分区只能被消费者组中的一个消费者处理。这样,我们就可以确保消费者只能是一个分区的“阅读者(reader)”,并且处理的过程是有序的。即使有多个分区,依然可以通过多个消费者实例来实现负载均衡。但是需要注意的是,消费者实例不能超过分区数量。

Kafka集群内部结

Kafka 仅提供了在一个分区的顺序,而不提供在一个主题的不同分区之间的顺序。结合分区提供分区内的排序,对于大多数应用来说,已经足够用了。尽管如此,如果你确实需要针对一个主题所有消息的总排序,那么可以通过只使用一个分区来实现,这样也意味着只有一个消费者进程。

保证(Guarantees)

从一个高级视角来看,Kafka 给出了如下几个保证:

  • 由生产者发送到特定主题分区的消息以发送的顺序追加进来。就是说,如果消息M1M2被同一个生产者发送出去,并且M1先被发送,那么M1将有一个比M2低的下标,并且更早出现在日志里。

  • 消费者实例以在日志中存储的顺序来查看消息。

  • 对于一个主题来说,如果复制因数是N,那么即使有N-1个服务器宕机,我们也可以保证不丢失任何已经提交到日志中的消息。