4、Kafka原理-Consumer

1、Kafka消费者原理-Consumer

1.1 Offset的维护
1.1.1 Offset的存储

在partition中,消息是不会删除的,所以才可以追加写入,写入的消息连续有序的。这种特性决定了 kafka可以消费历史消息,而且按照消息的顺序消费指定消息,而不是只能消费队头的消息。正常情况下,我们希望消费没有被消费过的数据,而且是从最先发送(序号小的) 的开始消费(这样才是有序和公平的)。

那么对于一个partition,消费者组怎么才能做到接着上次消费的位置(offset)继续消费呢?肯定要把这个对应关系保存起来,下次消费的时候查找一下。

首先这个对应关系确实是可以查看的。比如消费者组gp-assign-group-1和 ass5part (5个分区)的partition的偏移量关系,可使用如下命令査看:

java 复制代码
./kafka-consumer-groups.sh "bootstrap-server 192.168.44.161:9093,192.168.44.161:9094,192.168.44.161:9095 ---describe ---group gp-assign-group-1
  • CURRENT-OFFSET:下一个未使用的offset
  • LEO(Log End Offset):下一条等待写入的消息的offset (最新的offset + 1)
  • LAG是延迟量

注意:这不是表示一个消费者和一个Topic的关系,而是一个consumer group和topic中的一个partition的关系(offset在partition中连续编号而不是全局连续编号)。

这个对应关系存在哪里?因为所有的消费者都可以使用这个consumer group id,放在本地是做不到统一维护的,肯定要放到服务端。

Kafka早期的版本把消费者组和partition的offset直接维护在ZK中,但是读写的性能消耗太大了。后来就放在一个特殊的topic中,名字叫**_consumer_offsets,默认有 50 个分区**(offsets.topic.num.partitions 默认是 50),每个分区默认一个replication。

java 复制代码
./kafka・topics・sh ---topic  	consumer_offsets ---describe ---zookeeper localhost:2181

那么这样一个特殊的Topic怎么存储消费者组gp-assign-group-1对于分区的偏移量的?Topic里面是可以存放对象类型的value(经过序列化和反序列化)。这个Topic 里面主要存储两种对象:

GroupMetadata :保存了消费者组中各个消费者的信息(每个消费者有编号)。

OffsetAndMetadata:保存了消费者组和各个partition的offset位移信息元数据。

java 复制代码
./kafka-console-consumer.sh ---topic consumer_offsets --bootstrap-server 192.168.44.161:9093,192.168.44.161:9094,192.168.44.161:9095 -formatter "kafka.coordinator.group.GroupMetadataManager\$OffsetsMessageFormatter" ---from-beginning

__consumer_offsets大致的数据结构:

怎么知道一个consumer group的offset会放在这个特殊的Topic的哪个分区呢? 可以通过哈希取模计算得到:

java 复制代码
Math.abs("gp-assign-group-1".hashCode()) % 50;
1.1.2 如果找不到Offset

如果增加了一个新的消费者组去消费---个topic的某个partion,没有offset的记录,这个时候应该从哪里开始消费呢? 由以下参数控制:

java 复制代码
auto.offset.reset=latest
  • latest: 从最新的消息(最后发送的)开始消费。
  • earliest: 从最早的(最先发送的)消息开始消费。可以消费到历史消息。
  • none: 如果consumer group在服务端找不到offset会报错。
1.1.3 Offset的更新

消费者组的offset是保存在Broker的,但是是由消费者上报给 Broker的。并不是消费者组消费了消息,Offset就会更新,消费者必须要有一个commit (提交)的动作。消费者可以自动提交或者手动提交。由以下参数控制:

java 复制代码
enable.auto.commit=true

还可以使用这个参数来控制自动提交的频率:

java 复制代码
auto.commit.interval.ms=5000

如果我们要在消费完消息做完业务逻辑处理之后才commit,就要把这个值改成 false。如果是false,消费者就必须要调用一个方法让Broker更新offset。有两种方式:

  • consumer.commitSync()手动同步提交。
  • consumer.commitAsync()手动异步提交。

如果不提交或者提交失败,Broker的Offset不会更新,消费者下次消费的时候会收到重复消息。

2.1 消费者和消费策略
2.1.1 分区分配策略

在同一个消费者组中,一个分区只能被一个消费者消费。所以当消费者数量大于分区数量时,多余的消费者是无法消费消息的。那么如果分区数大于消费者数呢,Kafka又是如何分配的?

为了保证系统最大的利用率,Kafka的分配策略一定会遵循一个最基本的原则------平均分配,简单理解,不会有任意两个消费者消费的分区数差大于1。在此基础上,Kafka有3种分区分配方式。以8个partition分配给3个consumer为例:

(1)Range(范围)

C1:P0 P1 P2

C2:P3 P4 P5

C3:P6 P7

(2)RoundRobin(轮询)

C1:P0 P3 P6

C2:P1 P4 P7

C3:P2 P5

(3)sticky(粘滞)

粘滞的分配策略较为复杂,它的核心思想是在分区重新分配时保证最小的移动(类似Redis的一致性hash思想,实现方式不同)。

第一次分配类似轮询,结果如下:

C1:P0 P3 P6

C2:P1 P4 P7

C3:P2 P5

假设此时C2挂掉:

若按照RoundRobin,结果如下:

C1:P0 P2 P4 P6

C3:P1 P3 P5 P7

sticky结果如下(尽量保证P0 P3 P6 P2 P5不动):

C1:P0 P3 P6 P1

C3:P2 P5 P4 P7

在分区分配时是会造成性能损耗的,若采用sticky分配策略可以尽可能减少性能损耗。该思想与Redis的一致性Hash是类似的,只是实现方式不同。

2.1.2 分区重分配Rebalance

什么时候会触发分区重分配Rebalance呢?有两种情况,一种是消费者数量发生变化,一种是分区数量发生变化。Rebalance过程如下:

  1. 确定协调者coordinator,通常由集群中负载最小的Broker承担。
  2. 所有consumer向coordinator发送join group请求,确认自己是该组成员。
  3. coordinator在所有consumer中确定leader(通常是第一个)并由leader确定分区分配结果。
  4. coordinator向所有consumer发送分区分配结果。
相关推荐
独自破碎E3 小时前
怎么在RabbitMQ中配置消息的TTL?
分布式·rabbitmq
七夜zippoe3 小时前
缓存策略:从本地到分布式架构设计与Python实战
分布式·python·缓存·lfu·lru
num_killer4 小时前
小白的Spark初识(RDD)
大数据·分布式·spark
小北方城市网4 小时前
微服务架构设计实战指南:从拆分到落地,构建高可用分布式系统
java·运维·数据库·分布式·python·微服务
heartbeat..4 小时前
Spring 全局上下文实现指南:单机→异步→分布式
java·分布式·spring·context
上海锟联科技4 小时前
相干衰弱在分布式光纤声波传感(DAS)系统中的影响与抑制应用
分布式·分布式光纤传感·光频域反射·das
【D'accumulation】5 小时前
如何快速解决某些文件保存不了权限问题
kafka
johnny_hhh5 小时前
Confluent 单节点部署配置
运维·阿里云·zookeeper·kafka·centos·数据可视化