Kafka设计(5)消费者

参考文档: http://kafka.apache.org/documentation/#design

消费者

Kafka消费者通过向期望消费的分区leader发送“fetch”请求来进行工作。消费者在每个请求中指定日志偏移量并接收到日志偏移量后的数据块。消费者由此可以显式控制日志位置并在需要时倒回去重复消费数据。

推送vs.拉取

我们考虑的最初问题是消费者应该从kafka拉取数据还是kafka应该推送数据到消费者。在这方面,Kafka采用了传统的、大多数消息系统共享的方案,数据被生产者推送到kafka,被消费者从kafka拉取。一些以日志为中心的系统,如Scribe和Apache Flume,采用了不同的基于推送的方案将数据推送到下游。两种方法各有利弊。然而,基于推送方案的系统难以应对多种多样的消费者,因为推送时kafka控制了数据传输速率。消费者的目标通常被认为是最大可能速率进行消费,不幸的是,在推送系统中这意味着在消费速率落后于生产速率时(本质上是一种拒绝服务攻击)消费者将被压垮。基于拉取机制的系统有更好的特性,消费者可以简单的落后于生产者并在可能是赶上生产者。通过一些补偿协议可以让消费者感知自己过载以减轻推送机制的问题,但要让消费者全力工作但又不能过量比看起来难得多。先前以这种模式构建系统的尝试让我们选择了更传统的拉取模型。

拉取系统的另一个优势是让其积极地批量处理数据并发送给消费者。推送系统必须选择要么立即发送要么积累更多数据延后发送但却不知道下游消费者能否立即处理完成。如果为了低延迟优化,推送系统会导致每次只发送一个消息,只为了数据立即被传送到缓冲区,这太浪费了。拉取系统可以解决这个问题,消费者总是拉取从当前日志位置开始的所有数据(或者配置的最大数据量),这样就在不带来非必要延迟情况下提供最优的批处理。

天真的拉取系统有一个缺陷,如果kafka没有数据,消费者可能会不断循环的轮询,以有效的紧张等待数据的到来。为了避免这点,有参数可以让消费者阻塞长轮询直到数据到来(同样可以选择配置等待指定的可用数据量以便加大传输数据量)。

在端到端只能是拉取的时候,你可以想象其他可能的设计。生产者将在本地写入本地日志,kafka将像消费者拉取数据一样从生产者拉取数据。类似的“store-and-forward”生产者类型经常被提及。这很迷人但我们感觉不是很适合几千个生产者的目标用例。我们在上规模的持久化数据系统经验让我们觉得在系统中跨多个应用(生产者)处理几千磁盘并不能让事情更加可靠并可能成为待处理的梦魇。在实践中,我们发现可以在高服务等级协议(SLA)下大规模运行一个管道,不需要生产者端的持久化。

消费位置

令人惊奇的是,记录消费位置成为消息系统的关键性能指标。

大多数消息系统记录已消费的消息的元数据。当消息被分发到消费者,消息系统要么在本地立刻记录要么等待消费者的确认。这是相当直观的选择,实际上在单机器服务上并不清楚消息的消费状态趋势(PS:被单个消费者消费成功还是消费失败)。由于在很多消息系统中使用的数据结构可伸缩性(PS:基于数据量大小的可伸缩性)很差,这是很自然的选择,因为消息系统知道消费过的消息可以立即删除,以保持低数据量。

可能不太明显的是,消息系统和消费者就消息是否消费完成达成一致是重要的问题。如果消息系统在每次将消息通过网络发出后立即记录消息“consumed”,一旦消费者处理消息失败(消费者宕机、请求超时等等原因)消息将会在丢失。为了解决这个问题,很多消息系统增加了确认功能,这表示消息在发出时只被标记为“sent”而不是“consumed”,消息系统等待消费者特定的确认后才标记消息为是“consumed”。这个策略修复了丢失消息问题,但带来了新的问题。

  • 多次消费:如果消费者进程处理了消息但在发送确认时失败,消息将被消费两次
  • 性能问题:消息系统必须记录每个独立消息的多个状态(首先锁定消息让消息不会被第二次发出,然后将消息标记为永久消费以便消息能被移除)。

这些狡猾的问题必须被处理,比如消费已发送但未收到确认时要如何处理。

Kafka采用不同的处理方式。我们的topic被拆分为一组完全顺序的分区(PS:每个分区内保持顺序),每个分区在任意给定时间都只能被每个订阅消费者组中的唯一一个消费者消费。这意味着每个分区中消费者的位置是一个整形,表示下个待消费消息的偏移量。这让已消费状态需记录的数据量变得很小,每个分区只需一个数字。状态可以定期检查。这让等效的消息确认变得很轻。

这个实现还有一个好处。消费者可以谨慎的绕回到老的偏移量并重新消费数据。这违反了队列的一般契约,但对于很多消费者是必须的功能。例如:如果消费者代码有bug,bug在已经消费了一些数据后才被找到,消费者可以在bug修复后重新消费这些消息。

问题:消费状态记录在消费者端,如果消费者宕机,如何找回消费状态?

离线数据加载

可伸缩性的存储允许只定期性消费的消费者使用,例如批量数据加载,其周期性批量数据加载到离线系统(如Hadoop或者关系型数据仓库)。

在Hadoop场景下,我们通过分割负载到独立的map任务实现并行数据加载,每个map任务对应一个节点、Topic、分区组合,这将让加载完全并行。Hadoop提供任务管理,失败的任务可以重启并不用担心数据重复——任务从原始位置重启。

稳定关系

稳定关系(Static membership)是为了提升流应用的可用性,消费者组和其他应用基于组平衡协议构建。平衡协议依靠组协调器搜集组成员的实体id。如果这些生成的id是短暂的,在成员重启或者重新加入组将改变。对于基于消费者模式的应用,在管理操作(例如:代码部署、配置更新、周期性重启,这些操作将重启消费者组成员)执行时动态关系将导致有大比例的任务重新分配到不同的实例。对于大量有状态的应用程序,在开始处理消息前搅乱任务将导致消费者需要很长时间恢复他们的本地状态,将导致应用部分或者整体不可用。受到这个观察的启发,Kafka组管理协议允许组成员提供持久的实体id。组成员基于这些id保持不变,因此不会触发再平衡。

如果想要使用稳定关系:

  • 升级kafka集群和客户端应用到2.3或者更高,并确保升级的kafka使用的inter.broker.protocol.version为2.3或者更高;
  • 在一个组内的每个消费者实例上设置配置ConsumerConfig#GROUP_INSTANCE_ID_CONFIG为不同的值;
  • 对于Kafla流应用,为每个Kafka流实例设置唯一的ConsumerConfig#GROUP_INSTANCE_ID_CONFIG就够了,和实例使用的线程数量无关。

如果kafka版本低于2.3,但在客户端设置了ConsumerConfig#GROUP_INSTANCE_ID_CONFIG,应用将探测kafka版本并抛出UnsupportedException异常。如果你意外的为不同实例配置了重复的id,kafka的围栏机制将通过抛出org.apache.kafka.common.errors.FencedInstanceIdException异常来通知重复id的客户端立即关闭。更多信息,见文档

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值