前言:该篇主要是介绍RabbitMQ的理论知识
项目在GitHub上能找得到 ,可直接运行,希望能帮助到你 点击链接跳转
RabbitMQ配置使用篇地址
1、rabbitmq里的组成成分
- Broker:传输服务,维护一条生产者到消费者的路线,保证数据按照指定方式进行传输,差不多就是RabbitMQ
- Channel:消息通道,在客户端的每个连接里可以建立多个channe
- Exchange:消息交换机,它指定消息按什么规则,路由到那个列队
- Queue: 消息的队列,每个消息都会被投到一个或者多个列队
- Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来
- Routing Key:路由关键字,exchange根据这个关键字进行消息投递
- vhost:虚拟主机,一个borker里可以有多个vhost,用作不同用户的权限分离
- Producer:消息生产者
- Consumer:消费者
2、传输步骤
- 1、消息由生产者通过消息通道(Channel)发布到交换器(Exchange)
- 2、交换器(Exchange)根据根据绑定(binding),将消息路由(分发)到列队(queue)
- 3、消费者获取列队中的消息。
- 4、消息确定,消费者收到消息时,通知消息代理,消息将被消息代理从列表中移除
3、发送的原理
应用和RabbitServer创建一个TCP连接,连接成功后就应用和Rabbit就创建一条AMQP消息通道(Channel),它是创建在真实的TCP上的虚拟连接,AMQP命令都是通过消息通道发送出去的,每个消息通道都有一个唯一的ID,不论是发布消息、订阅列队或者接收消息都是通过消息通道完成的
4、重要概述角色消息
4.1、交换器(Exchage)
4.1.1、将消息路由到多个列队,它的关键属性有
- 名称(name)
- 类型:消息路由的规则,由交换器类型和绑定规则沟通决定
- 持久化(Durability):消息代理重启后,交换机是否还存在。交换机可以偶两个姿态:持久(durable)、暂存(transient)。持久化的交换机会在mq重启后依旧存在,而暂存的交换机则不会(这样的mq重启后重新被声明)。然后并不是所有的应用场景都需要持久化
- 自动删除(Auto-delete):当所有与之绑定的消息列队都完成了对此交换机的使用后自动删除它
4.1.2交换器类型
direct
- 工作机制,如果某列队设定键X绑定到direct交换器上,那么消息发送到direct交换器上的键也等于X,那么消息进入列队。
- 场景:单发送,单接收的简单的应用场景,简单,不够灵活
topic
- topic 交换器与direct交换器类似,基于消息路由键与列队绑定键进行匹配,区别在于topic交换器支持通配符形式的绑定键:
- “键”以 . 划分多个词
-
- 匹配任意一个词
- #匹配0到多个词demo.rmq.example_1 与 *.rmq.example_1 和 demo.# 匹配。
- 场景:单发送,单接收的应用场景。多发送,单接收的应用场景(主要)
fanout
- fanout交换器将消息路由到所有绑定的列队。类似于“广播”。
- 场景:单发送,多接收的应用场景
hrader(用的少不记了)
与direct交换器类似,区别在于header不依赖于消息路由键与队列绑定的匹配,而是依赖于消息和绑定的“headers”匹配
4.2消息队列(Queue)
列队接收来自交换器分发的消息,同消费者读取。关键属性有:
- 列队名称
- 持久化:mq重启后列队是否存在,持久化列队(Durable )会被存储在磁盘上,重启后依然存在。没有持久化的列队称为暂存列队(Transient)
- 专用列队(Exclusive):可以这样理解当创建这个队列的Connection关闭后列队即被删除
- 自动删除:列队没有订阅的消费者时,是否自动删除
注意点:
消息列队在声明(declare)后才能被使用。如果一个队列不存在,声明就会创建它。如果声明的列队已存在,并且属性完全相同,那么此次声明不会对原有的对了产生影响。如果声明中的属性(除名称外)与存在的列队的属性有差异,那么则会申明失败
4.3、消息(message)
消息的关键属性包括:
- 路由器:交换器由消息的“依据”。
- 投递模式:消息是否“持久化”
- Content-Type / Content-Encoding:通常作为“载荷”数据结构的标识。(“载荷”即为消息传递的数据,其数据结构由应用决定,AMQP保持透明,仅将其作为字节数组
- 消息头(header):消息的附加属性,K-V机构。
为了达成消息的持久化,一般消息、交换器、列队必须全部持久化
4.4路由键(RoutingKey)
路由关键字,交换机exchange的路由规则利用这个关键字进行消息投递到消息队列。(路由键长度不能超过255个字节)
4.5 绑定(Binding)
用于把交换器的消息绑定到消息列队上。
5、消息持久化
5.1、为了保证mq在重启后不丢失消息,所以需要持久化。
- 1、投递消息需要持久化。durable设为true
/**
* 参数说明:
* 1、第一个为交换器名
* 2、是否持久化,默认true
* 3、消费者断开时是否删除
* 4、消息其他参数 实际开发需要持久化,只需要输入交换器的名称,其他用默认的
*
*/
//参数2设置为true持久化
channel.queueDeclare(x, true, false, false, null),
- 2、设置投递模式deliveryMode设为2
//参数 3 设置为存储纯文本到磁盘;
channel.basicPublish(x, x, 2,x),
- 3、消息达到持久化的交换器上
- 4、消息达到持久化的列队上
5.2、持久化的原理
是把持久化消息写入磁盘上的持久化日志文件里,等消息被消费后,mq会把这条消息标识为等待垃圾回收
5.3、持久化的缺点
性能、吞吐量都会降低更多
6、rabbit保障性的措施
6.1 生产者发送完后,能够获取来自消息代理的确定
@Component
public class ConfirmCallbackDemo implements RabbitTemplate.ConfirmCallback{
/**
* 通过实现 ConfirmCallback 接口,消息发送到 Broker 后触发回调,确认消息是否到达 Broker 服务器,也就是只确认是否正确到达 Exchange 中
* 1.如果消息没有到exchange,则 ack=false
* 2.如果消息到达exchange,则 ack=true
* @param correlationData
* @param ack
* @param cause
*/
public void confirm(@Nullable CorrelationData correlationData, boolean ack, @Nullable String cause) {
System.out.println("[confirm]: id=" + correlationData.getId());
if(ack){// 成功接收
//todo 成功处理逻辑
}else{
// 失败原因
System.out.println("[confirm]: cause=" + cause);
//todo 失败处理逻辑
}
}
}
6.2 路由无法将消息投递到任何一个列队上,默认情况下会被丢弃,特定场景中,生产者需要被感知
@Component
public class ReturnCallbackDemo implements RabbitTemplate.ReturnCallback{
/**
* 当消息从交换机到队列失败时,该方法被调用。(若成功,则不调用)
* 需要注意的是:该方法调用后,MsgSendConfirmCallBack中的confirm方法也会被调用,且ack = true
* @param message
* @param replyCode
* @param replyText
* @param exchange
* @param routingKey
*/
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("消息主体 message : "+message);
System.out.println("消息主体 message : "+replyCode);
System.out.println("描述:"+replyText);
System.out.println("消息使用的交换器 exchange : "+exchange);
System.out.println("消息使用的路由键 routing : "+routingKey);
}
}
6.3 消费者确定
注意
为保证可靠性,不使用自动确认。
当且仅当消费者完成消息的处理,进行消费者确定
@Component
public class MessageHandler {
@RabbitListener(queues = "directqueue")
public void handleMessage(Message message, Channel channel) throws Exception{
try {
System.out.println("消费消息");
System.out.println(new String(message.getBody()));
channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
} catch (Exception e){
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,true);
}
}
}