一、MQ简介#
官网连接 :https://www.rabbitmq.com/documentation.html
简介#
大多应用中,可通过消息服务中间件来提升系统异步通信、扩展解耦能力。
消息的发送者和接收者不需要同时与消息队列交互。消息会保存在队列中,知道接收者取回它。
下面是架构图:
Producer:消息生产者,负责生产和发送消息到Broker;
Broker:消息处理中心,负责消息存储、确认、重试等;
Consumer:消息消费中心,负责从Broker中获取消息并处理。
消息队列-特性
异步性:将耗时的同步任务通过发送消息的方式进行异步处理,减少等待时间。
松耦合:不同系统、服务之间可以通过消息队列进行通信,不用关心彼此的实现细节,数据格式一致。
分布式:为了防止消息堵塞,可以对消费者集群进行横向扩展,避免单点故障,同样队列本身也可以。
可靠性:将接收到的消息落盘,就算服务器重启或者发生故障,恢复之后也能重新加载。
RabbitMQ核心概念#
RabbitMQ是一个由erlang开发的AMQP(Advanved Message Queue Protocol)的开源实现。
Message
消息,消息是不具名的,它由消息头和消息体组成
消息头,包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等
Publisher
消息的生产者,也是一个向交换器发布消息的客户端应用程序
Exchange
交换器,将生产者消息路由给服务器中的队列
类型有direct(默认),fanout, topic, 和headers,具有不同转发策略
Queue
消息队列,保存消息直到发送给消费者
Binding
绑定,用于消息队列和交换器之间的关联
Connection
网络连接,比如一个TCP连接
Channel
信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内的虚拟连接,AMQP命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成的。因为对于操作系统来说建立和销毁TCP都是非常昂贵的开销,所以引入了信道的概念,以复用一条TCP连接。
Consumer
消息的消费者,表示一个从消息队列中取得消息的客户端应用程序
Virtual Host
虚拟主机,表示一批交换器、消息队列和相关对象。
vhost 是 AMQP 概念的基础,必须在连接时指定
RabbitMQ 默认的 vhost 是 /
Broker
消息队列服务器实体
二、Docker安装RabbitMQ
docker run -d --name rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 25672:25672 -p 15671:15671 -p 15672:15672 rabbitmq:management
说明:
4369,25672 (Erlang发现&集群端口)
5672,5671(AMQP端口)
15672(web管理后台端口)
61613,61614(STOMP协议端口)
1883,8883(MQTT协议端口)
设置rabbitMQ docker自启动
docker update rabbitmq --restart=always
查看容器命令:
[root@localhost vagrant]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1d7e85d6333a rabbitmq:management "docker-entrypoint.s…" 3 minutes ago Up 3 minutes 0.0.0.0:4369->4369/tcp, 0.0.0.0:5671-5672->5671-5672/tcp, 0.0.0.0:15671-15672->15671-15672/tcp, 0.0.0.0:25672->25672/tcp, 15691-15692/tcp rabbitmq
访问mq网页 端口地址:15672
三、RabbitMQ消息发送测试#
RabbitMQ提供了四种Exchange模式:fanout,direct,topic,header 。 header模式在实际使用中较少,这里只讨论前三种模式.
fanout 模式#
fanout 模式就是广播模式,消息来了,会发给所有的队列
测试广播模式:
先在交换机创建 fanout模式的交换机,命名为 my.fanout.exchange,然后再到队列创建多个队列,再到交换机绑定队列,fanout可以不设置路由key,因为这个是广播模式的,最后发消息测试。
队列接收消息:
可以看到绑定的队列已经收到消息了。
Direct 模式
Direct 模式就是指定队列模式, 消息来了,只发给指定的 Queue, 其他Queue 都收不到。
Topic 模式#
主题模式,注意这里的主题模式,和 ActivityMQ 里的不一样。 ActivityMQ 里的主题,更像是广播模式。
那么这里的主题模式是什么意思呢? 如图所示消息来源有: 美国新闻,美国天气,欧洲新闻,欧洲天气。
如果你想看 美国主题: 那么就会收到 美国新闻,美国天气。
如果你想看 新闻主题: 那么就会收到 美国新闻,欧洲新闻。
如果你想看 天气主题: 那么就会收到 美国天气,欧洲天气。
如果你想看 欧洲主题: 那么就会收到 欧洲新闻,欧洲天气。
这样就可以灵活搭配~
管理界面
1.4.1. 添加用户
如果不使用guest,我们也可以自己创建一个用户:
1、 超级管理员(administrator)
可登陆管理控制台,可查看所有的信息,并且可以对用户,策略(policy)进行操作。
2、 监控者(monitoring)
可登陆管理控制台,同时可以查看rabbitmq节点的相关信息(进程数,内存使用情况,磁盘使用情况等)
3、 策略制定者(policymaker)
可登陆管理控制台, 同时可以对policy进行管理。但无法查看节点的相关信息(上图红框标识的部分)。
4、 普通管理者(management)
仅可登陆管理控制台,无法看到节点信息,也无法对策略进行管理。
5、 其他
无法登陆管理控制台,通常就是普通的生产者和消费者。
1.4.2. 创建Virtual Hosts
虚拟主机:类似于mysql中的database。他们都是以“/”开头
1.4.3. 设置权限
1.4.3. 设置权限
创建交换机
创建队列
绑定队列
SpringBoot正式整和RabbitMQ
1、引入 Spring-boot-starter-amqp
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2、application.yml配置
spring:
rabbitmq:
host: 192.168.56.10
port: 5672
virtual-host: /
使用步骤:
1、引入amqp场景,RabbitAutotConfiguration就会自动生效
2、给容器中自动配置了RabbitTemplate.AmqpAdmin CachingConnectionFactory、RabbitMessagingTemlate
3、@EnableRabbit
3、测试RabbitMQ
1、AmqpAdmin:管理组件
/**
* 创建Exchange
* 1、如何利用Exchange,Queue,Binding
* 1、使用AmqpAdmin进行创建
* 2、如何收发信息
*/
@Test
public void contextLoads() {
// public DirectExchange(
// String name, 交换机的名字
// boolean durable, 是否持久
// boolean autoDelete, 是否自动删除
// Map<String, Object> arguments)
// {
DirectExchange directExchange = new DirectExchange("hello-java.exchange",true,false);
amqpAdmin.declareExchange(directExchange);
log.info("Exchange[{}]创建成功:","hello-java.exchange");
}
/**
* 创建队列
*/
@Test
public void createQueue() {
// public Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments) {
Queue queue = new Queue("hello-java-queue",true,false,false);
amqpAdmin.declareQueue(queue);
log.info("Queue[{}]:","创建成功");
}
/**
* 绑定队列
*/
@Test
public void createBinding() {
// public Binding(String destination, 目的地
// DestinationType destinationType, 目的地类型
// String exchange,交换机
// String 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");
}
2、RabbitTemplate:消息发送处理组件
@Autowired
@Test
public void sendMessageTest() {
for(int i = 1; i <=5; i++) {
if(i%2==0) {
OrderReturnReasonEntity reasonEntity = new OrderReturnReasonEntity();
reasonEntity.setId(1l);
reasonEntity.setCreateTime(new java.util.Date());
reasonEntity.setName("哈哈");
//
String msg = "Hello World";
// 发送的对象类型的消息,可以是一个json
rabbitTemplate.convertAndSend("hello-java.exchange","hello.java",reasonEntity);
} else {
OrderEntity orderEntity = new OrderEntity();
orderEntity.setOrderSn(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend("hello-java.exchange","hello.java",orderEntity);
}
log.info("消息发送完成{}");
}
}
如果需要配置mq消息非序列化,则加入配置文件
@Configuration
public class MyRabbitConfig {
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
效果图:
13.7 RabbitMQ消息确认机制 - 可靠到达
- 保证消息不丢失,可靠抵达,可以使用事务消息,性能下降250倍,为此引入确认机制
- publisher confirmCallback 确认模式
- publisher returnCallback 未投递到 queue 退回
- consumer ack 机制
可靠抵达 - ConfirmCallback
spring.rabbitmq.publisher-confirms=true
在创建 connectionFactory
的时候设置 PublisherConfirms(true) 选项,开启 confirmcallback
。
CorrelationData
用来表示当前消息唯一性
消息只要被 broker 接收到就会执行 confirmCallback,如果 cluster 模式,需要所有 broker 接收到才会调用 confirmCallback
被 broker 接收到只能表示 message 已经到达服务器,并不能保证消息一定会被投递到目标 queue 里,所以需要用到接下来的 returnCallback
可靠抵达 - ReturnCallback
spring.rabbitmq.publisher-retuns=true
spring.rabbitmq.template.mandatory=true
confirm 模式只能保证消息到达 broker,不能保证消息准确投递到目标 queue 里。在有些模式业务场景下,我们需要保证消息一定要投递到目标 queue 里,此时就需要用到 return 退回模式
这样如果未能投递到目标 queue 里将调用 returnCallback,可以记录下详细到投递数据,定期的巡检或者自动纠错都需要这些数据
可靠抵达 - Ack 消息确认机制
- 消费者获取到消息,成功处理,可以回复Ack给Broker
- basic.ack 用于肯定确认:broker 将移除此消息
- basic.nack 用于否定确认:可以指定 beoker 是否丢弃此消息,可以批量
- basic.reject用于否定确认,同上,但不能批量
- 默认,消息被消费者收到,就会从broker的queue中移除
- 消费者收到消息,默认自动ack,但是如果无法确定此消息是否被处理完成,或者成功处理,我们可以开启手动ack模式
- 消息处理成功,ack(),接受下一条消息,此消息broker就会移除
- 消息处理失败,nack()/reject() 重新发送给其他人进行处理,或者容错处理后ack
- 消息一直没有调用ack/nack方法,brocker认为此消息正在被处理,不会投递给别人,此时客户端断开,消息不会被broker移除,会投递给别人