原文地址:https://kafka.apache.org/0101/documentation.html#theconsumer
消费者通过从主分区的服务器获取数据进行消费。消费者指定每次请求时日志的偏移量,然后从这个位置开启批量获取数据。消费者对位移量有绝对的控制权,这样消费者可以重新设置位移位置,并在有需要的时重新消费。
推送 vs 拉取
一个基本的问题是,我们在考虑消费者是主动从服务器那里拉去数据,还是服务器应该主动推送数据到消费者端。在这方面,kafka和传统的消息吸引设计一样,生产者推送消息到服务器,消费者从服务器拉去消息。在一些日志中心系统,像 Scribe 和 Apache Flume,使用一种特殊的推送流数据推送机制,这些方式都有利有弊,但是,在一个基于推送方式消息系统,很难处理大量的消费者,因为服务器需要控制数据的传输速率。目标是为了让消费者尽可能多消费数据;不幸的是,在一个推送系统,这意味着消费者往往被消息淹没,如果消费率低于生产速度(例如密集的服务攻击)。基于拉取的系统往往比较优雅些,消息处理只是落后,消费者在后面尽可能赶上。
使用基于拉取方式的系统还有一个好处就是容易汇集批量数据后发给消费者。基于推送的系统,要么马上发送请求,要么汇总数据后再发送,而不光下游的消费者是否能够处理得上。如果为了进一步降低延迟,这会导致缓存还没有结束时就传输单条数据过去,这样很浪费。基于拉的方式可以从当前日志位置拉去可用的消息(或者根据配置的大小)。这样能在没有引入不必要的延迟的情况下,获取到比较好的批处理性能。
基于拉取方式的系统不足的地方是如果没有任何数据,消费者就要循环检测,使用空轮询的繁忙检测方式等候数据到来。为了避免这一点,我们可以设置拉请求的参数,允许消费者请求在“长轮询”时阻塞,直到数据到达。
你可以想象一些其他从端到端的一些可能性设计。生产者把记录写入到本地日志中,服务器将从消费者拉取的数据中拉取。一种类似的储存和转发的生产者模型经常被提议。这虽然挺有趣的,但不适合有成千上万生产者的情况。在我们大规模运行数据储存系统的经验来看,成千上万的磁盘跨越多个应用并不让系统更为可靠,操作起来将会是一个噩梦。在实践中,我们发现可以创建具有很强壮的SLAs保障的,大规模的管道,并且不需要提供者有持久化能力。
消费位置
令人惊讶的是,跟踪已消耗的内容是消息传递系统的关键性能点之一。
大部分的消息系统在服务器端记录哪些消息被消费的元数据信息。那就是,消息被发送给消费者时,服务器要么在本地马上记录日志,要么等待消费者反馈后记录。这样的话相当不直观,事实上,对于一台服务器,很难理清楚这个状态到底去哪里了。因为在大部分的消息储存系统中,数据结构很难被扩展,这也依赖于编程的语义,如果服务器知道消息被消费后可以马上删除,那么就可以维持比较小的数据集。
碰巧不太明显的是,让服务器和消费者对已经消费的数据达成一致并不是一件简单的事情。如果服务器在每次数据分发出去后,马上标记消息已经被消费了,如果消费者处理消息失败了(例如宕机了),那么消息可能会丢失。为了解决这个问题,很多消息系统添加了反馈机制,用于标记消息已经被发送,而不是被消费,服务器等待消费者发送一个反馈来确认消息已经被消费。这个策略解决消息丢失的问题,但是同时也引发新的问题。首先,如果消费者已经消费了记录,但是在反馈时失败,则有可能重复消费两次。其次,是多一个来回的性能损耗,现在服务器就要为每个消息保存不同的状态(先锁定,这样不会发送第二次,然后标记为永久消费后,才能把它删除)。还有些麻烦的问题需要处理,比如消息被发送了,但是从来没有接受到反馈。
kafka使用不一样的处理方式,主题被划分成一系列有序的分区集合,每个分区在一个时刻仅被订阅分组中的一个消费者消费。这意味这每个消费者在一个分区位置就只是一个数值,用于记录下一次消息要被消费的位置。这意味着记录消费者状态的代价非常小,只是每个分区一个数值。这个状态可以定期做检查点,这使等价的消息反馈代价非常小。
这个方案还有另外的好处,消费者可以优雅地重新指定一个旧的位移位置,并重新消费数据。这个和通常的队列观念有点相悖,但是对很多消费者来说是一个很重要的特性。例如,如果消费代码有bug,并且在一些消息被消费后发现,一旦bug被修复,消费者可以重新使用这些消息。
离线数据加载
可扩展的持久性储存能力,使得消费者能定期批量把数据导入到离线系统中,如:Hadoop 或关系型数据仓库。
在hadoop的例子中,我们通过把数据分发到独立的任务集中进行并行处理,每个的单位是按服务器/主题/分区,这样可以允许很好的并发数据加载处理。Hadoop 提供任务管理,任务可以在失败四重新启动,而不用担心会重复处理数据——只需要简单从他们原来处理的位置重新开始。