导语

在数字化时代,消息队列系统已成为企业架构中不可或缺的一部分,其中,TDMQ CKafka 版作为一种高效、可扩展的分布式消息系统,广泛应用于各类业务场景中。上一篇我们深入探讨了 TDMQ CKafka 版的生产实践,从消息发送、分区策略到高可用性保障,全方位解析了如何在生产环境中高效利用 TDMQ CKafka 版。本文将接续前文,聚焦于 TDMQ CKafka 版的消费实践,探讨如何稳扎稳打、精准消费,确保消息从生产到消费的完整链条顺畅无阻。

在消费篇中,我们将详细阐述消费消息的基本流程、负载均衡机制、应对重平衡的策略、订阅关系的管理、消费位点的控制、消息重复与消费幂等性的处理、消费失败的应对、消费延迟与堆积的解决,以及如何通过调整套接字缓冲区、模拟消息广播、实现消息过滤等高级技巧,优化 TDMQ CKafka 版的消费性能。

接下来,让我们一同深入探索 TDMQ CKafka 版的消费实践,解锁高效消息处理的秘诀。

消费篇:稳扎稳打,精准消费

消费消息流程

消费消息的基本流程并不复杂,首先是 Poll 数据,消费者从消息队列中拉取消息;接着执行消费逻辑,对拉取到的消息进行处理;处理完成后再次 Poll 数据,如此循环往复。例如,在一个电商订单处理系统中,消费者从消息队列中拉取订单消息,然后根据订单信息进行库存扣减、订单状态更新等操作,完成后继续拉取下一批订单消息。

负载均衡机制

负载均衡

负载均衡在消费过程中起着关键作用。每个 Consumer Group 可以包含多个 Consumer ,只要将参数 group.id 设置成相同的值,这些 Consumer 就属于同一个 Consumer Group,共同负责消费订阅的 Topic。

例如:Consumer Group A 订阅了 Topic A,并开启三个消费实例 C1、C2、C3,则发送到 Topic A 的每条消息最终只会传给 C1、C2、C3 的某一个。TDMQ CKafka 版默认会均匀地把消息传给各个消费实例,以做到消费负载均衡。

TDMQ CKafka 版负载均衡的内部原理是:把订阅的 Topic 的分区,平均分配给各个 Consumer。因此,Consumer 的个数不要大于分区的数量,否则会有消费实例分配不到任何分区而处于空跑状态,尽量保证消费者数量能被分区总数整除。除了第一次启动上线之外,后续消费实例发生重启、增加、减少,分区数发生增加等变更时,都会触发一次重均衡。

应对重均衡

如果频繁出现 Rebalance,可能有多种原因。

1、 消费者消费处理耗时很长,比如在处理一些复杂的业务逻辑时,可能需要进行多次数据库查询或远程接口调用,这会导致消费速度变慢;

2、 消费某一个异常消息也可能导致消费者阻塞或者失败,例如消息格式错误,消费者无法解析;

3、 心跳超时同样会引发 Rebalance 。

4、 在 v0.10.2 之前版本的客户端,Consumer 没有独立线程维持心跳,而是把心跳维持与 Poll 接口耦合在一起,若用户消费出现卡顿,就会导致 Consumer 心跳超时,引发 Rebalance;在 v0.10.2 及之后版本的客户端,如果消费时间过慢,超过一定时间(max.poll.interval.ms 设置的值,默认5分钟)未进行 Poll 拉取消息,则会导致客户端主动离开队列,而引发 Rebalance。

可以通过优化消费处理提高消费速度和参数调整等方法解决:

1、 消费端需要和 Broker 版本保持一致。

2、 可以参考以下说明调整参数值:

● session.timeout.ms:在 v0.10.2 之前的版本可适当提高该参数值,需要大于消费一批数据的时间,但不要超过 30s,建议设置为25s ,而 v0.10.2 及其之后的版本,保持默认值10s即可;

● max.poll.records:降低该参数值,建议远远小于单个线程每秒消费的条数 * 消费线程的个数 * max.poll.interval.ms / 1000 的值;

● max.poll.interval.ms :该值要大于 max.poll.records / (单个线程每秒消费的条数 * 消费线程的个数 ) 的值。

3、 尽量提高客户端的消费速度,将消费逻辑另起线程进行处理,并针对耗时进行监控。

4、 减少 Group 订阅 Topic 的数量,一个 Group 订阅的 Topic 最好不要超过5个,建议一个 Group 只订阅一个 Topic。

主题订阅关系

在订阅关系方面,同一个 Consumer Group 内,建议客户端订阅的 Topic 保持一致,即一个 Consumer Group 订阅一个 Topic,这样可以避免给排查问题带来更多复杂度。

Consumer Group 订阅多个 Topic

一个 Consumer Group 可以订阅多个 Topic ,此时多个 Topic 的消息会被 Cosumer Group 中的 Consumer 均匀消费。例如 Consumer Group A 订阅了 Topic A、Topic B、Topic C,则这三个 Topic 中的消息,被 Consumer Group 中的 Consumer 均匀消费。

Consumer Group 订阅多个 Topic 的示例代码如下:

String topicStr = kafkaProperties.getProperty("topic");
String[] topics = topicStr.split(",");
for (String topic: topics) {
    subscribedTopics.add(topic.trim());
}
consumer.subscribe(subscribedTopics);

Topic 被多个 Consumer Group 订阅

一个 Topic 可以被多个 Consumer Group 订阅,且各个 Consumer Group 独立消费 Topic 下的所有消息。例如 Consumer Group A 订阅了 Topic A,Consumer Group B 也订阅了 Topic A,则发送到 Topic A 的每条消息,不仅会传一份给 Consumer Group A 的消费实例,也会传一份给 Consumer Group B 的消费实例,且这两个过程相互独立,相互没有任何影响。

一个 Consumer Group 对应一个应用

建议一个 Consumer Group 对应一个应用,即不同的应用对应不同的代码。如果您需要将不同的代码写在同一个应用中,请准备多份不同的 kafka.properties。例如:kafka1.properties、kafka2.properties。

消费位点解析

每个 Topic 会有多个分区,每个分区会统计当前消息的总条数,这个称为最大位点 MaxOffset。

TDMQ CKafka 版的 Consumer 会按顺序依次消费分区内的每条消息,记录已经消费了的消息条数,称为消费位点 ConsumerOffset。

剩余的未消费的条数(也称为消息堆积量)=MaxOffset-ConsumerOffset。

Offset 提交

TDMQ CKafka 版的 Consumer 有两个相关参数:

● enable.auto.commit:默认值为 True。

● auto.commit.interval.ms: 默认值为5000,即5s。

这两个参数组合的结果为:每次 Poll 数据前会先检查上次提交位点的时间,如果距离当前时间已经超过参数 auto.commit.interval.ms 规定的时长,则客户端会启动位点提交动作。

因此,如果将 enable.auto.commit 设置为 True,则需要在每次 Poll 数据时,确保前一次 Poll 出来的数据已经消费完毕,否则可能导致位点跳跃。

如果想自己控制位点提交,请把 enable.auto.commit 设为 False,并调用 Commit(Offsets) 函数自行控制位点提交。

注意:

尽量避免提交位点请求过于频繁,否则容易导致 Broker CPU 很高,影响正常的服务。例如自动提交位点设置 auto.commit.interval.ms 为100ms,手动提交位点,在高吞吐场景下,每消费一条消息提交一个位点。

重置 Offset

以下两种情况,会发生消费位点重置:

● 当服务端不存在曾经提交过的位点时(例如客户端第一次上线)。

● 当从非法位点拉取消息时(例如某个分区最大位点是10,但客户端却从11开始拉取消息)。

Java 客户端可以通过 auto.offset.reset 来配置重置策略,主要有三种策略:

● Latest:从最大位点开始消费。

● Earliest:从最小位点开始消费。

● None:不做任何操作,即不重置。

说明:

建议设置成 Latest,而不要设置成 Earliest,避免因位点非法时从头开始消费,从而造成大量重复。

如果是您自己管理位点,可以设置成 None。

拉取消息优化

拉取消息

消费过程是由客户端主动去服务端拉取消息的,在拉取大消息时需要控制拉取速度,注意以下参数设置:

● max.poll.records:如果单条消息超过1MB,建议设置为1。

● max.partition.fetch.bytes:设置为比单条消息的大小略大一点。

● fetch.max.bytes:设置为比单条消息的大小略大一点。

通过公网消费消息时,通常会因为公网带宽的限制导致连接被断开,此时需要注意控制拉取速度,修改配置:

● fetch.max.bytes:建议设置成公网带宽的一半(注意该参数的单位是 bytes,公网带宽的单位是 bits)。

● max.partition.fetch.bytes:建议设置成 fetch.max.bytes 的三分之一或者四分之一。

拉取大消息

消费过程是由客户端主动去服务端拉取消息的,在拉取大消息时,需要注意控制拉取速度,注意修改配置:

● max.poll.records:每次 Poll 获取的最大消息数量。如果单条消息超过1MB,建议设置为1。

● fetch.max.bytes:设置比单条消息的大小略大一点。

● max.partition.fetch.bytes:设置比单条消息的大小略大一点。

拉取大消息的核心是逐条拉取。

消息异常处理

消息重复和消费幂等

TDMQ CKafka 版消费的语义是 at least once, 也就是至少投递一次,保证消息不丢失,但是无法保证消息不重复。在出现网络问题、客户端重启时均有可能造成少量重复消息,此时应用消费端如果对消息重复比较敏感(例如订单交易类),则应该做消息幂等。

以数据库类应用为例,常用做法为:

  • 发送消息时,传入 Key 作为唯一流水号 ID。
  • 消费消息时,判断 Key 是否已经消费过,如果已经消费过了,则忽略,如果没消费过,则消费一次。

当然,如果应用本身对少量消息重复不敏感,则不需要做此类幂等检查。

消费失败

TDMQ CKafka 版是按分区消息顺序逐条向前推进消费的,如果消费端拿到某条消息后执行消费逻辑失败,例如应用服务器出现了脏数据,导致某条消息处理失败,等待人工干预,那么有以下两种处理方式:

失败后一直尝试再次执行消费逻辑。这种方式有可能造成消费线程阻塞在当前消息,无法向前推进,造成消息堆积。

由于 Kafka 没有处理失败消息的设计,实践中通常会打印失败的消息或者存储到某个服务(例如创建一个 Topic 专门用来放失败的消息),然后定时检查失败消息的情况,分析失败原因,根据情况处理。

消费延迟

消费过程是由客户端主动去服务端拉取消息。一般情况下,如果客户端能够及时消费,则不会产生较大延迟。若产生了较大延迟,请先关注是否有堆积,并注意提高消费速度。

消费堆积

通常造成消息堆积的原因是:

● 消费速度跟不上生产速度,此时应该提高消费速度。

● 消费端产生了阻塞。

● 消费端拿到消息后,执行消费逻辑,通常会执行一些远程调用,如果这个时候同步等待结果,则有可能造成一直等待,消费进程无法向前推进。

消费端应该尽量避免堵塞消费线程,如果存在等待调用结果的情况,建议设置等待的超时时间,超时后作为消费失败进行处理。

提高消费速度

方式1:增加 Consumer 实例个数提高并行处理能力,如果消费者和分区数已经1:1,可以考虑增加分区数(注意:对于 Flink 自动维护分区的场景不会自动感知新增分区后可能需要修改相关代码后重启)。 可以在进程内直接增加(需要保证每个实例对应一个线程),也可以部署多个消费实例进程。

说明:

实例个数超过分区数量后就不再能提高速度,将会有消费实例不工作。

方式2:增加消费线程。

  1. 定义一个线程池。

  2. Poll 数据。

  3. 把数据提交到线程池进行并发处理。

  4. 等并发结果返回成功后,再次 poll 数据执行。

消费某些分区不消费

消费者在消费过程中,可能遇到消费者在线,但是某些分区的位点一致不前进,可能原因如下:

  1. 遇到一条异常消息,可能是超大消息,格式异常,导致消费者拉取消息时候,转换成业务位点。

  2. 使用公网带宽,带宽较小,拉取大消息时候直接把带宽打满,导致在超时时间内拉取不到消息。

  3. 消费者假死,导致不去拉取。

解决方式:

关掉消费者,在 TDMQ CKafka 版控制台设置位点,跳过某些异常消息,或者优化消费代码,然后重启消费者消费。

消息订阅模式

消息广播

Kafka 目前没有消息广播的语义,可以通过创建不同的 Group 来模拟实现。

消息过滤

Kafka 自身没有消息过滤的语义。实践中可以采取以下两个办法:

● 如果过滤的种类不多,可以采取多个 Topic 的方式达到过滤的目的。

● 如果过滤的种类多,则最好在客户端业务层面自行过滤。

实践中请根据业务具体情况进行选择,也可以综合运用上面两种办法。

总结:回顾要点,展望应用

通过前面的学习,我们系统地了解了生产消费的关键要点。在生产方面,从 Topic 的使用与创建,到分区数的估算、重试策略的设置,再到发送模式和参数的优化,以及 Key、Value 和分区策略的应用,每一个环节都需要我们精心配置,以确保高效、稳定的生产。在消费方面,消费的基本流程、负载均衡的实现、应对重均衡的策略、订阅关系的管理、Offset 的管理与拉取策略,以及处理消息重复和消费失败的方法,这些都是我们在消费过程中需要重点关注的内容。

这些生产消费知识在实际应用中具有巨大的价值。希望大家能将所学的生产消费知识运用到实际工作和生活中,不断探索和实践。也期待大家在实践过程中,能总结出更多宝贵的经验,欢迎在留言区分享你们的实践故事和心得,让我们一起共同成长,共同进步 。

文章来源于腾讯云开发者社区,点击查看原文