Kafka Consumer
Push VS Pull
An initial question we considered is whether consumers should pull data from brokers or brokers should push data to the consumer. In this respect Kafka follows a more traditional design, shared by most messaging systems, where data is pushed to the broker from the producer and pulled from the broker by the consumer.
Kafka遵循传统设计,也是被大多数消息系统所采用的,即,producer push数据到broker,consumer从broker那里pull数据。
A push-based system has difficulty dealing with diverse consumers as the broker controls the rate at which data is transferred. The goal is generally for the consumer to be able to consume at the maximum possible rate; unfortunately, in a push system this means the consumer tends to be overwhelmed when its rate of consumption falls below the rate of production.
基于push的系统在面对不同的消费者的时候需要控制数据传输的速率这是很困难的,一般消费者都是以最大速率去消费的,不幸的是这对于push系统来说意味着当消费的速率低于生产的速率时消费者有崩溃的倾向。而基于pull的系统就有很好的属性,消费者可以简单的落后,并且在可能的时候迎头赶上。在消费者感觉快要崩溃的时候可以缓和一下,让消费者充分利用传输速率比看起来要复杂得多。
基于pull的系统的另一个优势在于broker可以将自己的数据批量的发送给消费者。而基于push的系统必须选择是立即发送请求还是累积更多的数据再发请求,而且发送以后它不知道消费者是否能很快的处理它。基于pull的系统就能够很好的解决这个问题,因为消费者总是拉取在当前日志的位置之后的所有可用的消息。
基于pull的系统有一个缺陷就是如果broker没有数据,消费者会一直循环等待,有效的繁忙的等待直到有数据到达。为了避免这个问题, 我们可以让消费者在请求的时候以“长轮询”的形式阻塞,直到有数据到达。
Consumer Position
大部分的消息系统在broker中维护关于消息是否已经被消费的元数据。因此,一个消息被分发给消费者以后broker可以理解处理或者等到消费者确认。
或许你还没有意识到让broker和consumer就消息是否已经被消息达成一致是一个很重要的问题。如果每次消息被分发出去以后broker就立即将这条消息标记为consumed,这个时候如果消费者处理消息失败了(可能是因为它宕机了或者请求超时了或者其它的原因),那么这条消息就丢失了。为了解决这个问题,许多消息系统增加一个确认特性,这就意味着在消息被发送以后只能将它们标记为sent而不是consumed,broker会等待来自消费者的确认,之后才会将这条消息标记为consumed。这个策略虽然解决了丢失消息的问题,但是又带来了新的问题。首先,如果消费者已经处理完消息,但是在它发送确认之前它失败了,那么这种情况下会导致消息被消费两次。第二个问题跟性能有关,现在broker对于每条消息必须维护多个状态(首先锁定它以至于它不会被分发第二次,然后永久的标记它为consumed以至于它肯能会被删除)。棘手的问题必须被处理,比如消息发送后没有确认。
Kafka handles this differently. Our topic is divided into a set of totally ordered partitions, each of which is consumed by exactly one consumer within each subscribing consumer group at any given time. This means that the position of a consumer in each partition is just a single integer, the offset of the next message to consume. This makes the state about what has been consumed very small, just one number for each partition. This state can be periodically checkpointed. This makes the equivalent of message acknowledgements very cheap.
Kafka对这些问题的处理不同,主题被划分为一系列有序的分区,在任意时刻,一个分区只能被订阅这个主题的每个消费者组中的一个消费者消费。这就意味着,消费者的位置在每个分区中仅仅是一个整数,这个整数就是即将要消费的下一个消息的offset(偏移量)。这使得标记关于消息是否已经被消费的状态变得很简单,每个分区仅仅一个数。这个状态可以周期性的被检查。这跟消息确认是等价的,而且也是非常廉价的。
消费者可以故意倒回到一个旧的offset去重新消费数据。当然,这一点违反了队列的公共契约,但事实上这是许多消费者的一个基本特性。例如,如果消费者的代码有bug,并且在一些消息被消费以后才发现,那么消费者可以重新消费这些消息以修复这个bug。
参考 http://kafka.apache.org/documentation/#design