一、消费者和消费者组
1.简介
2.应用场景:
(1)如果生产者写入消息的速度比消费者读取的速度快怎么办呢?这样随着时间增长,消息堆积越来越严重。对于这种场景,我们需要增加多个消费者来进行水平扩展。
(2)如果一个应用想要获取所有消息那么就可以将整个应用配置成一个消费者组,如果需要分环境,比如线上和预发都需要消费一遍那么就需要将线上和预发配置成两个消费者组
3.消息投递模式
4.工作流程:
- 连接 ZK 集群,拿到对应 topic 的 partition 信息和 partition 的 leader 的相关信息
- 连接到对应 leader 对应的 broker
- consumer 将自己保存的 offset 发送给 leader
- leader 根据 offset 等信息定位到 segment(索引文件和日志文件)
- 根据索引文件中的内容,定位到日志文件中该偏移量对应的开始位置读取相应长度的数据并返回给 consumer
二、订阅主题(topic)与分区(partition)
下一章会详解主题和分区
nsumer.subscribe(Collections.singletonList("topic"));
也可以通过正则的方式订阅
consumer.subscribe(Pattern.compile("topic-.*"));
如果想直接订阅某个主题下的分区就可以使用assign()方法,如下是订阅了topic-demo主题 中分区编号为0的分区
consumer.assign(Arrays.asList(new TopicPartition("topic-demo", 0)));
consumer.unsubscribe();
还有其他的一些订阅和取消订阅的方式就略过了、
三、反序列化
在生产者一章中提到过序列化,反序列化顾名思义就是和序列化对应的,将字节数组转换回原来的消息,
四、消息消费
1.poll()方法
2. offset偏移量(位移)
Kafka 的每一条消息都有一个偏移量属性,记录了其在分区中的位置,偏移量是一个单调递增的整数。消费者通过往一个叫作 _consumer_offset 的特殊主题发送消息,消息里包含每个分区的偏移量。如果Kafka 消费者查找不到所记录的消费位移或者位移越界时, 就会根据消费者客户端参数 auto offset.reset 的配置来决定从何处开始进行消费,这个参数的默认值为“latest ”,表 示从分区末尾开始消费消息。
如果消费者一直处于运行状态,那么偏移量就没有什么用处。不过,如果有消费者退出或者新分区加入,此时就会触发再均衡。完成再均衡之后,每个消费者可能分配到新的分区,而不是之前处理的那个。为了能够继续之前的工作,消费者需要读取每个分区最后一次提交的偏移量,然后从偏移量指定的地方继续处理。 因为这个原因,所以如果不能正确提交偏移量,就可能会导致数据丢失或者重复出现消费,比如下面情况:
(1)如果提交的偏移量小于客户端处理的最后一个消息的偏移量 ,那么处于两个偏移量之间的消息就会被重复消费;
(2)如果提交的偏移量大于客户端处理的最后一个消息的偏移量,那么处于两个偏移量之间的消息将会丢失。
3.偏移量的提交
我们把将消费位移存储起来(持久化)的 动作称为“提交’ ,消费者在消费完消息之后需要执行消费位移的提交。提交分为两种:
(1)自动提交
kafka默认的就是自动提交,
实现方式很简单只需要将消费者的 enable.auto.commit 属性配置为 true 即可完成自动提交的配置。 此时每隔固定的时间,消费者就会把 poll() 方法接收到的最大偏移量进行提交,提交间隔由 auto.commit.interval.ms 属性进行配置,默认值是 5s。
使用自动提交是存在隐患的,假设我们使用默认的 5s 提交时间间隔,在最近一次提交之后的 3s 发生了再均衡,再均衡之后,消费者从最后一次提交的偏移量位置开始读取消息。这个时候偏移量已经落后了 3s ,所以在这 3s 内到达的消息会被重复处理。可以通过修改提交时间间隔来更频繁地提交偏移量,减小可能出现重复消息的时间窗,不过这种情况是无法完全避免的,或者如果在未提交偏移量时消费者服务器挂了也会造成重复消费的问题。基于这个原因,Kafka 也提供了手动提交偏移量的 API,使得用户可以更为灵活的提交偏移量。
自动提交会漏消费造成丢失数据的情况,如果想避免可以设置为手动提交
(2)手动提交偏移量
用户可以通过将 enable.auto.commit 设为 false,然后手动提交偏移量。基于用户需求手动提交偏移量可以分为两大类:
手动提交当前偏移量:即手动提交当前轮询的最大偏移量;
手动提交固定偏移量:即按照业务需求,提交某一个固定的偏移量。
而按照 Kafka API,手动提交偏移量又可以分为同步提交和异步提交。
同步提交:使用consumer.commitSync()提交,如果某个提交失败,同步提交还会进行重试,这可以保证数据能够最大限度提交成功,但是同时也会降低程序的吞吐量
异步提交:使用consumer.commitAsync()异步提交可以提高程序的吞吐量,异步提交存在的问题是,在提交失败的时候不会进行自动重试,实际上也不能进行自动重试。假设程序同时提交了 200 和 300 的偏移量,此时 200 的偏移量失败的,但是紧随其后的 300 的偏移量成功了,此时如果重试就会存在 200 覆盖 300 偏移量的可能。同步提交就不存在这个问题,因为在同步提交的情况下,300 的提交请求必须等待服务器返回 200 提交请求的成功反馈后才会发出。基于这个原因,某些情况下,需要同时组合同步和异步两种提交方式。
如果一切正常,使用commitAsync()方法来提交。这样速度更快,而且即使这次提交失败,下一次提交很可能会成功。如果直接关闭消费者,就没有所谓的下一次提交了。在异常处理后使用commitSync()提交。
提交特定的偏移量:
上述几种方式的提交偏移量的频率与处理消息批次的频率是一样的,如果poll()方法返回一大批数据,为了避免因再均衡引起的重复处理整批消息,可以在调用commitAsync()和commitSync()方法时传进去希望提交的分区和偏移量的map
具体实现代码参考https://blog.csdn.net/m0_37809146/article/details/91126212
五、指定位移消费seek()
public void seek(TopicPartition partition,long offset)
六、控制或关闭消费
七、再均衡
- public void onPartitionRevoked(Collection partitions):此方法会在消费者停止消费消费后,在重平衡开始前调用。
- public void onPartitionAssigned(Collection partitions):此方法在分区分配给消费者后,在消费者开始读取消息前调用。
我们也可以在消费组停止前将记录和偏移量存到db中,然后在分配分区之后调用seek() 方法将存入库中的偏移量设置进去!这样可以防止消费者发生在均衡时造成的消息丢失或者消息重复!
八、消费者拦截器
与生产者拦截器类似,略过
九、线程安全
十、重要的消费者参数
1.fetch .min .bytes 和fetch .max.bytes
2.fetch.max.wait.ms
3.max.patition.fetch.bytes
4.max.poll.records
5.receive.buffer.bytes和send bu er.bytes
设置 Socket 输入/接收消息缓冲区( 接受窗口)大小,
6.isolation.level
十一、消费者常见问题:
1.丢失消息:
先commit再处理消息。如果在处理消息的时候异常了,但是offset 已经提交了,这条消息对于该消费者来说就是丢失了,再也不会消费到了
解决方案:手动提交并且同步提交
2.消费到重复消息
网络拥塞导致 producer 误以为发送错误,导致重试,从而产生重复数据,可以通过幂等性配置避免。
3.消费到的数据乱序
参数配置 max.in.flight.requests.per.connection = 1 ,但同时会限制 producer 未响应请求的数量,即造成在 broker 响应之前,producer 无法再向该 broker 发送数据。