Kafka学习笔记之入门篇
消息
Kafka的数据单元被称为消息。消息由字节数组组成。消息可以有一个可选的元数据,也就是键,键也是一个字节数组。当消息以一种可控的方式写入不同的分区时,会用到键。最简单的例子就是为键生成一个一致性散列值,然后使用散列值对主题分区进行取模,为消息选取分区。这样可以保证具有相同键的消息总是被写到相同的分区上。
消息批次
为了提高效率,消息被分批次写入Kafka。批次就是一组消息,这些消息属于同一个主题和分区。如果每一个消息都单独穿行于网络,会导致大量的网络开销,把消息分成批次传输可以减少网络开销。不过,这要在时间延迟和吞吐量之间做出权衡:批次越大,单位时间内处理的消息就越多,单个消息的传输时间就越长。批次数据会被压缩,这样可以提升数据的传输和存储能力,但要做更多的计算处理。
消息格式
对于Kafka来说,消息不过是晦涩难懂的字节数组,所以有人建议用一些额外的结构来定义消息的内容,让它们更易于理解,比如像JSON、XML、Apache Avro和Google Protocolbuf等。
主题(Topic)
Kafka的消息通过主题进行分类,主题就好比数据库的表,或者文件系统里的文件夹。
分区
主题可以被分为若干个分区,消息以追加的方式写入分区,然后以先入先出的顺序读取,个人认为可以把分区理解成队列。要注意,由于一个主题一般包含几个分区,因此无法在整个主题范围内保证消息的顺序,但可以保证消息在单个分区内的顺序。Kafka通过分区来实现数据冗余和伸缩性,分区可以分布在不同的服务器上,也就是说,一个主题可以横跨多个服务器,以此来提升比单个服务器更强大的性能。
生产者
概览
生产者创建消息。默认情况下,生产者把消息均匀的分布在主题的所有分区上,而并不关心特定消息会被写到哪个分区。不过也可以把消息写到指定的分区,这通常是通过消息键和分区器来实现的,分区器为键生成一个散列值,并将其映射到指定的分区上。生产者也可以使用自定义的分区器。生产者发送消息示意图如下:
我们从创建一个ProducerRecord对象开始,ProducerRecord对象需要包括目标主题和要发送的内容,我们还可以指定键或分区。
接下来,数据被传给分区器。如果之前在ProducerRecord对象里指定了分区,那么分区器什么都不做,直接把指定的分区返回;如果没有指定分区,那么分区器会根据ProducerRecord对象的键来选择一个分区。选好分区以后,生产者就知道该往哪个主题和分区发送这条记录了。紧接着,这条记录被添加到一个记录批次里,这个批次里的所有消息会被发送到相同的主题和分区上。
如果消息成功写入Kafka,就返回一个RecordMetaData对象,它包含了主题和分区信息,以及记录在分区里的偏移量;如果写入失败,则会返回一个错误,生产者在收到错误之后会尝试重新发送消息,几次之后如果还是失败,就返回错误信息。
发送消息方式
发送消息主要有以下三种方式:
- 发送并忘记
把消息发送给服务器,但并不关心它是否正常到达。大多数情况下,消息会正常到达,因为Kafka是高可用的,而且生产者会自动尝试重发。不过,使用这种方式有时候也会丢失一些消息。 - 同步发送
我们使用send()方法发送消息,它会返回一个Future对象,调用get()方法进行等待就可以知道消息是否发送成功。 - 异步发送
我们使用send()方法发送消息,并指定一个回调函数,服务器在返回响应时调用该函数。
消费者
消费者和消费者群组
消费者读取消息。消费者可以订阅一个或多个主题。消费者通过检查消息的偏移量来区分已经读取过的消息。偏移量是另一种元数据,它是一个不断递增的整数值。在给定的分区里,每个消息的偏移量都是唯一的。
消费者是消费者群组的一部分,也就是说,会有一个或多个消费者共同读取一个主题。群组保证每个分区只能被一个消费者使用。如图所示群组中,有3个消费者同时读取一个主题,其中的两个消费者各自读取一个分区,另外一个消费者读取其他两个分区。消费者与分区之间的映射通常被称为消费者对分区的所有权关系。
除了通过增加消费者来横向伸缩单个应用程序外,还经常出现多个应用程序从同一个主题读取数据的情况。每个应用程序可以获取到所有的消息,而不是其中的一部分。只要保证每个应用程序有自己的消费者群组,就可以让它们获取到主题所有的消息。不同于传统的消息系统,横向伸缩Kafka消费者和消费者群组并不会对性能造成负面影响。
分区再均衡
当一个新的消费者加入群组或者当一个消费者挂掉或者主题添加了新的分区时,会发生分区重分配。分区的所有权从一个消费者转移到另外一个消费者,这样的行为被称为再均衡。
订阅主题
消费者订阅主题时可以使用正则表达式来匹配主题,如果有人创建了新的主题,并且主题的名字与正则表达式匹配,那么会立即触发一次再均衡。
消息轮询
消息轮询是消费者API的核心,通过一个简单的轮询向服务器请求数据。一旦消费者订阅了主题,轮询就会处理所有的细节,包括群组协调、分区再均衡、发送心跳和获取数据。轮询不只是获取数据那么简单。在第一次调用新消费者的poll()方法时,它会负责查找GroupCoordinator,然后加入群组,接受分配的分区。如果发生了再均衡,整个过程也是在轮询期间进行的。当然心跳也是从轮询里发出去的。所以,我们要确保在轮询期间所做的任何处理工作都尽快完成。
提交和偏移量
每次调用poll()方法,总是返回由生产者写入Kafka但还没有被消费者读取过的记录。消费者可以使用Kafka来追踪消息在分区里的位置(偏移量)。我们把更新分区当前位置的操作叫作提交。
那么消费者是如何提交偏移量的呢?消费者往一个叫作_consumer_offset的特殊主题发送消息,消息里包含每个分区的偏移量。再均衡发生的时候,偏移量就起到了作用,因为完成再均衡之后,每个消费者可能分配到新的分区,而不是之前处理的那个。为了能够继续之前的工作,消费者需要读取每个分区最后一次提交的偏移量,然后从偏移量指定的地方继续处理。
如果提交的偏移量小于客户端处理的最后一个消息的偏移量,那么处于两个偏移量之间的消息就会被重复处理,如图:
如果提交的偏移量大于客户端处理的最后一个消息的偏移量,那么处于两个偏移量之间的消息将会丢失,如图:
自动提交
将enable.auto.commit设置为true,那么每过5s,消费者会自动把从poll()方法接收到的最大偏移量提交上去。提交时间间隔由auto.commit.interval.ms控制,默认值是5s。与消费者里的其他东西一样,自动提交也是在轮询里进行的。消费者每次在进行轮询时会检查是否该提交偏移量了,如果是,那么就会提交从上一次轮询返回的偏移量。
使用自动提交无法避免再均衡导致的消息重复消费问题,不过时间间隔越短,重复的消息也越少。
在使用自动提交时,每次提交偏移量时消费者并不知道具体哪些消息已经被处理了,所以在再次调用之前最好确保所有当前调用返回的消息都已经处理完毕(在调用close()方法之前也会进行自动提交)。一般情况下不会有什么问题,不过在处理异常或提前退出轮询时要格外小心。
手动提交
同步提交
把auto.commit.offset设置为false,让应用程序决定何时提交偏移量。commitSync()在成功提交或碰到无法恢复的错误之前会一直重试,使用commitSync()提交偏移量最简单也最可靠。commitSync()将会提交由poll()返回的最新偏移量,所以在处理完所有记录后要确保调用了该方法。手动提交也无法避免再均衡发生时消息重复的问题。
commitSync()有一个不足之处,在broker对提交请求作出回应之前,应用程序会一直阻塞,这样会限制应用程序的吞吐量。我们可以通过降低提交频率来提升吞吐量,但如果发生了再均衡,会增加重复消息的数量。
异步提交
commitAsync()是异步提交的方法,支持回调。commitAsync()不会重试,因为在它收到服务器响应的时候,可能有一个更大的偏移量已经提交成功。
同步和异步组合提交
一般情况下,针对偶尔出现的提交失败,不进行重试问题不大,因为如果提交失败是临时问题导致的,那么后续的提交总会有成功。但如果这是发生在关闭消费者或再均衡前的最后一次提交,就要确保能够提交成功。这时我们可以使用组合提交:
- 如果一切正常,我们使用commitAsync()提交。这样速度更快,而且即使这次提交失败,下一次提交很可能会成功。
- 如果直接关闭消费者,就没有所谓的“下一次提交了”。使用commitSync()方法会一直重试,直到提交成功或发生无法恢复的错误。
提交特定的偏移量
提交偏移量的频率与处理消息批次的频率是一样的。但如果想要更频繁地提交该怎么 ?如果poll()方法返回大量数据,为了避免因再均衡引起的重复处理整批消息,想要在批次中间提交偏移量该怎么办?这种情况可以给commitSync()或commitAsync()方法传参,参数是分区和偏移量的map。
Broker和集群
一个独立的Kafka服务器被称为broker,broker是集群的组成部分,每个集群都有一个broker同时充当了集群控制器的角色(自动从集群的活跃成员中选举出来)。控制器负责管理工作,包括将分区分配给broker和监控broker。在集群中,一个分区从属于一个broker,该broker被称为分区的首领。一个分区可以分配给多个broker,这个时候会发生分区复制。如果有一个broker失效,其他broker可以接管领导权。
保留消息
保留消息(在一定期限内)是Kafka的一个重要特性。Kafka broker默认的消息保留策略是这样的:要么保留一段时间(比如7天),要么保留到消息达到一定大小的字节数(比如1GB)。当消息数量达到这些上限时,旧消息就会过期并被删除,所以在任何时刻,可用消息的总量都不会超过配置参数所指定的大小。主题可以配置自己的保留策略,可以将消息保留到不再使用它们为止。
参考资料
- 《Kafka权威指南》