4.4 The Producer
Load balancing
生产者将数据直接发送到作为分区leader的broker,而不需要任何中间路由曾。为了帮助生产者执行此操作,所有kafka节点都可以回答有关于那些服务器处于活动状态的源数据请求一级主题分区的leader在任何给定时间的位置,以允许生产者合适的指向它的请求。
客户端控制它发布消息的分区。这可以随机完成,实现一种随机负载均衡,或者通过一些语义分区功能来完成。我们通过允许用户指定要分区的关键字并使用它来散列到分区来公开用于语义分区额接口(如果需要,还有一个覆盖分区函数的选项)。例如,如果选择的关键字是用户ID,则给定用户的所有数据将会发送到同一分区。这反过来允许消费者对其消费作出地点假设。这种分区方式明确设计为允许在消费者中进行对位置敏感的处理。
Asynchronous send
批处理是效率的重要驱动因素之一,并且为了实现批处理,Kafka生产者将尝试在内存中积累数据并在单个请求中发送更大的批量。批处理可以配置为积累不超过固定数量的消息,并且等待不超过固定延迟的限制(例如64K或10ms)。这允许积累更多的字节来发送,并且在服务器上几乎没有更大的I/O操作。这种缓冲值可配置的,并且提供了一种机制来权衡少量的额外延迟以获得更好的吞吐量。
4.5 The Consumer
Kafka消费者通过向broker发出“fetch“请求来主导他想要消费的分区。使用者在每个请求的日志中指定其偏移量,并从该位置开始接收一块日志。因此,消费者可以对该位置进行重要控制。并且可以再需要时将其倒回以重新消费数据。
Push vs. pull
我们考虑的一个初步问题是应该让消费者从broker pull数据还是broker向消费者push数据。在这方面Kafka遵循更传统,由大多数消息传递系统共享的设计,数据从生产者push到broker再从broker pull到消费者。一些以日志为中心的系统,例如scribe和Apache Flume,遵循一种非常不同于push的路径,数据被push到下游。这两种方式各有利弊。然而,由于broker控制数据传输的速率,基于推送的系统难以和不同的消费者打交道。目标通常是消费者能以最大的可能速度消费;不幸的是,在基于push的系统中,这意味着当消费速率低于生产速率(实际上是拒绝服务攻击)时,消费者往往不堪重负,基于pull的系统具有更好的特性,消费者可以简单的落后并在可能的情况赶上。这可以通过某种退避协议来缓解,通过这个协议消费者可以表示它不堪重负了,但是获得的转移的效率以充分利用(但是从不过度利用)消费者比看起来更棘手。以前以这种方式构建的系统的尝试使我们采用了更传统的pull模型。
采用基于pull的系统的另一个优点是它有助于对发送给消费者的数据进行积极的批处理。基于push的系统必须选择立即发送请求或累计更多的数据,然后在不知道下游消费者能否立即处理它的情况下发送它。如果针对低延迟进行调整,这将导致一次发送单个消息仅用于传输最终被缓冲,这是浪费的。一个基于pull的系统设计解决了这个问题,因为消费者总是在日志中的当前位置(或者去到一些可配置的最大大小)之后拉出的所有可用消息。因此,不引入不需要的延迟时可以获得最佳批处理。
Consumer Position
令人惊讶的是,跟踪已经消费的内容是消息传递系统的关键特征之一。
大多数消息传递系统保留有关在已经在broker上消费的消息的元数据,也就是说,当消息被分发给消费者时,broker要么立即在本地记录该事实,要么等待来自消费者的确认。这是一个相当直观的选择,实际上对于单个服务器来说,并不清楚这个状态去到哪里。由于在许多消息传递系统中用于存储的数据结构规模很小,这也是一个实用的选择——因为代理知道消费了什么,他可以立即删除它,保持小的数据量。
可能并不明显的是让broker和消费者就消费的内容达成一致并不是一个微不足道的问题。如果broker在每次通过网络分发消息的时候立即记录消息,那么如果消费者未能处理消息(比如因为它崩溃了或者请求超时或其他原因)消息就丢失了。为了解决这个问题,许多消息传递系统添加了一个确认功能,这意味着消息在发送后只被标记为已发送未消费;broker等到来自消费者的特定确认才小才将消费记录确认为已消费。这个策略解决了消息丢失的问题,但是会产生新问题:首先,如果消费者处理了消息但是在发送确认之前失败了,那么消息就会被消费两次。第二个问题是性能问题,此时broker对于每个消息必须保存多个状态(首先锁定它以便不发出第二次,然后将其标记为永久消费了以便可以删除)。这些棘手的问题必须被处理,例如如何处理已发送但是未确认的消息。
Kafka处理的方式不同。主题被分解成为一组完全有序的分区,每个分区在任意给定时间由每个消费者订阅消费者组中的一个消费者来消费。这意味着每个分区的使用者的位置只是一个整数,即下一个消息偏移量。这使得被消费的状态非常小,每个分区仅有一个数字。可以定期检查这个状态,这使得确认消息的消耗非常简单。
这个决定有一个附带好处。消费者可以故意回退到旧的偏移量并重新使用数据。这违反了队列的通用约定,但是对于许多消费者来说,这是一个必不可少的功能。例如,如果消费者代码有错误并且在消费了某些消息后发现,消费者可以再修复错误后重新使用这些消息。
离线数据加载
可扩展的持久性允许消费者只有定期消费,例如批量数据加载,定期将数据批量加载到离线系统(例如Hadoop或者关系型数据库)中。
在Hadoop的情况下,我们通过将负载分配到各个映射任务来并行化数据负载,每个节点/主题/分区组合一个,在加载允许完全并行。Hadoop提供任务管理,失败的任务可以在没有重复数据的危险下重新启动——他们只是从原始位置重新启动。
网友评论