1.如果发布者配置了exchange与queue的绑定路由关系,消费者又利用@RabbitListener注解配置了一次路由关系会怎么样?
答:@rabbitListener会将发布者的绑定关系覆盖,一般开发过程中只需要在一方进行配置即可,可以预先约定好配置规则与位置,防止导致绑定关系混乱
2.如果exchange根据key没有找到对应的队列会怎么办
答:官网介绍
本人只测试了direct类型的交换器及绑定队列情况,测试情况
- 发送消息到不存在的交换机会进入到ConfirmCallback中ack应答为false的处理逻辑
- 发送消息到正确交换器错误队列时会进入到returnsCallback回调函数中
总结:
消息是否发送到交换器ConfirmCallback回调(ack:true成功发送到交换器,ack:false未成功发送到交换器)
消息未成功路由到队列returnsCallback回调
@Bean
@Primary
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate= new RabbitTemplate(connectionFactory);
//判断消息是否被正确路由到了队列
rabbitTemplate.setMandatory(true);
rabbitTemplate.setReturnsCallback(returnedMessage -> {
System.out.println("Message returned: " + returnedMessage.getMessage());
// 在这里处理消息被返回的逻辑
});
//判断消息是否发送到了exchange交换机
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
// 消息成功发送到队列
System.out.println("Message sent successfully.");
} else {
// 消息发送失败,可以进行相应的处理
System.out.println("Message sent failed: " + cause);
}
});
return rabbitTemplate;
}
3.多个消费者同时消费同一个队列时,如何进行权重配置
答:可以在rabbitListener注解中配置concurrency 参数配置,也可以自定义权重进行处理
4.如何保证消息有序性
答:
-
单一队列: 使用单一队列,并确保只有一个消费者在任何时刻处理该队列的消息。这样可以保证消息的顺序性,但同时也可能限制系统的并发性能。
-
分区队列: 如果业务允许,可以将消息按照某种规则分散到不同的队列中,每个队列由单一消费者处理。这样每个队列的消息顺序可以得到保证。
-
消息分组: RabbitMQ 的插件
rabbitmq_consistent_hash_exchange
允许你定义一个基于消息内容的哈希函数,将消息路由到同一组的消费者。这样,同一组内的消息处理顺序是有序的,但不同组之间的消息处理顺序无法保证。 -
消息排序字段: 将消息设计成带有排序字段,消费者在处理消息时可以根据这个字段来进行排序。这通常需要业务上有一个明确的排序标准。
请注意,以上的方法都是基于特定的场景和业务需求来选择的。在某些情况下,有序性的要求可能导致系统性能上的牺牲。在设计时,需要根据实际情况权衡性能和有序性需求。
5.如何保证消息不丢失
注意:该方法也只是保证理论上消息不丢失,特别极端情况还是有可能导致数据丢失
1. 消息持久化
在 RabbitMQ 中,可以通过设置消息的 deliveryMode 属性来实现消息的持久化。deliveryMode 为 2 表示消息持久化,为 1 表示非持久化。通过 RabbitTemplate 发送消息时,可以设置 deliveryMode 属性。以下是一个示例:
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
public class MyMessageSender {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendMessage(String message) {
MessageProperties properties = new MessageProperties();
properties.setDeliveryMode(MessageDeliveryMode.PERSISTENT); // 设置消息持久化
Message rabbitMessage = new Message(message.getBytes(), properties);
rabbitTemplate.convertAndSend("exchangeName", "routingKey", rabbitMessage);
}
}
2. 消息确认机制
保证消息成功发送到了rabbitmq服务端
RabbitMQ 提供了消息确认机制,确保消息成功发送到队列中。可以通过配置 RabbitTemplate 的 ConfirmCallback 和 ReturnCallback 来实现消息的确认。以下是一个示例:
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
public class MyMessageSender {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendMessage(String message) {
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
// 消息成功发送到队列
System.out.println("Message sent successfully.");
} else {
// 消息发送失败,可以进行相应的处理
System.out.println("Message sent failed: " + cause);
}
});
rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
// 消息没有路由到交换器,可以进行相应的处理
System.out.println("Message returned: " + message);
});
rabbitTemplate.convertAndSend("exchangeName", "routingKey", message, new CorrelationData("correlationId"));
}
}
通过设置 ConfirmCallback 和 ReturnCallback,可以在消息成功发送到队列或发生错误时得到相应的回调通知,从而确保消息不丢失。
保证消息被消费掉
消费端您使用消费确认机制进行处理
//bindings 当没有配置队列与交换机关系时,可以使用该参数进行绑定关系配置
@RabbitListener(
queues = "directQueue1",ackMode="MANUAL"
)
public void receive(String message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
System.out.println("directQueue1接收到消息"+message);
//消息成功消费应答,tag当前消息编号,multiple:下方有解释
// channel.basicAck(tag,false);
/*
tag: 这是消息的传送标签(Delivery Tag),用于标识消息。每条消息都有一个唯一的标签。
multiple: 这是一个布尔值,表示是否拒绝所有小于或等于提供的传送标签(tag)的消息。如果设置为 true,则拒绝所有小于或等于 tag 的消息;如果设置为 false,则仅拒绝传送标签为 tag 的消息。
requeue: 这是一个布尔值,表示是否重新将被拒绝的消息重新放入队列。如果设置为 true,则重新放入队列;如果设置为 false,则消息将被丢弃。
*/
// channel.basicNack(tag,false,false);//失败应答方式,但是也是成功消费
//消息拒绝消费,如果配置了死信队列进入到死信队列,参数与basicAck参数解释一致
channel.basicReject(tag,false);
}
6.rabbitmq_consistent_hash_exchange插件如何使用
注意:此处路由key需要使用整数来作为key路由(实例中的routing-key-2/routing-key-1)
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class ConsistentHashExchangeExample {
public static void main(String[] args) {
SpringApplication.run(ConsistentHashExchangeExample.class, args);
}
// 创建一致性哈希交换机
@Bean
public Exchange myConsistentHashExchange() {
return new CustomExchange("myConsistentHashExchange", "x-consistent-hash", true, false);
}
// 创建队列
@Bean
public Queue myQueue1() {
return new Queue("myQueue1");
}
@Bean
public Queue myQueue2() {
return new Queue("myQueue2");
}
// 将队列绑定到一致性哈希交换机上
@Bean
public Binding binding1(Queue myQueue1, Exchange myConsistentHashExchange) {
return BindingBuilder.bind(myQueue1).to(myConsistentHashExchange).with("routing-key-1").noargs();
}
@Bean
public Binding binding2(Queue myQueue2, Exchange myConsistentHashExchange) {
return BindingBuilder.bind(myQueue2).to(myConsistentHashExchange).with("routing-key-2").noargs();
}
// 消费者监听队列
@RabbitListener(queues = "myQueue1")
public void handleMessageFromQueue1(String message) {
System.out.println("Received message from myQueue1: " + message);
}
@RabbitListener(queues = "myQueue2")
public void handleMessageFromQueue2(String message) {
System.out.println("Received message from myQueue2: " + message);
}
}
7.如何为一个队列添加死信队列
直接上代码
1.声明一个死信队列
死信交换器名称/队列名称/路由key都可以自定义
// 声明死信交换机
@Bean
public DirectExchange deadLetterExchange() {
return new DirectExchange("dead-letter-exchange");
}
// 声明死信队列
@Bean
public Queue deadLetterQueue() {
return QueueBuilder.durable("dead-letter-queue")
// .withArgument("x-dead-letter-exchange", "dead-letter-exchange") // 指定死信交换机
// .withArgument("x-dead-letter-routing-key", "dead-letter-routing-key") // 指定死信消息的路由键
.build();
}
@Bean
public Binding BingDeadQueue(){
return BindingBuilder.bind(deadLetterQueue()).to(deadLetterExchange()).with("dead-letter-routing-key");
}
2.为正常队列绑定死信队列
@Bean
public Queue directQueue1(){
// 配置死信队列
// 进入死信队列的集中情况
// 1.消息被拒绝 2.消息过期 3.队列达到最大长度 4.队列达到最大权重
Map<String, Object> args = new HashMap<>();
args.put("x-dead-letter-exchange", "dead-letter-exchange");
args.put("x-dead-letter-routing-key", "dead-letter-routing-key");
/*
durable: 队列是否持久化
exclusive:只有当前获取连接的消费者可以获取到队列消息,其他消费者获取不到
autoDelete: 当消息队列没有消费者连接的时候自动删除队列
*/
return new Queue("directQueue1",false,false,false,args);
// return QueueBuilder.nonDurable("directQueue1")
// .deadLetterExchange("dead-letter-exchange")
// .deadLetterRoutingKey("dead-letter-routing-key")
// .build();
}
8.消费者重试机制配置
spring: rabbitmq: listener: simple: retry: enabled: true # 开启消费者失败重试 initial-interval: 1000 # 初识的失败等待时长为1秒 multiplier: 2 # 失败的等待时长倍数,下次等待时长 = multiplier * last-interval max-attempts: 3 # 最大重试次数 stateless: true # true无状态;false有状态。如果业务中包含事务,这里改为false
9.如何确保消息不重复消费
-
消息确认机制: RabbitMQ 提供了消息确认(acknowledgment)机制,消费者处理完消息后需要向 RabbitMQ 发送确认,告知 RabbitMQ 消息已经成功处理。只有在收到消费者的确认之后,RabbitMQ 才会将消息从队列中删除,确保消息不会被重复消费。
-
消费者端实现幂等性: 在消费者端实现消息的幂等性处理。即使消息被重复消费,消费者也能够保证处理的结果是一致的。通过对消费者的业务逻辑进行设计,确保同一条消息被消费多次时,产生的影响是相同的,不会导致错误结果。
-
消息去重: 可以在消费者端实现消息的去重机制。消费者在处理消息之前,先查询某个持久化的存储(如数据库或缓存),检查消息是否已经被处理过。如果消息已经被处理过,则直接丢弃,避免重复处理。
-
使用消息唯一标识: 在消息的属性中包含唯一标识符,消费者在处理消息时,先检查消息的唯一标识符是否已经处理过。如果已经处理过,则认为消息重复,不再处理。
-
使用事务或幂等性操作: 在消费者端使用事务或幂等性操作来确保消息的处理具有原子性和幂等性。通过在处理消息时进行事务提交或执行幂等性操作,可以避免同一条消息被重复处理。
10.延迟队列
使用场景:
- 实现消息的延迟投递:例如,订单支付成功后延迟一段时间发送订单完成通知消息。
- 实现定时任务:例如,定时发送邮件或短信,定时执行某个任务等。
- 实现消息重试机制:例如,消息处理失败后延迟一段时间进行重试。
-
使用 TTL(Time-To-Live)和死信队列: 在消息发布时设置消息的 TTL,即消息的存活时间。当消息过期后,会被发送到死信交换机,然后再路由到死信队列。通过配置死信队列,可以实现消息的延迟投递。
//定义正常队列
@Bean
public Queue delayQueue(){
return QueueBuilder.nonDurable("delayQueue")
.deadLetterExchange("delay-letter-exchange")
.deadLetterRoutingKey("delay-letter-routing-key")
.build();
}
@Bean
public Binding BingTTL(){
return BindingBuilder.bind(delayQueue()).to(directExchange()).with("ttl");
}
//声明延迟队列
@Bean
public Binding BingDeadQueue(){
return BindingBuilder.bind(deadLetterQueue()).to(deadLetterExchange()).with("dead-letter-routing-key");
}
// 声明延时交换机
@Bean
public DirectExchange delayLetterExchange() {
return new DirectExchange("delay-letter-exchange");
}
// 声明延时队列
@Bean
public Queue delayLetterQueue() {
return QueueBuilder.nonDurable("delay-letter-queue")
.build();
}
//使用方法
public void sendTTLMessage(String exchange, String key, String message, long ttlInMillis) {
Message buildMessage = MessageBuilder.withBody(message.getBytes(StandardCharsets.UTF_8))
.setExpiration(String.valueOf(ttlInMillis))//与插件延迟队列不同,配置位置不同
.build();
rabbitTemplate.convertAndSend(exchange, key, buildMessage);
}
-
使用延迟插件(Delay Plugin): RabbitMQ 提供了延迟插件,可以直接在 RabbitMQ 中实现延迟队列的功能。该插件允许你创建延迟队列,并设置消息的延迟时间,消息会在指定的延迟时间后被投递到指定的队列中。
1.下载/安装/启动延迟插件,地址中都有详细步骤 地址:https://github.com/rabbitmq/rabbitmq-delayed-message-exchange 2.在客户端进行延迟队列声明测试 /* -------------------------------------- 使用延迟插件实现延迟队列 */ // 声明延时交换机 @Bean public CustomExchange delayPluginExchange() { HashMap<String, Object> properties = new HashMap<>(); properties.put("x-delayed-type", "direct"); return new CustomExchange("delay-Plugin-exchange","x-delayed-message",false,false,properties); } // 声明延时队列 @Bean public Queue delayPluginQueue() { return QueueBuilder.nonDurable("delay-Plugin-queue") .build(); } @Bean public Binding BingPluginQueue(@Qualifier("delayPluginQueue")Queue queue,@Qualifier("delayPluginExchange")Exchange exchange){ return BindingBuilder.bind(queue).to(exchange).with("delay-Plugin-routing-key").noargs(); } 正常发送测试即可 public void sendTTLMessage(String exchange, String key, String message, long ttlInMillis) { Message buildMessage = MessageBuilder.withBody(message.getBytes(StandardCharsets.UTF_8)) .setHeader("x-delay",ttlInMillis) .build(); rabbitTemplate.convertAndSend(exchange, key, buildMessage); } //注意这里的延迟配置需配置到header中 使用该插件发送消息会触发ReturnsCallback回调函数,无须担心正常现象,因为消息发送之后不会立即推送到队列,需要等他ttl时间结束进行推送,所以会触发该现象
11.事务如何使用
- 开启事务:在发送消息之前,需要先开启事务。
- 执行消息发送操作:在事务中执行消息的发送操作。
- 提交事务:在所有的消息发送操作完成之后,提交事务。如果所有的消息发送操作都成功,事务将会被提交,消息会被发送到 RabbitMQ 服务器。如果任何一个消息发送操作失败,事务将会被回滚,消息不会被发送到 RabbitMQ 服务器。
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
public class MyProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private ConnectionFactory connectionFactory;
@Transactional
public void sendMessageInTransaction(String exchange, String routingKey, String message) {
// 开启事务
rabbitTemplate.setChannelTransacted(true);
try {
// 执行消息发送操作
rabbitTemplate.convertAndSend(exchange, routingKey, message);
// 提交事务
rabbitTemplate.getConnectionFactory().getTransactionManager().commit();
} catch (Exception e) {
// 回滚事务
rabbitTemplate.getConnectionFactory().getTransactionManager().rollback();
throw new RuntimeException("Failed to send message in transaction", e);
}
}
}
12.如何全局配置消息持久化
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.amqp.support.converter.SimpleMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfig {
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory, MessageConverter messageConverter) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(messageConverter);
return rabbitTemplate;
}
@Bean
public MessageConverter messageConverter() {
return new MyMessageConverter();
}
private static class MyMessageConverter extends SimpleMessageConverter {
@Override
protected Message createMessage(Object object, MessageProperties messageProperties) {
// 设置消息的持久化方式为持久化
messageProperties.setDeliveryMode(MessageProperties.DeliveryMode.PERSISTENT);
return super.createMessage(object, messageProperties);
}
}
}
在上面的示例中,通过自定义 MessageConverter
,重写了 createMessage
方法,在创建消息时设置了消息的持久化方式为持久化(MessageProperties.DeliveryMode.PERSISTENT
)。然后将这个自定义的 MessageConverter
注入到 RabbitTemplate
中,确保所有消息都使用这个消息转换器进行转换,并且都按照相同的方式进行持久化。
这样,所有通过 RabbitTemplate
发送的消息都会使用持久化的交付模式,实现了全局的消息持久化配置。
13.出现大量消息堆积怎么办?
-
增加消费者数量: 如果消息堆积是因为消费者无法及时处理消息导致的,可以尝试增加消费者数量,以加快消息处理速度。确保你的消费者逻辑能够处理消息的速度跟得上消息的产生速度。
-
优化消费者逻辑: 优化消费者逻辑,确保消费者能够尽快处理消息。可能的优化包括减少数据库查询、提高代码效率等。
-
增加队列和消费者: 如果可能的话,可以考虑将大量消息分散到多个队列和消费者中,以减轻单个队列和消费者的负载压力。
-
消息重试机制: 对于处理失败的消息,实现一个消息重试机制,使其能够重新排队并重新处理。可以根据消息的重试次数进行适当的延迟重试,或者将无法处理的消息发送到死信队列,以便后续分析和处理。
-
增加服务器资源: 如果消息堆积是由于 RabbitMQ 服务器资源不足导致的,可以考虑增加服务器的 CPU、内存等资源,或者使用集群模式来分担负载。
-
监控和警报: 配置监控和警报系统,及时发现和处理消息堆积问题。可以使用 RabbitMQ 自带的管理插件或第三方监控工具来监控队列的状态和消息的堆积情况,并设置警报规则,及时发现异常情况并采取相应的措施。
-
定期清理历史数据: 定期清理过期或不再需要的消息,以减少队列的负载和消息堆积的风险。