生产者将数据直接发送到作为分区leader的broker,而不需要任何中间路由曾。为了帮助生产者执行此操作,所有kafka节点都可以回答有关于那些服务器处于活动状态的源数据请求一级主题分区的leader在任何给定时间的位置,以允许生产者合适的指向它的请求。
客户端控制它发布消息的分区。这可以随机完成,实现一种随机负载均衡,或者通过一些语义分区功能来完成。我们通过允许用户指定要分区的关键字并使用它来散列到分区来公开用于语义分区额接口(如果需要,还有一个覆盖分区函数的选项)。例如,如果选择的关键字是用户ID,则给定用户的所有数据将会发送到同一分区。这反过来允许消费者对其消费作出地点假设。这种分区方式明确设计为允许在消费者中进行对位置敏感的处理。
批处理是效率的重要驱动因素之一,并且为了实现批处理,Kafka生产者将尝试在内存中积累数据并在单个请求中发送更大的批量。批处理可以配置为积累不超过固定数量的消息,并且等待不超过固定延迟的限制(例如64K或10ms)。这允许积累更多的字节来发送,并且在服务器上几乎没有更大的I/O操作。这种缓冲值可配置的,并且提供了一种机制来权衡少量的额外延迟以获得更好的吞吐量。
Kafka消费者通过向broker发出“fetch“请求来主导他想要消费的分区。使用者在每个请求的日志中指定其偏移量,并从该位置开始接收一块日志。因此,消费者可以对该位置进行重要控制。并且可以再需要时将其倒回以重新消费数据。
我们考虑的一个初步问题是应该让消费者从broker pull数据还是broker向消费者push数据。在这方面Kafka遵循更传统,由大多数消息传递系统共享的设计,数据从生产者push到broker再从broker pull到消费者。一些以日志为中心的系统,例如scribe和Apache Flume,遵循一种非常不同于push的路径,数据被push到下游。这两种方式各有利弊。然而,由于broker控制数据传输的速率,基于推送的系统难以和不同的消费者打交道。目标通常是消费者能以最大的可能速度消费;不幸的是,在基于push的系统中,这意味着当消费速率低于生产速率(实际上是拒绝服务攻击)时,消费者往往不堪重负,基于pull的系统具有更好的特性,消费者可以简单的落后并在可能的情况赶上。这可以通过某种退避协议来缓解,通过这个协议消费者可以表示它不堪重负了,但是获得的转移的效率以充分利用(但是从不过度利用)消费者比看起来更棘手。以前以这种方式构建的系统的尝试使我们采用了更传统的pull模型。
采用基于pull的系统的另一个优点是它有助于对发送给消费者的数据进行积极的批处理。基于push的系统必须选择立即发送请求或累计更多的数据,然后在不知道下游消费者能否立即处理它的情况下发送它。如果针对低延迟进行调整,这将导致一次发送单个消息仅用于传输最终被缓冲,这是浪费的。一个基于pull的系统设计解决了这个问题,因为消费者总是在日志中的当前位置(或者去到一些可配置的最大大小)之后拉出的所有可用消息。因此,不引入不需要的延迟时可以获得最佳批处理。