一个流处理平台有三个关键功能:
Kafka通常被用于两类应用:
为了了解Kafka如何进行这些工作,下面从底层开始挖掘和探索Kafka的能力。
首先介绍一些概念:
Kafka有以下四个核心API:
kafka-apis.png
在Kafka中,每一个客户端和服务器的连接都以一种简单的,高性能的,语言无关的TCP协议完成。这个协议的版本能够向后维护来兼容旧版本。我们提供了一个Java客户端,但是客户端其实在很多语言中都可用。
我们首先深入Kafka为一串记录提供的核心概念——主题。 一个主题是给被发布的记录的类别或者提名的名称。Kafka中的主题总是多重订阅的,意思是说,一个主题能有零个,一个,或者多个消费者来订阅往里面写入的数据。 对于每一个主题,Kafka集群维持着一个向下面这样的分区日志:
log_anatomy.png
每个分区都是一个有序,不可变的一列记录,这些记录被连续地加入到有组织的提交日志。分区中的记录每个都有指派一个有序id号被称为“偏移量(offset)”,在分区中唯一标识记录。 Kafka集群一直保存着所有发布的记录——无论它们是否被消费——用配置的保持时间。例如,如果保留时间设置为2天,那么一个记录被发布后的两天内,它都是可以被消费的,之后被废弃来释放空间。Kafka的性能在数据大小方面是恒定的,因此长时间存储数据不是问题。
log_consumer.png
事实上,基于每个消费者维持的源数据是该消费者在日志中的偏移量或者位置。这个偏移量由消费者控制:通常来说消费者读取数据的时候会线性移动其偏移量。但是,事实上,由于该位置由消费者控制,那么它能按照任何自己的喜好的顺序消费记录。例如,消费者能够重置较旧的偏移量来重新处理过去的数据,或者跳转到最近的记录,从“现在”开始消费。 这些功能的组合意味着Kafka消费者是非常轻量的——他们来去对集群和其他消费者都没什么影响。例如,能用命令行工具来"tail"任何主题的内容而无需更改任何现有使用者所消耗的内容。 日志中的分区有多种用途。首先,它们允许日志扩展到超出适合单个服务器的大小。每个单独的分区都必须适合托管它们的服务器,但是一个主题可能有很多个分区,因此它可以处理任意数量的数据。其次,它们充当了并行性的单元——这种用处多一点。
日志分区分布在Kafka集群上面,每个服务器处理数据和请求来实现分区的共享。每个分区都在可配置数量的服务器上进行复制,以实现容错。 每个分区都有一个服务器充当“领导者”,零个或者多个服务器充当“追随者”。领导者处理分区的所有读取和写入请求,而追随者自动被动地复制领导者。如果领导者挂了,追随者中的一个就会自动成为领导者。每个服务器都充当某些分区的领导者和其他分区的追随者,因此,负载在集群中能得到很好的平衡。
Kafka MirrorMaker为集群提供了异地备份支持。使用MirrorMaker,可以跨多个数据中心或者云端复制消息。可以在主动/被动方案中使用它来进行备份和回复,或者在主动/主动方案中将数据防止在离用户较近的地方,或者支持数据的位置要求。
生产者将数据发布到它们选择的主题。生产者负责选择将哪个记录分配到主题中的哪个分区。可以以轮询的方式完成,来实现负载均衡,或者根据一些语义分区函数(例如基于记录中的某些键)来完成。多数分区的使用在一秒钟内完成!
消费者用消费者组名称来标记自己,并且发布到主题上的每个记录都被传递到订阅了消费者组中的一个消费者实例中。消费者实例可以存在在单独的进程或者单独的机器上。 如果所有的消费者实例都有相同的消费者组,那么记录将有效地在消费者实例上进行负载均衡。 如果所有的消费者实例有不同的消费者组,那么每个记录都会广播到所有的消费者进程。
consumer-groups-2.png
两个服务器的Kafka集群,托管四个分区(P0-P3),包含两个消费者组。消费者组A有两个消费者实例,B组有四个。 然而,更常见的是,我们发现主题只有少量的消费者组,每个“逻辑订阅者”有一个(逻辑订阅者)。每个组由很多具有可伸缩性和容错的消费者实例构成。这不过是发布-订阅模式,其中订阅者是消费者集群而不是单个进程。 在Kafka中实现消费的方式是通过在消费者实例上划分日志中的分区,以实现每个实例在任何时间点都是分配的“公平分配”的独占消费者。维护组中成员的过程由Kafka协议动态处理。如果新实例加入到组中,他们将从该组的其他成员接管一些分区,如果实例死亡,它的分区将会分发给其他实例。 Kafka仅提供了分区中记录的总顺序,而不是主题中不同分区之间的顺序。对于大多数应用程序而言,按照分区排序和按照键划分数据的能力相结合就够了。但是,如果你需要对记录进行总排序,可以使用仅包含一个主题的分区来实现,但是这将意味着每个消费者组只有一个消费者进程。
可以将Kafka部署为多租户解决方案。通过配置哪些主题可以生产或者消费数据来启用多租户。也有一些操作支持配额。管理员可以定义和强制指定配额,以控制客户端使用的资源。更多相关信息,请参阅安全性文档。
高级别的Kafka提供了一下保证:
Kafka的流概念和传统企业消息系统比起来怎么样呢? 传统意义上的消息有两个模型:队列和发布-订阅。队列中,消费者池可以从服务器中读取,每个记录都转到其中一个;发布-订阅中,记录被广播到每一个消费者。这两种模型的都有长短处。队列的长处就是它允许在多个消费者实例上划分数据处理,从而对处理进行扩展。不幸的是,队列模式不能被多重订阅——一旦一个进程把数据读走了,就没了。发布-订阅模式允许广播数据到多个线程,但是没发对处理进行缩放,因为每个消息都被发送到了每个订阅者。 Kafka中消费者组的概念概括了这两个概念。队列方面消费者组允许将处理划分成一组进程(消费者组的成员)。发布-订阅模式方面,Kafka允许将消息广播到多个消费者组。 Kafka的模型的优点在于每一个主题都有这两个特征——又能弹性处理又能多重订阅——不需要二选一。 和传统消息系统相比,Kafka的顺序有更强的保证。 传统的队列在服务器上按顺序保留记录,如果多个消费者从队列中消费,服务器就按照记录存放的顺序发放记录。然而,尽管服务器按顺序处理记录,记录是异步发送到消费者的,所以它们(记录)到达不同的消费者的顺序可能是不同的。这实际上意味着在并行消费的情况下,记录的顺序丢失了。消息系统通常通过一个“独占消费者”的概念来解决这个问题,该概念只允许一个进程从队列中消费,但是当然这意味着处理中没有并行性了。 Kafka做的更好。通过主题中具有的并行性的概念+分区,Kafka既能保证顺序性,又能在消费者线程池中保证负载均衡。这是通过将主题中的分区分配给消费者组中的消费者来实现的,这样每个分区仅由该分区中的一个消费者使用。这样我们能确保消费者是这个分区的唯一读者,并且是按顺序消费数据的。由于有很多分区,这样仍然可以平衡许多消费者实例的负载。但请注意,消费者组中的消费者数量不能超过分区。
任何允许发布与消费消息解耦的消息的消息队列实际上充当了实时消息的存储系统。不同之处在于Kafka是一个非常优秀的存储系统。 写入Kafka的数据将写入磁盘并进行了复制以容错。Kafka允许生产者等待确认,这样直到写入操作被完全复制之前都不被认为是完成的,并且一直操作存在即使写入的服务失败。 磁盘结构的Kafka很好地使用了缓存——无论服务器上游50KB还是50TB数据,Kafka的表现都是一样的。 由于谨慎对待存储操作并允许客户端控制其读取位置,因此Kafka可以被认为是一种专用于高性能,低延迟提交日志存储,复制和传播的分布式文件系统。
仅仅对数据流进行读,写和存储是不够的,目的在于使对流的实时处理成为可能。 在Kafka中,流处理器是指从输入主题获取的连续数据流,对此进行一些处理,和生产输出主题的连续数据流的任何内容。 例如,一个零售应用可能会接受销售和发货的输入流,并输出重新排序后的流和根据这些数据计算出来的价格调整。 可以用生产者和消费者API直接进行简单处理。但是,对于更复杂的转换,Kafka提供了完全集成的Stream Api。这允许构建执行非平凡的处理应用程序,这些应用程序可以计算流的聚合,或将流连接在一起。 这个工具有助于解决此类应用程序面临的难题:处理无序数据,在代码变更的时候重新处理输入,执行有状态的计算,等等。 Streams API构建在Kafka提供的核心原语之上:它用生产者和消费者API作为输入,用Kafka作为有状态储存,并在流出器实例之间使用相同的组机制来实现容错。
把消息传递,存储和流处理组合在一起看起来很不寻常,但它对于Kafka作为流处理平台来说很有必要。 像HDFS这样的分布式文件系统允许存储静态文件用于批处理。引人注意的是,像这样的系统允许存储和处理过去的历史数据。 传统的企业消息系统允许处理订阅后到达的未来消息。以这种方式构建的系统在数据到达时处理未来数据。 Kafka结合了这两种功能,这种组合对于Kafka作为流应用和作为数据流管线的应用程序平台至关重要。 通过组合存储和低延迟订阅,流应用能以相同的方式处理过去和未来的数据。也就是说单个应用程序可以处理历史存储的数据,而不是在它达到能处理的最后一条未来到达的数据之后结束。这是归入批处理和消息驱动程序的流处理的一半概念。