1.RabbitMq运行机制
发送者(Producer) --> 交换机(Exchange) --> 队列(Queues) --> 消费者(Consumer)
2.交换机(Exchange) 类型
direct(直接):消息精确发送,而且只能到达一个队列
fanout(扇出):消息会分发到所有绑定的队列上。
topic(主题):与fanout类似,但是多了个属性路由key绑定,消息会发送到路由key对应的队列上
3.spring整合rabbitmq
3.1 导入amqp依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
3.2 yml配置rabbitmq
rabbitmq:
host: 128.122.111.68
port: 5672
username: root
password: 123456
virtual-host: /
#开启发送端确认
publisher-confirms: true
#开启发送端信息抵达队列的确认
publisher-returns: true
#只要抵达队列,以异步发送优先回掉我们这个returnconfirm
template:
mandatory: true
#手动ack消息
listener:
simple:
acknowledge-mode: manual
3.3 在启动类上加上@EnableRabbit注解
3.4 配置 把RabbitMQ接收和发送的数据转为json
1. AmqpAdmin:管理组件
2. RabbitTemplate:消息发送处理组件
3. @RabbitListener 监听消息的方法可以有三种参数(不分数量,顺序)
• Object content, Message message, Channel
package com.wanxin.gulimall.order.config;
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;
/**
* Rabbitmq 消息json化
*
* @author common
* @date 2022/05/23
*/
@Configuration
public class MyRabbitConfig {
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
}
3.5 测试发送消息
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class GulimallOrderApplicationTests {
// 可以创建交换机,队列,并管理
@Autowired
private AmqpAdmin amqpAdmin;
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 第一步:创建交换机
* 1、如何创建Exchange、Queue、Binding
* 1)、使用AmqpAdmin进行创建
* 2、如何收发消息
*/
@Test
public void createExchange() {
/**
* name 名称
* durable 是否持久化
* autoDelete 是否可自动删除,没有绑定任何队列时删除
*/
Exchange directExchange = new DirectExchange("hello-java-exchange",true,false);
amqpAdmin.declareExchange(directExchange);
log.info("Exchange[{}]创建成功:","hello-java-exchange");
}
/**
* 第二步:创建队列
*/
@Test
public void testCreateQueue() {
/**
* name 名称
* durable 是否持久化
* exclusive 是否排它, 如果有一个连接了其他就无法连接它,这个最好设置为false
* autoDelete 是否可自动删除,没有绑定任何队列时删除
*/
Queue queue = new Queue("hello-java-queue",true,false,false);
amqpAdmin.declareQueue(queue);
log.info("Queue[{}]创建成功:","hello-java-queue");
}
/**
*第三步:交换机与队列进行绑定
*/
@Test
public void createBinding() {
/**
* String destination, 目的地,队列名称
* Binding.DestinationType destinationType, 类型,可以绑定队列或者交换机
* String exchange, 交换机名称
* String routingKey, 路由键
* Map<String, Object> arguments 参数
*/
Binding binding = new Binding("hello-java-queue",
Binding.DestinationType.QUEUE,
"hello-java-exchange",
"hello.java",
null);
amqpAdmin.declareBinding(binding);
log.info("Binding[{}]创建成功:","hello-java-binding");
}
/**
* 第四步:交换机发送消息
*/
@Test
public void sendMessageTest() {
OrderReturnReasonEntity reasonEntity = new OrderReturnReasonEntity();
reasonEntity.setId(1L);
reasonEntity.setCreateTime(new Date());
reasonEntity.setName("reason");
reasonEntity.setStatus(1);
reasonEntity.setSort(2);
String msg = "Hello World";
//1、发送消息,如果发送的消息是个对象,会使用序列化机制,将对象写出去,对象必须实现Serializable接口
//2、发送的对象类型的消息,可以是一个json
/**
* String exchange, 交换机
* String routingKey, 路由键,要与第四步绑定时设置的路由键有关系才能接收
* Object object, 发送的对象,如果时对象那么这个对象必须是序列化的
* @Nullable CorrelationData correlationData
*/
rabbitTemplate.convertAndSend("hello-java-exchange","hello2.java",
reasonEntity,new CorrelationData(UUID.randomUUID().toString()));
rabbitTemplate.convertAndSend("hello-java-exchange","hello.java",
reasonEntity,new CorrelationData(UUID.randomUUID().toString()));
log.info("消息发送完成:{}",reasonEntity);
}
// @Test
// public void create() {
// HashMap<String, Object> arguments = new HashMap<>();
// arguments.put("x-dead-letter-exchange", "order-event-exchange");
// arguments.put("x-dead-letter-routing-key", "order.release.order");
// arguments.put("x-message-ttl", 60000); // 消息过期时间 1分钟
// Queue queue = new Queue("order.delay.queue", true, false, false, arguments);
// amqpAdmin.declareQueue(queue);
// log.info("Queue[{}]创建成功:","order.delay.queue");
// }
}
3.5 测试接收消息
1.@RabbitListener 类+方法上(监听哪些队列)
2.@RabbitHandler 标在方法上(重载区分不同的消息)
/**
* queues:声明需要监听的队列
* message : 如果我们不知道发送的对象类型,可以统一用org.springframework.amqp.core.Message 来接收
* OrderReturnReasonEntity:接收消息的对象,当时传的是什么类型的对象,接收的时候就是什么类型的对象
* Message message,OrderReturnReasonEntity 这两个参数写一个就可以
* channel:当前传输数据的通道
*/
@RabbitListener(queues = {"hello-java-queue"})
public void revieveMessage(Message message, OrderReturnReasonEntity content, Channel channel) {
//拿到主体内容
byte[] body = message.getBody();
//拿到的消息头属性信息
MessageProperties messageProperties = message.getMessageProperties();
System.out.println("接受到的消息...内容" + message + "===内容:" + content.getClass());
}
4.RabbitMq信息确认机制
@发送端
1.ConfirmCallback : Broker收到消息就回调(发送端 --> Broker)
1.1 publisher-confirms: true
1.2 设置确认回调ConfirmCallback
2.消息未正确抵达Queue进行回调(Exchange --> Queue)
2.1 publisher-returns: true
template:
mandatory: true
2.2 设置确认回调ReturnCallback
@消费端(保证每一个消息被正确消费, 此时才可以broker删除这个消息)
1.默认是自动确认的,只要消息接收到,客户端就会自动确认,服务端就会移除这个消息
问题:
我们收到很多消息,自动回复给服务器ack,只有一个消息处理成功,宕机了,发生消息丢失;
解决方法:
消费者手动确认模式。只要我们没有明确动告诉MQ,消息没有ack(货物没有被签收),
消息就一直是unacked(未被接收)状态,即使Consumer(消费者)宕机,消息也不会丢失,会重新变为Ready(准备被接收)状态,
下一次有新的Consumer(消费者)连接进来就发给他
2.如何签收
channel.basicAck(deliveryTag,false); 签收: 业务成功就应该签收
channel.basicNack(deliveryTag,false,false); 拒签: 业务失败就应该拒签
4.1 java代码配置消息确认配置
package com.wanxin.gulimall.order.config;
import org.springframework.amqp.core.Message;
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.web.bind.annotation.PostMapping;
import javax.annotation.Resource;
/**
* Rabbitmq 消息json序列化
*
* @author common
* @date 2022/05/23
*/
@Configuration
public class MyRabbitConfig {
@Resource
RabbitTemplate rabbitTemplate;
/**
* 消息json序列化
*
* @return {@link MessageConverter}
*/
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
/**
* 定制RabbitTemplate
* MyRabbitConfig对象创建完成以后,执行这个方法
*
* @发送端
* 1.Broker收到消息就回调(发送者 --> Broker)
* 1. publisher-confirms: true
* 2. 设置确认回调ConfirmCallback
*
* 2.消息正确抵达Queue进行回调(Exchange --> Queue)
* 1. publisher-returns: true
* template:
* mandatory: true
* 2.设置确认回调ReturnCallback
*
* @消费端 (保证每一个消息被正确消费, 此时才可以broker删除这个消息)
* 1.默认是自动确认的,只要消息接收到,客户端就会自动确认,服务端就会移除这个消息
* 1. listener:
* simple:
* acknowledge-mode: manual
* 问题:
* 我们收到很多消息,自动回复给服务器ack,只有一个消息处理成功,宕机了,发生消息丢失;
* 解决方法:
* 消费者手动确认模式。只要我们没有明确动告诉MQ,消息没有ack(货物没有被签收),
* 消息就一直是unacked(未被接收)状态,即使Consumer(消费者)宕机,消息也不会丢失,会重新变为Ready(准备被接收)状态,
* 下一次有新的Consumer(消费者)连接进来就发给他
*
* 2.如何签收
* channel.basicAck(deliveryTag,false); 签收: 业务成功就应该签收
* channel.basicNack(deliveryTag,false,false); 拒签: 业务失败就应该拒签
*
*/
@PostMapping
public void initRabbitTemplate(){
//消息抵达Broker的确认回调
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
* 确认
* 1.只要消息抵达Broker就 b=true
* @param correlationData 当前消息的唯一关联数据(这个是消息的唯一id)
* @param b 消息是否成功到达Broker
* @param s 失败原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
System.out.println("confirm。。。correlationData["+correlationData+"]==>b["+b+"]==>s["+s+"]");
}
});
//消息抵达Queue的确认回调
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
/**
* 返回消息
* 只要消息没有抵达指定的Queue,就会触发这个失败回调
* @param message 投递失败的消息的详情信息
* @param i 回复的状态码
* @param s 回复的文本内容
* @param s1 当时这个消息发给哪个交换机
* @param s2 当时这个消息用的哪个路由键
*/
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
System.out.println("Fail Message["+message+"]==>i["+i+"]==>s["+s+"]==>s1["+s1+"]==>s2["+s2+"]");
}
});
}
}