消费者负载均衡
当消费者组中的消费者从 Apache RocketMQ 主题拉取消息时,会使用负载均衡策略来确定如何将消息分配给消费者。负载均衡策略可以提高服务并发性和应用程序可伸缩性。本文介绍 Apache RocketMQ 为消费者提供的负载均衡策略。
背景信息
熟悉 Apache RocketMQ 提供的负载均衡策略可以帮助您在遇到以下场景时确定要采取的适当措施:
灾难恢复:您可以确定当本地节点发生故障时,消息如何重试和切换。
消息顺序:您可以更好地理解 Apache RocketMQ 如何确保严格的先进先出消息顺序。
水平分区:您可以根据消息分配方式规划流量迁移和水平扩展操作。
广播消费和集群消费
Apache RocketMQ 允许多个消费者组订阅同一消息,并且每个消费者组可以初始化多个消费者。消费者组和消费者可以配置为在以下场景中消费消息:
跨消费者组广播消费:此场景如上图左侧所示。每个消费者组初始化自己的消费者,该消费者消费所有消息。消息以一对多的关系从主题传递给多个订阅者。
此模式通常用于网关推送和配置推送等场景。
消费者组内集群消费:此场景如上图右侧所示。每个消费者组初始化多个消费者,消息被发送到组中的所有消费者。这在您希望在组内实现水平流量分区和负载均衡时非常有用。
此模式适用于微服务解耦。
消费者负载均衡策略介绍
在使用广播消费的场景中,不需要负载均衡,因为每个消费者组只包含一个消费者。
然而,在使用集群消费的场景中,每个消费者组包含多个消费者。负载均衡策略可以帮助确定消息如何分配。
根据消费者类型,负载均衡策略可分为以下两种:
消息级负载均衡
使用范围
消息级负载均衡是 PushConsumer 和 SimpleConsumer 的唯一且默认策略。
工作机制
消息级负载均衡将主题中的消息均匀地分配给消费者组中的多个消费者。
如上图所示,消费者组 A 由三个消费者组成:A1、A2 和 A3。这三个消费者消费主题中 Queue1 的消息。
消息级负载均衡确保队列中的消息可以被多个消费者并发处理。但是,消息是随机发送给消费者的,这意味着您无法指定消息如何分配给消费者。
消息级负载均衡基于主题中单个消息的确认语义。消费者收到消息后,Broker 会锁定该消息,确保在消息被消费或超时之前对其他消费者不可见。这可以防止同一队列中的消息被不同的消费者多次消费。
顺序消息的负载策略
在顺序消息中,消息的顺序是指消息组中多条消息的序列。这些消息必须按照它们在 Broker 上存储的完全相同的顺序进行处理。因此,消息级负载均衡需要确保消息组中的消息按照它们在服务器上存储的相同顺序被消费。当不同消费者处理同一组中的消息时,系统会严格按照消息顺序锁定消息,以确保消息按顺序消费。
在上图中,Queue1 的消息组 G1 中有四条顺序消息。它们的保存顺序用 M1 到 M4 表示。在消费过程中,当消息 M1 和 M2 被消费者 A1 处理时,如果 M1 和 M2 的消费状态未提交,消费者 A2 无法并行消费消息 M3 和 M4。只有当前面消息的消费状态提交后,消费者才能消费后面的消息。
特性
与队列级负载均衡相比,消息级负载均衡具有以下特性:
- 更均衡的消费分配。在传统的队列级负载均衡中,队列数量和消费者数量可能无法很好地平衡。这会导致某些消费者空闲,而某些消费者超负荷。相比之下,消息级负载均衡确保消费者之间负载均衡,无需您管理队列和消费者的数量。
- 对网络容量差异更具容忍度。在在线生产环境中,由于实际网络条件或网络硬件规格不一致,消费者的处理能力可能不同。如果消息是按队列分配的,则可能会出现某些消费者消息堆积而其他消费者空闲的情况。相比之下,消息级负载均衡按需分配消息,以在消费者之间实现更均衡的负载分布。
- 更易于队列分配的运维。在采用传统队列级负载均衡的场景中,您必须确保队列数量大于或等于消费者数量,以确保没有消费者空闲。消息级负载均衡不存在此问题。
场景
由于队列中的消息是离散地分配给消费者的,消息级负载均衡适用于大多数在线事件处理场景。在这些场景中,消费者只需要基本的处理能力,而不是消息的批量聚合。对于流处理和聚合计算等需要消息聚合和批量处理的场景,队列级负载均衡是更好的选择。示例
消费者无需为消息级负载均衡执行额外配置。默认情况下,此策略为 PushConsumer 和 SimpleConsumer 启用。
SimpleConsumer simpleConsumer = null;
// Consumption example 1: When push consumers consume normal messages, they need only to process messages on a message listener and do not need to consider load balancing.
MessageListener messageListener = new MessageListener() {
@Override
public ConsumeResult consume(MessageView messageView) {
System.out.println(messageView);
// Return the status based on the consumption result.
return ConsumeResult.SUCCESS;
}
};
// Consumption example 2: When simple consumers consume normal messages, they obtain and submit messages. The consumers obtain messages based on the subscribed topic and do not need to consider load balancing.
List<MessageView> messageViewList = null;
try {
messageViewList = simpleConsumer.receive(10, Duration.ofSeconds(30));
messageViewList.forEach(messageView -> {
System.out.println(messageView);
// After consumption is complete, consumers must invoke ACK to submit the consumption result.
try {
simpleConsumer.ack(messageView);
} catch (ClientException e) {
e.printStackTrace();
}
});
} catch (ClientException e) {
// If the pull fails due to system traffic throttling or other reasons, consumers must re-initiate the request to obtain the message.
e.printStackTrace();
}
队列级负载均衡
使用范围
对于 Broker 4.x 和 3.x 版本的消费者,包括 PullConsumer、DefaultPushConsumer、DefaultPullConsumer 和 DefaultLitePullConsumer,只能使用队列级负载均衡。
工作机制
在队列级负载均衡策略中,同一消费者组中的消费者消费分配给它们的消息。每个队列由一个消费者消费。
如上图所示,主题中的三个队列(Queue1、Queue2 和 Queue3)被分配给消费者组中的两个消费者。由于每个队列只能分配给一个消费者,消费者 A2 被分配了两个队列。如果队列数量小于消费者数量,则某些消费者将没有队列分配给它们。
队列级负载均衡根据队列数量和消费者数量等操作数据分配消息。每个队列都绑定到一个特定的消费者。然后,每个消费者根据获取消息的消费语义处理消息:>提交偏移量>持久化偏移量。当消费者获取消息时,消费状态不返回给队列。因此,为避免多个消费者重复消费消息,每个队列只能由一个消费者消费。
队列级负载均衡保证一个队列只被一个消费者处理。但是,此策略的实现取决于消费者和 Broker 之间的信息协商机制。
Apache RocketMQ 不保证队列中的消息只被一个消费者处理。因此,当消费者数量和队列数量发生变化时,可能会出现队列分配的暂时不一致,导致少量消息被多次处理。
特性
与消息级负载均衡相比,队列级负载均衡的粒度更大,灵活性更低。但是,队列级负载均衡非常适合流处理场景。它确保队列中的消息由一个消费者处理。因此,队列级负载均衡更适用于需要处理聚合消息或批量消息的场景。
场景
队列级负载均衡适用于需要处理聚合消息或批量消息的场景。这些是流计算和数据聚合应用中的常见场景。
示例
消费者无需为队列级负载均衡执行额外配置。默认情况下,此策略为 Broker 4.x 和 3.x 版本的 PullConsumer 启用。
有关示例代码的更多信息,请访问 Apache RocketMQ 代码库。
版本兼容性
消息级负载均衡策略从 Apache RocketMQ 的 Broker 5.0 版本开始可用。对于 Broker 4.x 和 3.x 版本,只有队列级负载均衡策略可用。
Apache RocketMQ 的 Broker 5.x 版本同时支持消息级和队列级负载均衡策略。具体哪个策略生效取决于客户端版本和消费者类型。
使用注意事项
为消费逻辑实现消息幂等性。
在添加消费者、移除消费者和 Broker 扩缩容等场景中,消息级和队列级负载均衡策略都会触发临时再平衡。这可能导致临时的负载不一致,并导致少量消息被多次消费。因此,需要进行去重以确保消息消费的幂等性。