Kafka相关知识点

624 阅读11分钟

消息队列的好处

(1) 解耦 生产者和消费者完全解耦开, 生产者只需要负责生产消息, 消费者在合适的时候消费消息, 两者互不影响.

(2) 异步 生产者生产完消息后, 即不需要阻塞, 也不需要轮询.

(3) 消峰 当下游系统处理速度有峰值的时候, 就可以采用消息队列, 这样上游扛住请求量生产消息, 但是下游可以自己控制消息的消费速度, 这样就将流量峰值后延了.

kafka

kafka 各部分功能

Kafka实战(二) - 摸清 Kafka 的“黑话“

  • 生产者: 负责生产消息
  • 消费者: 负责消费消息
  • broker: kafka节点, 可以理解为一个kafka服务器
  • topic: 一个话题, 消费者订阅的就是话题, 生产者生产的也是某个topic的数据
  • 分区: 每个topic可以有不同的分区, 同一topic的分区可能位于不同的broker上. 持久化数据到磁盘保存数据的.
  • 同一分区消息的先后是有顺序的, 同一topic不同分区的消息是无序的
  • 备份: kafka数据都是存储在分区中的, 每个分区的数据会备份到不同broker上, 就是说分区1在broker1上, 那么分区1的备份数据可能就放在broker2或者3上.
  • 消费者组: 多个消费者组合成一个group, 就是消费者组. 订阅topic是以消费者组为单位的

kafaka 生产者生产时如何选择分区, 消费者消费时如何选择分区?

kafka中生产者是如何把消息投递到哪个分区的?消费者又是怎么选择分区的?

生产者在生产消息时, 会指定生产到某个分区中, 不同生产者一般生产到不同的分区中, 如果不指定, 就将消息轮流放入分区

消费者在消费消息的时候, 会消费指定分区里的消息, 一个消费者可以消费多个分区, 但是同一分区不能被多个消费者消费. 每个消费者自己维护在分区里消息指针.

一个分区只会属于一个topic, 但是topic能被多个消费者组订阅, 每个消费者组消费分区消息是独立的, 互不影响的. 消息不会被消费删除, 而是只是会被读取内容而已

kafka集群如何服务注册和发现的

利用zookeeper完成的.通过创建临时节点并监听, 来得知哪些broker存活.

即kafaka集群是通过zookeeper进行集群管理的

kafka消费者如何消费消息

如何消费消息

消费者在消费消息的时候, 需要通过自己实现一个while循环, 不停的poll对应分区的消息

image.png

一次能获取多少消息

消费者采取拉的方式获取消息, 客户端会不断轮询broker上的对应分区, 如果有消息, 一次拉取多条(具体拉取的数目和条数配置, 信息大小有关), 每次轮询会阻塞一段事件, 等待消息队列是否有消息产生, 避免了过于频繁的轮询.

fetch.min.bytes

这个参数允许消费者指定从broker读取消息时最小的数据量。当消费者从broker读取消息时,如果数据量小于这个阈值,broker会等待直到有足够的数据,然后才返回给消费者.

fetch.max.wait.ms

上面的fetch.min.bytes参数指定了消费者读取的最小数据量,而这个参数则指定了消费者读取时最长等待时间,从而避免长时间阻塞。这个参数默认为500ms。

max.partition.fetch.bytes

这个参数指定了每个分区返回的最多字节数,默认为1M。

消费者心跳

session.timeout.ms: 心跳超时, 即多少秒内, 如果没有心跳连接, 就认为超时

heartbeat.interval.ms: 心跳间隔, 即每隔多少秒发送一次心跳

Kafka是通过心跳机制来控制超时,心跳机制对于消费者客户端来说是无感的,它是一个异步线程,当我们启动一个消费者实例时,心跳线程就开始工作了。

每个消费者都有个ConsumerCoordinator,而每个ConsumerCoordinator都会启动一个HeartbeatThread线程来维护心跳.

heartbeat线程则和上面提到的参数 heartbeat.interval.ms有关,heartbeat线程. 每隔heartbeat.interval.ms向broker上的coordinator发送一个心跳包,证明自己还活着。

只要heartbeat线程在 session.timeout.ms 时间内向coordinator发送过心跳包,那么group coordinator就认为当前的kafka consumer是活着的。

coordinator

coordinator出现的原因

在kafka0.9版本之前,consumer的rebalance是通过在zookeeper上注册watch完成的。每个consumer创建的时候,会在在Zookeeper上的路径为/consumers/[consumer group]/ids/[consumer id]下将自己的id注册到消费组下;然后在/consumers/[consumer group]/ids 和/brokers/ids下注册watch;最后强制自己在消费组启动rebalance。

这种做法很容易带来zk的羊群效应,任何Broker或者Consumer的增减都会触发所有的Consumer的Rebalance,造成集群内大量的调整;同时由于每个consumer单独通过zookeeper判断Broker和consumer宕机,由于zk的脑裂特性,同一时刻不同consumer通过zk看到的表现可能是不一样,这就可能会造成很多不正确的rebalance尝试;除此之外,由于consumer彼此独立,每个consumer都不知道其他consumer是否rebalance成功,可能会导致consumer group消费不正确。

基于zk的rebalance存在不可避免的羊群效应和脑裂问题,如何不用zk来协调,而是将失败探测和Rebalance的逻辑放到一个高可用的中心,那么上述问题就能得以解决;因此kafka0.9.*的版本重新设计了consumer端,诞生了这样一个高可用中心Coordinator,大大减少了zookeeper负载。

coordinator作用

coordinator主要分为两种, group Coordinator和consume Coordinator.

group Coordinator位于broer上, 每个broker都有一个, 每个group Coordinator组负责多一个consume group, 负责他们的offset位移管理和Consumer Rebalance。

consume Coordinator位于每个消费者客户单上, 唯一的作用就是向group Coordinator提交位移信息.

消费者组如何选择唯一的group Coordinator

每个消费者组都唯一对应一个group Coordinator, 保证Coordinator的单点, 那么选择的依据是什么呢?

  1. 确定consumer group位移信息写入__consumers_offsets这个topic的哪个分区。

具体计算公式:

 __consumers_offsets partition# = Math.abs(groupId.hashCode() % groupMetadataTopicPartitionCount)  

注意:groupMetadataTopicPartitionCount由offsets.topic.num.partitions指定,默认是50个分区。

  1. 该分区leader所在的broker就是被选定的coordinator

位移管理

group Coordinator拥有一个内置的topic(_consumer_offsets), 它管理的所有消费者组里的每个消费者, 就会将自己的位移提交到这里, 由group Coordinator统一管理.

Coordinator上负责管理offset的组件是Offset manager。

负责存储,抓取,和维护消费者的offsets. 每个broker都有一个offset manager实例. 有两种具体的实现:

ZookeeperOffsetManager: 调用zookeeper来存储和接收offset(老版本的位移管理)。

DefaultOffsetManager: 提供消费者offsets内置的offset管理。

通过在config/server.properties中的offset.storage参数选择。

重平衡

当消费者失去心跳后, 会触发重平衡, 重平衡会将现有的所有分区, 重新分配给所有保持心跳的消费者.分配的过程会尽量平衡每个消费者消费的分区数目

比如一个topic有100个分区,一个消费者组内有20个消费者,在协调者的控制下让组内每一个消费者分配到5个分区,这个分配的过程就是重平衡。

重平衡触发的条件

  1. 条件1:有新的consumer加入
  2. 条件2:旧的consumer挂了
  3. 条件3:coordinator挂了,集群选举出新的coordinator(0.10 特有的)
  4. 条件4:topic的partition新加
  5. 条件5:consumer调用unsubscrible(),取消topic的订阅

重平衡的缺点

因为重平衡过程中,消费者无法从kafka消费消息,这对kafka的TPS影响极大,而如果kafka集内节点较多,比如数百个,那重平衡可能会耗时极多。数分钟到数小时都有可能,而这段时间kafka基本处于不可用状态。所以在实际环境中,应该尽量避免重平衡发生。

如何避免重平衡

简单来说,会导致崩溃的几个点是:

消费者在超时时间内没有完成一次心跳,导致 rebalance。

消费者处理时间过长,导致 rebalance。

消费者心跳超时

我们知道消费者是通过心跳和协调者保持通讯的,如果协调者收不到心跳,那么协调者会认为这个消费者死亡了,从而发起 rebalance。

而 kafka 的消费者参数设置中,跟心跳相关的两个参数为:

session.timeout.ms 设置了心跳超时时间

heartbeat.interval.ms 心跳时间间隔

这时候需要调整 session.timeout.ms 和 heartbeat.interval.ms 参数,使得消费者与协调者能保持心跳。一般来说,超时时间应该是心跳间隔的 3 倍时间。即 session.timeout.ms 如果设置为 180 秒,那么 heartbeat.interval.ms 最多设置为 60 秒。

为什么要这么设置超时时间应该是心跳间隔的 3 倍时间?

因为这样的话,在一个超时周期内就可以有多次心跳,避免网络问题导致偶发失败。

消费者处理时间过长

如果消费者处理时间过长,超过了max.poll.interval.ms, 那么同样会导致协调者认为该 consumer 死亡了,为了将信息重新分配给别的消费者, 从而发起重平衡。

而 kafka 的消费者参数设置中,跟消费处理的两个参数为:

max.poll.interval.ms 每次消费的处理时间

max.poll.records 每次消费的消息数

对于这种情况,一般来说就是增加消费者处理的时间(即提高 max.poll.interval.ms 的值),减少每次处理的消息数(即减少 max.poll.records 的值)。

因此要处理好心跳超时问题和消费处理超时问题。

对于心跳超时问题。一般是调高心跳超时时间(session.timeout.ms),调整超时时间(session.timeout.ms)和心跳间隔时间(heartbeat.interval.ms)的比例。

阿里云官方文档建议超时时间(session.timeout.ms)设置成 25s,最长不超过 30s。那么心跳间隔时间(heartbeat.interval.ms)就不超过 10s。

对于消费处理超时问题。一般是增加消费者处理的时间(max.poll.interval.ms),减少每次处理的消息数(max.poll.records)。

阿里云官方文档建议 max.poll.records 参数要远小于当前消费组的消费能力(records < 单个线程每秒消费的条数 x 消费线程的个数 x session.timeout的秒数)。

什么是提交? 为什么要提交?

消费者在拉取消息的时候, 每个消费者自己维护了一个消息的偏移量offset, 每次拉取消息的时候, 根据偏移量在broker的分区中拉取消息, 因此消费者是否消费了消息, 是根据这个offset的值是多少来决定的.

当消息从broker返回消费者时,broker并不跟踪这些消息是否被消费者接收到;Kafka让消费者自身来管理消费的位移,并向消费者提供更新位移的接口,这种更新位移方式称为提交(commit)。

kafka需要每个客户端提交offset是为了记录当前分区消息的消费情况, 如果偏移量只由消费者记录的话, 当消费者挂了, 就没人知道这个分区的消费情况, 那么重平衡后的消费者, 必然会重复消费.

因此offset是客户端和broker都有一份.

(1) 消费者拉取了3条消息, 处理完了, 提交offset的时候挂了, broker没收到提交

重平衡后, 这三条消息会被新的消费者消费, 产生重复消费

(2) 消费者拉取了3条消息, 还没处理完, 先提交了offset, 然后消费者挂了

重平衡后, 这三条消息不会再被消费, 因此消息丢失了.

自动提交

一定程度避免重复消费, 不可避免消息丢失 为了不重复消费消息, kafka默认开启了自动提交的功能.

enable.auto.commit设置为true 表示开启自动提交

auto.commit.interval.ms 自动提交的间隔, 默认为5s

自动提交就会在你poll()方法拉取完消息后, 自动提交poll()拉取完后, 当前的offset(拉取100条, 就提交之前的offset+100, 无论这100条消息是否消费完).

手动提交

大多数情况下, 系统可以做到幂等性, 但是不能接受消息丢失的问题, 因此会关闭自动提交, 在所有消息全部处理完后, 才调用commitSync()来主动提交位移.

这样如果在未提交前, 消费者挂了, 这部分消息会重复被消费, 但是消费绝对不会丢失.