生产者
在使用rabbitmq的时候,消息的发送方会杜绝消息的丢失或者投递失败的场景,所以rabbitmq为我们提供了两种解决方式:
1、confirm
2、return
rabbitmq整个消息投递的路径为:
producer—>rabbitmq broker—>exchange—>queue—>consumer
当消息从producer—>exchange 会返回一个confirmReturn;
消息从producer—>queue 会返回一个returnCallback
我们利用这两个来保证消息的可靠性投递
java 代码:
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class RabbitInitConfig {
RabbitTemplate rabbitTemplate;
@Primary
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
RabbitTemplate rabbitTemplate=new RabbitTemplate(connectionFactory);
this.rabbitTemplate=rabbitTemplate;
initRabbitmq();
rabbitTemplate.setMessageConverter(messageConverter());
return rabbitTemplate;
}
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
public void initRabbitmq(){
//消息抵达broke触发回调
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
System.out.println("confirm方法被触发了:"+s);
if(b){
//接收成功
System.out.println("接收消息成功"+s);
}else {
System.out.println("接收消息失败"+s);
}
}
});
//消息return机制
rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
System.out.println("return触发"+returnedMessage);
}
});
}
}
配置文件
#开始confirm模式
spring.rabbitmq.publisher-confirms=true
#开始return模式
spring.rabbitmq.publisher-returns=true
消费者
consumer ACK
消费者有三种接收消息的方式:
默认自动确认,消息被consumer取到后,会自动确认,同时将队列中的该消息移除
我们也可以修改消息为手动确认,手动ack需要调用channel.basicAck()
如果出现异常,可以调用channel.basicNack(),进行重复发送
一般推荐手动确认,保证消息的可靠性
配置
#手动确认ack
spring.rabbitmq.listener.direct.acknowledge-mode=manual
#设置消费端一次拉去多少消息
spring.rabbitmq.listener.simple.prefetch=1000
消费端的确认模式一定为手动确认channel.basicAck()
延时队列=死信队列+TTL
延时队列在工作中会经常用到:如下订单30分钟未支付…
死信队列
生产者将消息发送到队列后,消息一定时间内未被消费,超过了TTL设置的时间,就会进入到死信交换机,然后由相应的队列进行消费
代码
package org.ww.boot_producer_rabbitmq.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitmqConfig {
//------------------------------------延时队列---------------------------------------------------
@Bean("createOrderQueue")
public Queue createOrderQueue(){
return QueueBuilder.durable("order.delay.order")
.deadLetterExchange("order-event-exchange")
.deadLetterRoutingKey("order.relase.order")
.ttl(5000).build();
}
@Bean("relaseQueue")
public Queue relaseQueue(){
return QueueBuilder.durable("order.relase.order.queue").build();
}
//死信交换机
@Bean("exchange")
public Exchange deadInfoExchange(){
return ExchangeBuilder.topicExchange("order-event-exchange").durable(true).build();
}
//创建绑定关系
@Bean
public Binding createBind(@Qualifier("createOrderQueue") Queue createQueue,@Qualifier("exchange") Exchange exchange){
return BindingBuilder.bind(createQueue).to(exchange).with("order.create.#").noargs();
}
@Bean
public Binding releaseOrderBingding(@Qualifier("relaseQueue") Queue relaseQueue,@Qualifier("exchange") Exchange exchange){
return BindingBuilder.bind(relaseQueue).to(exchange).with("order.relase.#").noargs();
}
}
监听
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
@Component
public class DeadMsgConfig{
// @RabbitListener(queues = "order.delay.order")
// @RabbitHandler
// public void onMessage(String msg, Channel channel,Message message) throws Exception {
//
// try {
// System.out.println("延时队列处理业务逻辑"+msg.toString());
// //手动接收
// channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
// }catch (Exception e){
// //拒绝
// channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
// //第三个参数,重回队列,如果设置为true,则消息重新回到队列,broker会重新发送该消息给消费端
//// channel.basicNack(message.getMessageProperties().getDeliveryTag(),true, true);
// e.printStackTrace();
// }
//
// }
@RabbitListener(queues = "order.relase.order.queue")
@RabbitHandler
public void relase(String msg, Channel channel,Message message) throws Exception {
try {
System.out.println("最终队列处理业务逻辑----"+msg.toString());
//手动接收
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}catch (Exception e){
//拒绝
channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
//第三个参数,重回队列,如果设置为true,则消息重新回到队列,broker会重新发送该消息给消费端
// channel.basicNack(message.getMessageProperties().getDeliveryTag(),true, true);
e.printStackTrace();
}
}
}
幂等性:指一次或多次请求一个资源,对于资源本身应该具有相同的结果,也就是说,其多次执行的结果和进行一次执行的结果应该相同
在MQ中指,多次消费一条消息与消费一条消息的结果一样
消息幂等性保障:可以使用乐观锁机制
解决:
1、上多个消费者
2、将消息先取出来放数据库里在慢慢处理
在第一次消费该消息时,以messageId为key放入到redis中(设置个过期时间),下次在消费之前进行判断这个key有没有在redis中存在,存在就进行手动确认。