生产者发送消息到云消息队列 RocketMQ 版服务端时,云消息队列 RocketMQ 版将根据生产者负载均衡策略将消息均匀的存储在多个队列中,避免产生热点队列和性能瓶颈。本文介绍云消息队列 RocketMQ 版生产者的负载均衡策略。
背景信息
- 消息发送的容灾策略:您可以根据生产者负载均衡策略,明确当局部节点出现故障时,消息发送如何进行容灾切换。
- 消息发送的顺序性机制:通过生产者负载均衡策略,您可以进一步了解顺序消息发送时,如何保证相同消息组内消息的先后顺序。
- 消息服务能力的均衡性策略:了解生产者负载均衡策略,您可以知晓消息在不同节点间分配的规律,您可以按照负载策略针对性地设计流量迁移和水平扩缩容的方案。
RoundRobin模式
使用范围
对于非顺序消息(普通消息、定时/延时消息、事务消息)场景,默认且只能使用RoundRobin模式的负载均衡策略。
策略原理:轮询方式
RoundRobin模式下,生产者发送消息时,以消息为粒度,按照轮询方式将消息依次发送到指定主题中的所有可写目标队列中,保证消息尽可能均衡地分布到所有队列。
如上图所示,M1、M2表示生产者发送的第一条消息、第二条消息,Queue1、Queue2、Queue3表示主题中的三个队列。
生产者按照轮询方式分别将消息依次发送到这三个队列中,M1发送至Queue1中、M2发送至Queue2中、M3发送至Queue3中,以此类推,第四条消息M4又发送至Queue1中,循环往复。
异常处理
当发送某条消息发送失败时,云消息队列 RocketMQ 版会根据失败原因决定在接下来一段时间内,选择队列目标时跳过本地失败队列所在的节点,快速实现自适应的故障隔离。
策略特点
RoundRobin模式的生产者负载均衡策略仅适用于无顺序性的消息,该模式下是以消息为粒度进行轮询负载,因此消息能够尽可能实现均匀分布,使得主题的传输能力尽可能达到最大。
使用示例
RoundRobin模式不需要额外设置,对于非顺序类型的消息默认启用。
//普通消息发送模式默认采用RoundRobin负载均衡策略。
//普通消息发送。
MessageBuilder messageBuilder = null;
for (int i = 0; i < 10; i++) {
//普通消息发送时无需客户端额外设置,由SDK内置逻辑实现队列轮询和平均分配。
Message message = messageBuilder.setTopic("normalTopic")
//设置消息索引键,可根据关键字精确查找某条消息。
.setKeys("messageKey")
//设置消息Tag,用于消费端根据指定Tag过滤消息。
.setTag("messageTag")
//消息体。
.setBody("messageBody".getBytes())
.build();
try {
//发送消息,需要关注发送结果,并捕获失败等异常。
SendReceipt sendReceipt = producer.send(message);
System.out.println(sendReceipt.getMessageId());
} catch (ClientException e) {
e.printStackTrace();
}
}
MessageGroupHash模式
使用范围
对于顺序消息场景,默认且只能使用MessageGroupHash模式的负载均衡策略。
策略原理:Hash算法
MessageGroupHash模式下,生产者发送消息时,以消息组为粒度,按照内置的Hash算法,将相同消息组的消息分配到同一队列中,保证同一消息组的消息按照发送的先后顺序存储。
如上图所示,消息G1-M1、G1-M2、G1-M3属于消息组1中的第一条消息、第二条消息和第三条消息,生产者按照Hash算法将这几条消息分配到同一队列MessageQueue1中,且在队列中保存的先后顺序和发送顺序一致。
策略特点
MessageGroupHash模式的生产者负载均衡策略仅适用于顺序性的消息,可以很好地保证同消息组内消息的顺序性。
但是若不同消息组的消息数量差异较大,MessageGroupHash模式将不能很好地保障消息的均衡分配和性能扩展能力。在极端场景下,可能会出现大部分消息集中在少数队列中的情况,建议设计消息组时尽量将消息离散开,不要集中在少量消息组中。
使用示例
MessageGroupHash模式不需要额外设置,对于顺序消息类型默认启用。
//顺序消息发送,默认采用MessageGroupHash模式负载均衡策略。
for (int i = 0; i < 10; i++) {
Message message = messageBuilder.setTopic("fifoTopic")
//设置消息索引键,可根据关键字精确查找某条消息。
.setKeys("messageKey")
//设置消息Tag,用于消费端根据指定Tag过滤消息。
.setTag("messageTag")
//顺序消息场景需要设置MessageGroup,相同MessageGroup的消息通过Hash算法会被分配到同一个队列。
.setMessageGroup("fifoGroupA")
//消息体。
.setBody("messageBody".getBytes())
.build();
try {
//发送消息,需要关注发送结果,并捕获失败等异常。
SendReceipt sendReceipt = producer.send(message);
System.out.println(sendReceipt.getMessageId());
} catch (ClientException e) {
e.printStackTrace();
}
}
版本兼容性
- MessageGroupHash模式
MessageGroupHash模式的负载均衡策略从云消息队列 RocketMQ 版服务端5.0版本开始支持,历史版本4.x/3.x版本不支持。
因此,若将顺序消息的发送机制从历史4.x/3.x版本升级到5.x版本时,您需要自行保证消息的顺序性。例如,您可以将主题内的消息消费完,再切换到新的5.x版本上。
- RoundRobin模式
RoundRobin模式的负载均衡策略服务端5.0版本和历史版本4.x/3.x版本均支持,不涉及版本兼容性。
使用建议
使用MessageGroupHash模式时,避免出现热点队列
MessageGroupHash模式下,云消息队列 RocketMQ 版保证相同消息组的消息存储在同一个队列中。如果业务侧将消息都集中在少量或唯一的消息组,则此时服务端存储消息时,也会集中存储在少量或唯一的队列中。极大增加了服务端的存储压力,导致出现队列热点,不利于主题处理能力的水平扩展。
因此,建议您在设计消息组时,尽量将消息分散开。例如,采用较离散的订单ID、用户名作为消息组的关键字,既能保证消息被分散到多个消息组中,又能保证同一终端用户的消息按顺序处理。
避免绑定单队列发送
不管顺序消息还是非顺序消息,为了保证服务端性能的水平扩展和容灾能力,建议都尽可能使用多个队列,尽量避免负载均衡后只使用单个队列。例如,若某主题只有一个队列,则不管怎么负载均衡,消息也只能发送到这一个队列中,单个队列容易产生性能瓶颈及容灾风险。