Kafka以集群的方式运行,可以由一个或多个服务组成,每个服务叫做一个Broker。生产者通过网络将消息发送到Kafka集群,集群向消费者提供消息,客户端和服务端通过TCP协议通信,其架构如下图所示。Kafka提供了Java客户端,并且对多种语言都提供了支持。
主题(Topic):对一组消息的归纳。一个主题类似新闻中的体育、娱乐、教育等分类概念,在实际工程中通常一个业务一个主题。
分区(Partition):一个Topic中的消息数据按照多个分区组织,分区是Kafka消息队列组织的最小单位,可以看作是一个先入先出(FIFO)的队列。每个分区都由一系列有序的、不可变的消息组成,这些消息被连续的追加到分区中。分区中的每个消息都有一个连续的序列号叫做offset,用来在分区中唯一的标识这个消息。
在一个可配置的时间段内,Kafka集群保留所有发布的消息,不管这些消息有没有被消费。比如,如果消息的保存策略被设置为2天,那么在一个消息被发布的两天时间内,它都是可以被消费的,之后它将被丢弃以释放空间。Kafka的性能是和数据量无关的常量级的,所以保留太多的数据并不是问题。
实际上每个consumer唯一需要维护的数据是消息在日志中的位置,也就是offset。这个offset由consumer来维护:一般情况下随着consumer不断的读取消息,offset的值不断增加,但其实consumer可以以任意的顺序读取消息,比如它可以将offset设置成为一个旧的值来重读之前的消息。
每个分区在Kafka集群的若干服务中都有副本,这些持有副本的服务可以共同处理数据和请求,副本数量是可以配置的。副本使Kafka具备了容错能力:每个分区都有一个服务器作为Leader,零或若干服务器作为Follower,Leader负责处理消息的读和写,Follower则复制Leader。如果Leader宕机了,Follower中的一台则会自动成为新的Leader。集群中的每个服务都会同时扮演两个角色:作为它所持有的一部分分区的Leader,同时作为其他分区的Follower,这样整个集群就会有较好的负载均衡。
Producer将消息发布到它指定的Topic中,并负责决定发布到哪个分区。通常可由负载均衡机制随机选择分区,也可以通过特定的分区函数选择分区。使用的更多的是第二种。
发布消息通常有两种模式:队列模式和发布—订阅模式。队列模式中多个Consumer可以同时从服务端读取消息,每个消息只被其中一个Consumer读到;发布—订阅模式中消息被广播到所有的Consumer中。Consumer可以加入一个Consumer组,共同竞争一个Topic,Topic中的消息将被分发到组中的一个成员中。如果所有的Consumer都在一个组中,这就成为了传统的队列模式。如果所有的Consumer都在不同的组中,这就成为了发布—订阅模式。更常见的是,每个Topic都有若干数量的Consumer组,每个组都是一个逻辑上的订阅者,为了容错和更好的稳定性,每个组由若干Consumer组成。这其实就是一个发布—订阅模式,只不过订阅者是组而不是单个Consumer。
如下所示的Kafka集群由两台机器组成,总共有4个分区和2个Consumer组,A组有2个Consumer而B组有4个。
2.3 对比传统消息系统相比传统的消息系统,Kafka可以很好的保证消息的有序性:
传统的队列在服务器上保存有序的消息,如果多个Consumer同时从这个服务器消费消息,服务器就会以消息存储的顺序向Consumer分发消息。虽然服务器按顺序发布消息,但是消息是被异步的分发到各Consumer上的,所以当消息到达时可能已经失去了原来的顺序,这意味着并发消费将导致顺序错乱。为了避免故障,这样的消息系统通常使用“专用Consumer”的概念,其实就是只允许一个消费者消费消息,当然这就意味着失去了并发性。
在这方面Kafka做的更好。通过分区的概念,Kafka可以在多个Consumer组并发的情况下提供较好的有序性和负载均衡。将每个分区只分发给一个Consumer组,一个分区就只被这个组的一个Consumer消费,这样就可以顺序的消费这个分区的消息。因为有多个分区,依然可以在多个Consumer组之间进行负载均衡。注意Consumer组的数量不能多于分区的数量,也就是有多少分区就允许多少并发消费。