消息中间件
一、概述
二、RabbitMQ概念
三、Docker安装RabbitMQ
四、RabbitMQ运行机制
1.Exchange 类型
direct:直接
fanout:扇出
topic:主题
比如使用的是topic主题交换机,设置消息路由键 atguigu.news,如下图交换机路由键,四个队列都能匹配
五、RabbitMQ整合
1.引入 spring-boot-starter-amqp
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2.application.yml配置
# RabbitMQ配置
spring.rabbitmq.host=IP地址
spring.rabbitmq.port=5672
# 虚拟主机配置
spring.rabbitmq.virtual-host=/
# 开启发送端消息抵达Broker确认
spring.rabbitmq.publisher-confirms=true
# 开启发送端消息抵达Queue确认
spring.rabbitmq.publisher-returns=true
# 只要消息抵达Queue,就会异步发送优先回调returnfirm
spring.rabbitmq.template.mandatory=true
# 手动ack消息,不使用默认的消费端确认
spring.rabbitmq.listener.simple.acknowledge-mode=manual
3.启动类
/**
* 使用RabbitMQ
* 1、引入amqp场景;RabbitAutoConfiguration就会自动生效
* 2、给容器中自动配置了
* RabbitTemplate、AmqpAdmin、CachingConnectionFactory、RabbitMessagingTemplate;
* 所有属性都是spring.rabbitmq
* @ConfigurationProperties(prefix = "spring.rabbitmq")
* 3、给配置文件中配置 spring.rabbitmq信息
* 4、@EnableRabbit:@EnableXxxxx;开启功能
* 5、监听消息:使用@RabbitListener;必须有@EnableRabbit(如果只是发送消息可以不使用@EnableRabbit)
* @RabbitListener: 类 + 方法上(监听哪些队列)
* @RabbitHandler:标在方法上 (重载区分不同的消息:同一队列不同消息,不同队列的消息)
*/
@EnableRabbit
@SpringBootApplication
public class GulimallOrderApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallOrderApplication.class, args);
}
}
4.如何创建Exchange,Queue,Binding并发送消息
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
@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() {
//参数1(name):创建名为"hello-java-exchange"的路由,
//参数2(durable):是否持久化
//参数3(autoDelete):是否自动删除
Exchange directExchange = new DirectExchange("hello-java-exchange",true,false);
amqpAdmin.declareExchange(directExchange);
log.info("Exchange[{}]创建成功:","hello-java-exchange");
}
@Test
public void testCreateQueue() {
//参数1(name):创建名为"hello-java-exchange"的队列,
//参数2(durable):消息是否持久化
//参数3(exclusive):是否排他,如果为true:指定连接才能消费消息
//参数4(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[目的地]
// DestinationType destinationType[目的地类型,交换机和交换机绑定,交换机和队列绑定]
//String exchange[交换机]
//String routingKey[路由键]
//Map<String,Object> arguments[自定义参数]
//将exchange指定的交换机和destination目的地进行绑定,使用routingKey作为指定的路由键
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
rabbitTemplate.convertAndSend("hello-java-exchange","hello2.java",
reasonEntity);
log.info("消息发送完成:{}",reasonEntity);
}
}
默认发送消息使用的Java序列化,如果想要使用json序列化,自定义配置类
@Configuration
public class MyRabbitConfig{
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
5.接受消息
/**
* 1.queues:声明需要监听的队列
* 2.T<发送的消息的类型> OrderReturnReasonEntity content;
* 3.channel:当前传输数据的通道
*
*Queue :可以很多人都来监听。只要收到消息,队列就会删除消息,而且只能有一个收到此消息
*场景:
* 1)、订单服务启动多个;同一个消息,只能有一个客户端收到(同一个订单服务启动多个,向同一队列中发送消息,发现多个服务轮询接受到消息)
* 2)、只有一个消息处理完,方法运行结束,我们就可以接收到下一个消息(启动一个服务,在接受消息方法中加入线程睡眠,发现只有一个消息处理完,才会接受另一个消息)
*/
@RabbitListener(queues = {"hello-java-queue"})
public void revieveMessage(Message message, 实体类类型 content) {
//拿到主体内容
byte[] body = message.getBody();
//拿到的消息头属性信息
MessageProperties messageProperties = message.getMessageProperties();
System.out.println("接受到的消息...内容" + message + "===内容:" + content);
}
接受不同类型的消息
public class RabbitController{
@Autowite
RabbitTemplate rabbitTemplate;
@GetMapping("/sendMq")
public String sendMq(){
for(int i = 0 ;i < 10 ;i++){
if(i % 2 == 0){
OrderReturnReasonEntity reasonEntity = new OrderReturnReasonEntity();
reasonEntity.setId(1L);
reasonEntity.setCreateTime(new Date());
reasonEntity.setName("reason");
String msg = "Hello World";
//1、发送消息,如果发送的消息是个对象,会使用序列化机制,将对象写出去,对象必须实现Serializable接口
//2、发送的对象类型的消息,可以是一个json
rabbitTemplate.convertAndSend("hello-java-exchange","hello.java",reasonEntity,new CorrelationData(UUID.randomUUID().toString()));
log.info("消息发送完成:{}",reasonEntity);
}else{
OrderEntity entity = new OrderEntiy();
entity.setOrderSn("1111");
rabbitTemplate.convertAndSend("hello-java-exchange","hello.java", entity,new CorrelationData(UUID.randomUUID().toString()));
}
}
}
}
@RabbitListener(queues = {"hello-java-queue"})
public class OrderItemServiceImpl{
@RabbitHandler
public void revieveMessage(OrderReturnReasonEntity content) {
System.out.println("接受到的消息...内容" + message + "===内容:" + content);
}
@RabbitHandler
public void revieveMessage2(OrderEntity content) {
System.out.println("接受到的消息...内容" + message + "===内容:" + content);
}
}
六、RabbitMQ消息确认机制-可靠抵达
可靠抵达-ConfirmCallback
# 开启发送端消息抵达Broker确认
spring.rabbitmq.publisher-confirms=true
# 新版本更换为
spring.rabbitmq.publisher-confirm-type: correlated
@Configuration
public class MyRabbitMQConfig {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 定制RabbitTemplate
* 1、服务收到消息就回调
* 1、spring.rabbitmq.publisher-conforms=true
* 2、设置确定回调
*/
@PostConstruct
public void initRabbitTemplate() {
//设置确认回调
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
* 1、只要消息抵达Broker就ack=true
* @param correlationData 当前消息的唯一的关联数据(这个是消息的唯一id)
* @param ack 消息是否成功收到
* @param cause 失败的原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("confirm...correlationData[" + correlationData + "]==>ack[" + ack + "]===>cause[" + cause + "]");
}
});
}
}
可靠抵达-ReturnCallback
# 开启发送端消息抵达Queue确认
spring.rabbitmq.publisher-returns=true
# 只要消息抵达Queue,就会异步发送优先回调returnfirm
spring.rabbitmq.template.mandatory=true
@Configuration
public class MyRabbitMQConfig {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 定制RabbitTemplate
* 1、服务收到消息就回调
* 1、spring.rabbitmq.publisher-conforms=true
* 2、设置确定回调
* 2、消息正确抵达队列进行回调
* 1、spring.rabbitmq.publisher-returns=true
* 2、spring.rabbitmq.template.mandatory=true
* 3、消费者确定(保证每个消息被正确消费,此时才可以broker删除这个消息)
*/
@PostConstruct
public void initRabbitTemplate() {
//设置确认回调
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
* 1、只要消息抵达Broker就ack=true
* @param correlationData 当前消息的唯一的关联数据(这个是消息的唯一id)
* @param ack 消息是否成功收到
* @param cause 失败的原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("confirm...correlationData[" + correlationData + "]==>ack[" + ack + "]===>cause[" + cause + "]");
}
});
// 设置消息抵达队列的确定回调(当消息路由不到消息队列中时才会调用该方法)
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
/**
*
* @param message 投递失败的信息详细信息
* @param replyCode 回复的状态码
* @param replyText 回复的文本内容
* @param exchange 当时这个消息发送给哪个交换机
* @param routingKey 当时这个消息用哪个路由键
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("message = " + message + ", replyCode = " + replyCode + ", replyText = " + replyText + ", exchange = " + exchange + ", routingKey = " + routingKey);
}
});
}
}
可靠抵达-Ack消息确认机制
# 手动ack消息,不使用默认的消费端确认
spring.rabbitmq.listener.simple.acknowledge-mode=manual
/**
* 1.queues:声明需要监听的队列
* 2.T<发送的消息的类型> OrderReturnReasonEntity content;
* 3.channel:当前传输数据的通道
*
*Queue :可以很多人都来监听。只要收到消息,队列就会删除消息,而且只能有一个收到此消息
*场景:
* 1)、订单服务启动多个;同一个消息,只能有一个客户端收到(同一个订单服务启动多个,向同一队列中发送消息,发现多个服务轮询接受到消息)
* 2)、只有一个消息处理完,方法运行结束,我们就可以接收到下一个消息(启动一个服务,在接受消息方法中加入线程睡眠,发现只有一个消息处理完,才会接受另一个消息)
*/
// @RabbitListener(queues = {"hello-java-queue"})
@RabbitHandler
public void revieveMessage(Message message,
实体类类型 content,
Channel channel) {
//拿到主体内容
byte[] body = message.getBody();
//拿到的消息头属性信息
MessageProperties messageProperties = message.getMessageProperties();
System.out.println("接受到的消息...内容" + message + "===内容:" + content);
// channel内按顺序自增的。
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.println("deliveryTag ==> " + deliveryTag);
// 手动签收货物,非批量模式
try {
if (deliveryTag % 2 == 0){
// 收货
channel.basicAck(deliveryTag,false);
System.out.println("签收了货物...."+deliveryTag);
}else{
// 退货 requeue=false丢弃 requeue=true 发回服务器,服务器重新入队。
// long deliveryTag, boolean multiple, boolean requeue
channel.basicNack(deliveryTag,false,true);
// (long deliveryTag, boolean requeue)
// channel.basicReject();
System.out.println("没有签收货物...."+deliveryTag);
}
} catch (Exception e) {
// 网络中断
e.printStackTrace();
}
}
/**
* 定制RabbitTemplate
* 1、服务收到消息就回调
* 1、spring.rabbitmq.publisher-conforms=true
* 2、设置确定回调
* 2、消息正确抵达队列进行回调
* 1、spring.rabbitmq.publisher-returns=true
* 2、spring.rabbitmq.template.mandatory=true
* 3、消费者确定(保证每个消息被正确消费,此时才可以broker删除这个消息)
* spring.rabbitmq.listener.simple.acknowledge-mode=manual
* 1、默认是自动确认的,只要消息接受到,客户端会自动确认,服务端就会移除这个消息
* 问题:
* 我们收到很多消息,自动回复给服务器ack,只有一个消息处理成功,宕机了。发生消息丢失;
* 消费者手动确认模式。只要我们没有明确告诉MQ,货物被签收。没有Ack,
* 消息就一直是unacked状态。即使Consumer宕机。消息不会丢失,会重新变为Ready,下一次有新的consumer进来,就发给它
* 2、如何签收
* channel.basicAck(deliveryTag,false);签收;业务成功完成就应该签收
* channel.basicNack(deliveryTag,false,true);拒签;业务失败,拒签
*/
七、RabbitMQ延时队列(实现定时任务)
延时队列场景
定时任务的时效性问题
消息的TTL(Time To Live)
Dead Letter Exchanges(DLX)死信路由
延时队列实现-1
延时队列实现-2
具体实现流程1
具体实现流程2
SpringBoot中使用延时队列
八、如何保证消息可靠性
消息丢失
消息重复
消息积压
实操
@Configuration
public class MyRabbitMQConfig {
//@Bean Binding,Queue,Exchange(并且有监听消息监听时自动创建(使用@RabbitListener(queue = "")))
/**
* 容器中的 Binding,Queue,Exchange都会自动创建(RabbitNQ没有的情况)
* RabbitMQ只要有。@Bean声明属性发生变化也不会覆盖
* 死信队列
* @return
*/
@Bean
public Queue orderDelayQueue() {
/*
Queue(String name, 队列名字
boolean durable, 是否持久化
boolean exclusive, 是否排他
boolean autoDelete, 是否自动删除
Map<String, Object> arguments) 属性
*/
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);
return queue;
}
/**
* 普通队列
* @return
*/
@Bean
public Queue orderReleaseQueue() {
Queue queue = new Queue("order.release.order.queue", true, false, false);
return queue;
}
/**
* TopicExchange
* @return
*/
@Bean
public Exchange orderEventExchange() {
/*
* String name,
* boolean durable,
* boolean autoDelete,
* Map<String, Object> arguments
* */
return new TopicExchange("order-event-exchange", true, false);
}
@Bean
public Binding orderCreateOrderBinding() {
/*
* String destination, 目的地(队列名或者交换机名字)
* DestinationType destinationType, 目的地类型(Queue、Exhcange)
* String exchange,
* String routingKey,
* Map<String, Object> arguments
* */
return new Binding("order.delay.queue",
Binding.DestinationType.QUEUE,
"order-event-exchange",
"order.create.order",
null);
}
@Bean
public Binding orderReleaseOrderBinding() {
return new Binding("order.release.order.queue",
Binding.DestinationType.QUEUE,
"order-event-exchange",
"order.release.order",
null);
}
}
商城图:
本文由mdnice多平台发布