开发环境:rabbitmq-server-3.6.10、otp_win64_19.3(erlang版本);
在安装过程中,可能遇到以下问题:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
publisher-confirms: true
publisher-returns: true
template:
mandatory: true
cache:
channel:
size: 100
其中publisher-confirms和publisher-returns是消息投递到MQ的回调,在代码中配置消息回调后的执行方案,mandatory设置为true表示routingkey找不到exchange绑定的queue就return给生产者,否则直接将消息丢弃。
CorrelationData correlationData = new CorrelationData();
String uuid = UUID.randomUUID().toString();
correlationData.setId(uuid);
redisTemplate.opsForValue().set(uuid, msg);
rabbitTemplate.convertAndSend("exchange1", "routingKey1", msg, correlationData);
@Configuration
public class RabbitmqConfiguration {
@Bean
public Queue queue() {
return new Queue("queue1");
}
@Bean
public DirectExchange directExchange() {
return new DirectExchange("exchange1");
}
@Bean
public Binding binding() {
Binding binding = BindingBuilder.bind(queue()).to(directExchange()).with("routingKey1");
return binding;
}
@Autowired
private RedisTemplate redisTemplate;
/**
* 设置消息可靠性传递
* @param connectionFactory
* @return
*/
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
//消息发送到交换器时回调
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
System.out.println("redis key: " + correlationData.getId());
System.out.println(s);
if(b) {
System.out.println("delete pre-msg in redis");
redisTemplate.delete(correlationData.getId());
} else {
//可以设置定时任务
System.out.println("try to resend msg");
}
}
});
// rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
// if(ack) {
// System.out.println("delete pre-msg in redis");
// } else {
// System.out.println(correlationData.toString());
// System.out.println(s.toString());
// }
// });
//消息发送到交换器,但无队列与交换器绑定时回调;
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println(message.toString());
System.out.println(replyCode);
System.out.println(replyText);
System.out.println(exchange);
System.out.println(routingKey);
}
});
// rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
// System.out.println(message.toString());
// System.out.println(replyCode);
// System.out.println(replyText);
// System.out.println(exchange);
// System.out.println(routingKey);
// });
return rabbitTemplate;
}
}
本例采用direct模式。通过routingkey(routingKey1)找到与交换机(exchange1)绑定的队列(queue1)。
RabbitTemplate配置可靠性传输,需设置为原型模式SCOPE_PROTOTYPE,由于每次消息投递回调对应不同的线程,如果采用单例,回调时无法确定对应的消息id。
rabbitTemplate.setConfirmCallback中匿名类方法实现confirm()参数:CorrelationData为消息标志,boolean b代表是否投递成功。Stirng s不详,反正打印为null。
rabbitTemplate.setReturnCallback消息发送到交换器,但无队列与交换器绑定时回调。
除了保障生产者的消息投递,还需要保障消费者的成功消费。
listener:
simple:
acknowledge-mode: manual
prefetch: 10
retry:
enabled: true
max-attempts: 3
@Component
@RabbitListener(queues = "queue1")
public class RabbitmqListener {
/**
*
* @throws Exception
* 只有异常抛出时,才会触发消息重发机制
*/
@RabbitHandler
public void process(String msg, Channel channel, Message messages) throws Exception {
try {
System.out.println(msg);
//消费序列,从1开始
System.out.println(messages.getMessageProperties().getDeliveryTag());
//MessageProperties [headers={}, contentType=application/x-java-serialized-object, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, redelivered=true, receivedExchange=exchange1, receivedRoutingKey=routingKey1, deliveryTag=3, consumerTag=amq.ctag-EetiB3hRZg_g2KVLKJSP2Q, consumerQueue=queue1]
System.out.println(messages.getMessageProperties().toString());
//null
System.out.println(messages.getMessageProperties().getCorrelationId());
int i = 1/0;
channel.basicAck(messages.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
System.out.println("IO Exception");
/**
* param1:消息唯一ID,由MQ生成
* param2:批量确认包括当前ID之前的所有消息
* param3:是否回队
*/
// channel.basicNack(messages.getMessageProperties().getDeliveryTag(), false, true);
throw e;
}
}
}
只有异常抛出的时候才会触发消息重发机制,本例中故意设置一个除0异常做测试,重发3次后消息回队,下次启动后重新推送,当然也可以配置死信队列单独处理,但是要设置rabbitmq.listener.simple.default-requeue-rejected为false。同时,channel提供了一个basicNack方法,意思是消息回队,可以在异常时手动调用,回队后会由rabbittmq重新发送。
最后是要保障监听接口的幂等性,即防止消息重复消费,对于服务提供者或消费者而言,无论是HTTP还是MQ发送数据,都要做接口幂等的保证。常见的有保存标志信息的唯一ID,如果存在则丢弃。比如,如果是数据库操作,可以设置某个字段的唯一索引,或者使用悲观锁/乐观锁。