概述
本文说明了Kafka的生产者和消费者的设计原则,通过本文可以了解生产者和消费者的设计原理和思想,为更好的理解Kafka的运行机制打下基础。
生产者(Producer)的设计
生产则的设计考虑了两个方面:
- 如何实现负载均衡
- 如何提高I/O效率
负载均衡
生产者(Producer)把数据直接发送到Partition的leader所在的Broker服务器,而不经过任何的路由层。为了帮助Producer完成该任务,所有的Kafka节点都能够回答Producer发出的询问元数据(MetaData)的请求:哪些服务器是存活的?哪个服务器是这个topic的leader?
producer决定消息要发往哪个partition,partition的确定方式灵活,默认的round-robin方式来在partition间负载均衡,也可以指定一个partition function实现自定义的均衡方法。
异步发送机制提高吞吐率
可以为producer配置消息的批量send,异步send,以达到高吞吐。通过批量数据发送的配置可以有效的减少接收端的I/O。
批处理可以配置为累加到不超过固定数量的消息,并且等待不超过固定的延迟限制(例如64k或10ms)。这允许累加更多字节才进行发送,这样在服务器上大的I/O操作也不会太多。
生产者的配置和API可以在文档中找到。
消费者的设计
Kafka的Consumer(消费者)向想要消费的topic的分区(partition)的leader所在的broker发起一个”fetch”请求。每个请求都指定offset,获取到从该offset位置开始的一大块数据(log)。Consumer对这个消费位置有着非常重要的控制权(broker端只是保存消费者告知的消费位置),如果需要consumer可以重新设置offset的值(可以重复消费数据)。
推和拉(Push vs Pull)
考虑一下这个问题:消费者应该从brokers拉数据还是broker把数据推给消费者?在这一点上kafka遵从传统的设计,即数据由Producer推(push)到broker,Consumer从broker上拉(pull)数据进行消费。
一些以logging-centric的系统比如 Scribe和Apache Flume,则采用的是push-based方式将数据push给下游。这两种方式各有利弊。
消费者基于push-based的方式的缺点主要有:
* 当broker控制了数据的传输速度时,无法处理各种不同类型的消费者。
* 消费者的目标通常是能够以最大的速度消费,然而,在推动系统中,这意味着消费者在消费率低于生产率(实质上是拒绝服务攻击)时往往不堪重负。
消费者基于pull-based的方式的好处主要有:
* 轻量级,消费者不会因消息发送速率过高而被overwhelmed,即便落后了,也能在后面catch up上来
* 向消费者sent数据时可以做些激进的batching优化
基于pull-based方式的缺点在于,当broker没有数据时,消费者可能不断的轮训来等待数据的到达。为了避免这种情况,Kafka在拉取请求中有参数,允许消费者请求在“长轮询”中等待直到数据到达(并且可选地等待直到给定的字节数可用以确保大的传送大小)。
消费的位置(Consumer Position)
若消费者每消费一条记录,broker就记录一条消息,那么当消费者处理消息失败或进程死掉时,消息就可能丢失。为了解决这个问题,很多消息系统通过消费者发送确认消息的方式解决,然而,这种方式解决了消息丢失的问题,但却引入了新的问题:
* 若消费者在发送确认之前,处理信息失败,那么该消息就要被消费两次
* 第二点是性能方面的,broker必须为每一条消息保存多个状态
* 第三点:如何处理永远没有发送确认的消息
Kafka采取不同的方式:Kafka的topic被分为一组完全有序的分区,每个分区在任何给定的时间由消费者组中的一个消费者消费。
这意味着消费者在每个分区中的位置只是一个整数:下一个消息消耗的偏移量。这使得关于什么已经消耗的状态非常小,每个分区只有一个数字。可以定期检查此状态。这使得等同于消息确认非常便宜。这样设计有一个好处。消费者可以故意地回绕到旧的偏移量并重新消耗数据。
离线数据加载
可以按一个broker/topic/partition一个map的方式来组织数据加载任务,这样避免数据的重复消费。使得数据的消费可以并行进行。
总结
本文描述了Kafka的生产者和消费者的设计原则,为更好的理解Kafka的运行,这是解决实际问题的基础。