运用场景
异步处理,应用解耦,流量控制
基本结构
核心概念
Broker:消息队列服务进程,此进程包括两个部分:Exchange和Queue
Exchange:消息队列交换机,按一定的规则将消息路由转发到某个队列,对消息进行过虑。
Queue:消息队列,存储消息的队列,消息到达队列并转发给指定的消费者
Producer:消息生产者,即生产方客户端,生产方客户端将消息发送
Consumer:消息消费者,即消费方客户端,接收MQ转发的消息。
几种交换机
Direct exchange(直连交换机)
通过路由键(routing key)绑定队列,路由键为R的消息会通过交换机发送给路由键也为R的队列。
Fanout exchange(扇型交换机)
可多个队列绑定到这个交换机上,这个交换机收到消息之后会转发给所有绑定了他的队列。
Topic exchange(主题交换机)
队列通过路由键绑定到交换机上,然后,交换机根据消息里的路由值,根据路由规则将消息路由给一个或多个绑定队列。
路由规则:
* (星号) 用来表示一个单词 (必须出现的)
# (井号) 用来表示任意数量(零个或多个)单词
死信交换机(Dead Letter Exchange)
顾名思义接收死了的消息,死信一般是一下三种情况。
- 消息被拒绝,且设置requeue参数为false不重新放入队列
- 消息过期(消息默认不过期,但是可以设置过期时间)
- 队列达到最大长度
防止消息丢失
使用消息应答向消费者确认消息是否被消费。
可以让交换机、队列、消息都持久化,这样如果消息在消费者消费之前MQ宕机了也会保存消息。
消息应答
消费者处理完消息之后向生产者进行应答,告诉消费者已经处理完消息,如果消费者不应答,生产者不知道是否消费可能就会重复发消息。
默认开启自动应答。
手动应答:
channel.basicAck(deliveryTag, true);肯定应答,表示成功消费消息,第二个参数为true表示一次性确认 delivery_tag 小于等于传入值的所有消息。
channel.basicReject(deliveryTag, false);否定应答,表示消费失败,第二个参数false表示失败过后不重新将这条消息放入消息队列了。
消息分发策略
轮训分发:消费者轮休消费消息,如果有一个消费者很快一个很慢,这样就不合适。
不公平分发:通过设置参数channel.basicQos(1);实现不公平分发策略使得能者多劳。队列根据不同消费者消费消息的时间来判断消费者的消费能力,从而根据消费速度调整消息的分发。
预值分发: 定义通道上允许的未确认消息的最大数量。一旦未确认消息数量达到配置的最大数量,RabbitMQ 将停止在通道上传递更多消息,除非至少有一个未处理的消息被确认。
channel.basicQos(预值);
如何保证消息的顺序消费
如果有多个消费者消费同一个队列那怎么保证消费的顺序呢?
使用一个消费者根据业务数据关键值(订单id等)划分为多个消息集合,每一个消息集合由一个队列处理,比如说订单id为1的所有业务由队列1处理,id为2的由队列2处理。
队列高可用
普通集群模式:多个mq实例,但是其实还是从一个实例上获取数据,其他实例只是通过通讯手段获取主节点队列上的数据,如果队列所在的实例宕机了那就崩了。
镜像集群模式:多个mq实例,主节点的队列收到消息之后会广播给自己的从节点,但是消费者只从主节点获取消息,从节点只是为了保存主节点的消息在主节点驾崩之后上位不丢失数据。
整合springboot使用
1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
引入了这个依赖之后RabbitMQ的AutoConfiguration就会自动生效,然后在容器中配置了RabbitTemplate、AmqpAdmin、CachingConnectionFactory、RabbitMessageTemplate
2、开启功能
启动类加上注解@EnableRabbit
3、配置文件
spring.rabbitmq.host=192.168.229.3
spring.rabbitmq.port=5672
4、创建交换机(exchange)
使用容器中的AmqpAdmin对象创建
Exchangeexchange=newDirectExchange("hello", true, false);
amqpAdmin.declareExchange(exchange);
三个参数分别为:交换机的名字、是否开启持久化、是否自动删除。后两个参数默认就是true和false。还可以指定一个Map类型的参数。
5、创建队列(queue)
使用容器中的AmqpAdmin对象创建
Queuequeue=newQueue("hello", true, false, false);
amqpAdmin.declareQueue(queue);
参数含义:队列名称、是否持久化、是否排他、是否自动删除。还可以指定参数。
排他:只要有一条连接连上了这个队列别人就连不上这个队列。
6、创建绑定(Binding)
使用容器中的AmqpAdmin对象创建
Bindingbinding=newBinding("hello-queue",
Binding.DestinationType.QUEUE,
"hello-exchange",
"hello.java", null);
amqpAdmin.declareBinding(binding);
参数含义:目的地的名字、目的地类型、交换机名字、路由键、自定义参数。
7、发消息
使用容器中的RabbitTemplate
rabbitTemplate.convertAndSend("hello-exchange","hello.java","hello");
参数含义:交换机的名字、路由键、发送的内容。也可以指定第四个参数
new CorrelationData(new UUID.randomUUID().toString),这样可以指定消息的唯一id。
如果发送的内容是一个引用类型的对象,这个方法会自动解析,前提是这个对象的类实现了serialiable接口,或者可以创建配置文件使这个方法可以将对象转换成json对象。
如下:
@Beanpublic MessageConverter messageConverter(){
returnnewJackson2JsonMessageConverter();
}
这样就能将容器中注入的MessageConverter替换成Jackson2JsonMessageConverter了。
8、接受消息
使用@RabbitListener(queues = {"queueName1","queueName2"})注解,这个注解可以标在方法上也可以标在类上,表示这个接受哪些队列来的消息。
来自队列的消息可以用Message message来接收,这个对象有消息头信息和消息体信息,消息体就是传的内容。但是这个消息体对象是JSON对象,需要转化。所以可以传的是什么类型的数据就用什么类型来接收。
@RabbitHandle,只能标在方法上,表示用来接收队列消息的方法。配合@RabbitListener标在类上使用,表示这个类用来接收哪些队列的消息,然后哪些方法可以消费这些消息。使用场景:类接收到的消息不确定是什么类型的,可以声明多个方法并标上@RabbitHandle,参数接收不同的类型,根据队列中传过来的是什么类型来决定用哪个方法来消费消息,类似于重载。
9、可靠投递-发送端确认
配置文件
#消息发送到broker就确认回调
spring.rabbitmq.publisher-confirms=true
#消息抵达队列就确认回调
spring.rabbitmq.publisher-returns=true
在配置类中自定义容器中的RabbitTemplate
@PostConstruct//Config对象创建完成以后,执行这个方法publicvoidinitRabbitTemplate(){
rabbitTemplate.setConfirmCallback(newRabbitTemplate.ConfirmCallback() {
/**
*
* @param correlationData 当前消息的唯一关联数据
(这个消息的唯一id,发送消息的时候可以指定)
* @param b 消息是否成功收到
* @param s 失败的原因
*/@Overridepublicvoidconfirm(CorrelationData correlationData, boolean b, String s) {
System.out.println();
}
});
rabbitTemplate.setReturnCallback(newRabbitTemplate.ReturnCallback() {
/**
* 消息没有投递给指定队列,就触发这个失败回调
* @param message 投递失败的详细信息
* @param i 回复的状态码
* @param s 回复的文本内容
* @param s1 当时这个消息发送给哪个交换机
* @param s2 当时这个消息用的哪个路由键
*/@OverridepublicvoidreturnedMessage(Message message, int i, String s, String s1, String s2) {
System.out.println();
}
});
}
10、可靠投递-消费端确认(保证消息被正确消费之后broker才把消息给删除)
默认情况:自动确认,一堆消息被接收了,就全部确认了,这时这些消息再被一个个消费,可能再消费到某一个消息的时候宕机了,后面还未处理的消息就丢失了。解决方法使用手动确认。
手动确认:只要我不手动确认broker中的消息就不会被删除。
配置成手动模式

手动确认:
@Testpublicvoidconsumer(Message message, Channel channel)throws IOException {
Longtag= message.getMessageProperties().getDeliveryTag();
if(tag % 2 == 0){
channel.basicAck(tag,false);
}else{
channel.basicNack(tag,false,false);
}
}
tag:表示当前消息的编号,在channel内自增的,相当于这个信道传过来的第几个消息。
批量false:表示消费完一个消息确认一个,一个个来。
basicNack()第三个参数若果是true则不丢弃而是重新发送。
