(二)Kafka生产者与消费者

(一) 生产者Producer

生产者客户端由两个线程协调运行。其中主线程创建消息,并经过拦截器、序列化器、分区器作用后缓存到消息累加器; 消息累加器中的ProducerBatch是一个双端队列,消息添加时从尾部进入,Sender读取消息时从头部取出。ProducerBatch包含链多个ProducerRecord,这样使字节的使用更加紧凑,同时发送时减少了网络请求的次数以提升整体的吞吐量。

RecordAccumulator缓存的大小可以通过生产者客户端参数Buffer.memory配置,默认值为32MB。如果生产者发送消息的速度超过发送到服务器的速度,则会导致生产者空间不足,这个时候KafkaProducer的send方法要么被阻塞,要么抛出异常,这个取决于max.block.ms的配置,默认为60000,即60秒。如果生产者客户端要向很多分区发送消息,则可以适当增加buffer.memory,以提高整体的吞吐量。

在Sender线程将请求发送kafka之前,还会将请求保存到InFlightRequests中,其形式是Map<NodeId, Deque<Request>>, NodeId表示节点的id,这个map它的主要作用是缓存了已经发出去但还没有收到响应的请求,InFlightRequests提供配置参数来限制每个连接的最大缓存数,超过该值后就不能再向该连接发送请求,除非缓存的请求收到了响应。通过比较Deque<Request>的size与这个参数的大小进行比较来判断这个连接是否已经堆积了很多未响应的消息。果真如此的话,说明这个Node节点负载较大或者网络连接有问题,再继续向其发送请求会增大请求超时的可能。

leastLoadedNode是所有Node中负载最小的那一个,选择这个Node发送请求可以使请求尽快发出,避免因网络拥塞等异常而影响发送的进度。通常用于元数据请求、消费者组播协议的交互。

在向一个主题发送消息时,KafkaProducer要将此消息追加到指定主题的某个分区所对应的leader副本之前,首先需要知道分区的数量,然后经过计算得出目标分区,还要知道目标分区的leader副本所在的broker节点的地址、端口信息之后才能建立连接,最终才可以把消息发送到kafka。

元数据是指Kafka集群的元数据,这些元数据记录了集群有什么主题、主题有哪些分区、每个分区的leader副本分配在那个节点,follower副本分配在哪些节点上,哪些副本在AR 、ISR等集合中,集群中有哪些节点,控制器节点又是哪一个等等信息。

当客户端中没有需要使用的元数据信息时,比如没有指定的主题信息,或者超过metadata.max.age.ms 时间没有更新元数据都会引起元数据的更新操作。客户端参数metadata.max.age.ms的默认值为300000,即5分钟。元数据的更新由Sender线程向leastLoadedNode发送MetadataRequest请求。

image.png

                                                    生产者客户端架构

生产者配置参数:

1.acks

acks=1。默认值即为1。生产者发送消息之后,只要分区的leader副本成功写入消息,那么它就会收到来自服务端的成功响应。如果消息无法写入leader副本,比如在leader 副本崩溃、重新选举新的 leader 副本的过程中,那么生产者就会收到一个错误的响应,为了避免消息丢失,生产者可以选择重发消息。但是如果leadr 副本已经返回ACK给生产者之后挂掉,那么消息可能会出现丢失。

acks=0。生产者发送消息之后不需要等待任何服务端的响应。acks 设置为 0 可以达到最大的吞吐量。

acks=-1或acks=all。生产者在消息发送之后,需要等待ISR中的所有副本都成功写入消息之后才能够收到来自服务端的成功响应。在其他配置环境相同的情况下,acks 设置为-1(all)可以达到最强的可靠性。但是容易出现消息重复消费的情况,比如Leader 副本已经与follower完成了同步,但是正好这个时候挂掉,而生产者又没有收到ACK响应,之后生产者就会往新选举的leader重新发送刚才的消息。对于之前是follower而后被选举为leader的这个节点,就容易出现消息重复。

2.max.request.size

这个参数用来限制生产者客户端能发送的消息的最大值,默认值为 1048576B,即1MB。

3.retries和retry.backoff.ms

retries参数用来配置生产者重试的次数,默认值为0,即在发生异常的时候不进行任何重试动作。消息在从生产者发出到成功写入服务器之前可能发生一些临时性的异常,比如网络抖动、leader副本的选举等,这种异常往往是可以自行恢复的,生产者可以通过配置retries大于0的值,以此通过内部重试来恢复

retry.backoff.ms这个参数的默认值为100,它用来设定两次重试之间的时间间隔,避免无效的频繁重试.

4.compression.type

这个参数用来指定消息的压缩方式,默认值为“none”,即默认情况下,消息不会被压缩。该参数还可以配置为“gzip”“snappy”和“lz4”。对消息进行压缩可以极大地减少网络传输量、降低网络I/O,从而提高整体的性能。消息压缩是一种使用时间换空间的优化方式,如果对时延有一定的要求,则不推荐对消息进行压缩。

5.connections.max.idle.ms

指定在多久之后关闭限制的连接,默认值是540000(ms),即9分钟。

6.linger.ms

指定生产者发送 ProducerBatch 之前等待更多消息(ProducerRecord)加入ProducerBatch 的时间,默认值为 0。生产者客户端会在 ProducerBatch 被填满或等待时间超过linger.ms 值时发送出去。增大这个参数的值会增加消息的延迟,但是同时能提升一定的吞吐量。

7.receive.buffer.bytes

设置Socket接收消息缓冲区(SO_RECBUF)的大小,默认值为32768(B),即32KB。如果设置为-1,则使用操作系统的默认值。

8.send.buffer.bytes

设置Socket发送消息缓冲区(SO_SNDBUF)的大小,默认值为131072(B),即128KB。与receive.buffer.bytes参数一样,如果设置为-1,则使用操作系统的默认值。

9.request.timeout.ms

配置Producer等待请求响应的最长时间,默认值为30000(ms)。请求超时之后可以选择进行重试。注意这个参数需要比broker端参数replica.lag.time.max.ms的值要大,这样可以减少因客户端重试而引起的消息重复的概率。

 

生产者拦截器:

生产者拦截器既可以在消息发送前对消息做一些处理,比如根据某个规则过滤不符合要求的消息、修改消息的内容等。实现接口:org.apache.kafka.clients.producer.ProcuderInterceptor

配置拦截器:

properties.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG,MyProducerInterceptor.class.getName());

 

kafka生产者对消息进行分区的思路:

  • 如果没有指定具体的partition号,那么Kafka Producer可以通过一定的算法计算出对应的partition号。
  • 如果消息指定了key,则对key进行hash,然后映射到对应的partition号。
  • 如果消息没有指定key,则使Round Robin轮询算法来确定partition号,这样可以保证数据在所有的partition上平均分配。

另外,Kafka Producer也支持自定义的partition分配方式。客户端提供一个实现了org.apache.kafka.clients.producer.Partitioner 的类,然后将此实现类配置到Producer中即可。

 

(二) 消费者

在kafka中,一个group是一个“订阅者”,一个topic中的每个partions只会被一个“订阅者”中的一个consumer消费,不过一个consumer可以消费多个partitions中的消息。 一个消费者既可以消费多个topic的消息,也可消费同一个topic中多个partion的消息。

consumer group是kafka提供的可扩展且具有容错性的消费者机制。组内可以有多个消费者,它们共享一个公共的ID,即group ID。每条消息只会被同一个消费组中的一个消费者实例消费,不同的消费组可以同时消费同一条消息。

kafka保证了在稳定状态下,一个消费组中的某个消费者只会消费一个或多个特定的partion数据,同时一个topic下的某个partion数据只会被同一消费组中的一个消费者消费,这样的好处是降低了分配难度,当consumer与partion之间的关系确立,consumer只需要跟自己相关的partion所在的broker进行通信,也减少了通信开销,管理消息偏移量时也简单许多,最主要的是保证了一个分区的消息可以被有序消费。坏处是有有些分区可能消息非常多,这样与之相关的消费者消费压力就很大,而无法实现同一topic下的消息被同一消费组的所有消费者均匀地消费。

消费者提交方式:

提交方式 类型 实现方式 可能存在的问题
自动提交 自动提交 props.put("enable.auto.commit","true"); 自动提交可能会出现消息重复消费的情况
手动提交 同步提交 consumer.commitSync();  提交后没有响应就阻塞,影响消费者的吞吐量
异步提交 consumer.commitAsync();  可能产生重复消费

 

消费者拦截器:

消费者拦截器由开发人员自定义实现org.apache.kafka.clients.consumer.ConsumerInterceptor接口。kafkaConsumer会在poll方法返回之前调用拦截器的onConsumer方法,来对消息进行相应的定制化,比如改变消息的内容或者根据某个规则过滤消息(这样会减少poll方法返回的消息个数)。onConsumer如果抛出异常会记录到日志,但不会向上传递。

kafkaConsumer再提交完消费位移之后调用拦截器的onCommit()方法,来跟踪与记录所提交的唯一信息。

消费者拦截器与生产者拦截器都是通过properties进行配置。

properties.put(ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG,MyConsumerInterceptor.class.getName())

 

无消息丢失参考配置:

1、不要使用producer.send(msg),而要使用producer.send(msg, callback)。记住,一定要使用带有回调通知的 send 法。

2、设置 retries 为1个较大的值。这里的 retries 同样是 Producer 的参数,对应前面提到的Producer 自动重试。当出现网络的瞬时抖动时,消息发送可能会失败,此时配置了 retries > 0 的Producer 能够自动重试消息发送,避免消息丢失。此外为了防止多个线程重试发送消息时的乱序,最好是发送者线程数量为1个

3、Kafka Producer 会在客户端将消息暂存到 buffer 中,如果不能接受数据丢失,当 buffer 满时阻塞等待要比报错更可取。

4、设置 acks = all。acks 是 Producer 的1个参数,代表了你对“已提交”消息的定义。如果设置成 all,则表明所有副本 Broker 都要接收到消息,该消息才算是“已提交”。这是最高等级的“已提交”定义。

5、设置 unclean.leader.election.enable = false。这是 Broker 端的参数,它控制的是哪些Broker 有资格竞选分区的 Leader。如果1个 Broker 落后原先的 Leader 太多,那么它一旦成为新的 Leader,必然会造成消息的丢失。故一般都要将该参数设置成 false,即不允许这种情况的发生。

6、设置 replication.factor >= 3。这也是 Broker 端的参数。其实这里想表述的是,最好将消息多保存几份,目前防止消息丢失的主要机制就是冗余。

7、设置 min.insync.replicas > 1。这依然是 Broker 端参数,控制的是消息至少要被写入到多少个副本才算是“已提交”。设置成大于 1 可以提升消息持久性。在实际环境中千万不要使用默认值1。

8、确保 replication.factor > min.insync.replicas。如果两者相等,那么只要有1个副本挂机,整个分区就无法正常工作了。我们不仅要改善消息的持久性,防止数据丢失,还要在不降低可用性的基础上完成。推荐设置成 replication.factor = min.insync.replicas + 1。

9、确保消息消费完成再提交。Consumer 端有个参数 enable.auto.commit,最好把它设置成false,并采用手动提交位移的方式。

关于第5,6,7这里有篇参考帖子:acks、replication.factor 、min.insync.replicas三者之间的关系

 消息不丢失相关的配置:

复制代码
# producer
acks = all
block.on.buffer.full = true
retries = MAX_INT
max.inflight.requests.per.connection = 1

# consumer
auto.offset.commit = false

# broker
replication.factor >= 3
min.insync.replicas = 2
unclean.leader.election = false
replication.factor > min.insync.replicas
复制代码

 

影响kafka传输与消费速度的几个可能参数:

Kafka默认参数对生产和消费消息的大小做了限制,一般的数据都可以正常处理,如果要处理比较大的数据量,就需要调整参数。数据量大小的传输速度限制主要跟3个参数有关:

  1.Consumer参数:fetch.max.bytes 默认50M,在创建消费者时,可通过Properties修改

 2. Producer参数:max.request.size 默认1M,和Consumer参数一样,可通过Properties修改.如果生产者发送的消息大小大于配置的值,则将会抛出异常。

 3.Broker参数:message.max.bytes或Topic参数:max.message.bytes 默认约1M

 

可能发生重复消费的情形以及如何避免,做到幂等?

Kafka重复消费的原因主要有两种:

1 offset提交失败:默认情况下,消息消费完之后, 会自动提交offset的值,避免重复消费。但是kafka消费端的自动提交,会有一个默认的5秒时间间隔, 在5秒之后的下一次向Broker拉取消息的时候才提交上一批消费的offset. 那么就容易出现, 在消费者消费的过程中, 应用程序被强制kill或者宕机的情况, 就可能导致offset提交失败, 从而产生重复消费的问题。

2 Kafka服务端的Partion再均衡机制导致消息重复消费:Kafka的Partition Reblance机制把多个Partition均衡的分配给多个消费者,然后消费端从分配到的Partiition里面消费消息。如果消费者在默认的5分钟内没有处理完这批消息,就会触发Kafka的Reblance机制, 从而导致offset自动提交失败。 而Reblance之后, 消费者还是会从之前没提交的offset位置开始消费,从而导致重复消费。

可以通过如下措施避免重复消费:

1 提高消费端的处理性能避免触发Reblance (减少一次从broker上拉取的条数, 同多线程的方式处理消息,缩短单批消息的处理时长, 调整消息处理的超时时间,max.poll.interval.ms)

关于Reblace, 可以参考这个链接:kafka_rebalance

2 使用ConsumerReblanceListener,再均衡监听器, 他可以用来设定发生再均衡总做前后的一些准备或者收尾工作。

3 开启Kafka的幂等性功能prop.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true); 或者将消息生成md5,或者某个唯一字段 然后保存到数据库, 在处理消息之前先查询数据库,判断之前是否已经消费过。、

posted @   小兵要进步  阅读(214)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)

侧边栏
点击右上角即可分享
微信分享提示