一:摘要概览
第一篇文章RabbitMQ(一) – 初识RabbitMQ中基于AMQP协议对RabbitMQ整体进行了简介,旨在帮助阅读本系列文章的朋友建立初步的概念。文中最后部分使用到的客户端操作API并未深入的进行学习理解,本文将从RabbbitMQ应用服务部分即Broker所包含的交换器与队列两方面深入学习
二:Exchange
为什么要在生产者与队列之间提出交换器概念?试想如果生产者与队列直接耦合,当生产者客户端需要将一条消息发送至多个队列,那就需要多次操作。多次操作不仅仅意味着连接性能损耗,虽然Channel是轻量级,并且意味着客户端复杂度上升。所以,提出交换器概念,生产者只需要提前绑定设置好其与队列关系,发送消息时携带路由转发规则,后续逻辑交给服务端处理即可
2.1 默认交换器
RabbitMQ官网第一个使用示例Hello World中编写类似如下示例代码。当时就在好奇这不就生产者与队列直接交互耦合了么?其实不然,查看basicPublish()源码实现,你会发现参数含义依次为交换器、routingKey、BasicProperties、messageBytes。当第一个参数为空时消息将发送到默认交换器,即RabbitMQ服务端提前创建好的交换器,该交换器类型为direct
@SneakyThrows
public static void main (String[] args) {
Channel channel = createChannel();
String queueName = "queueName" , message = "测试消息";
channel.basicPublish("",queueName,null,message.getBytes());
}
打开RabbitMQ管理界面,查看AMQP Default Exchange的Bindings描述有如下一句话。所以这就可以解决很多初学者的疑惑,为什么官网给的第一个例子会出现将生产者与队列直接耦合的假象
// 默认交换器隐式地绑定到每个队列,其路由键等于队列名称
// 无法显式绑定到默认交换器或从默认交换器解绑定。它也不能被删除。
The default exchange is implicitly bound to every queue,
with a routing key equal to the queue name.
It is not possible to explicitly bind to,
or unbind from the default exchange.
It also cannot be deleted.
2.2 交换器类型
不同类型的交换器针对不同场景应用而设计,如将消息路由至全部绑定队列选用fanout、将消息路由至指定routingKey队列选用direct、将消息路由至模糊匹配routingKey则选用topic。当然还有一种header类型交换器,因为其性能与依赖消息头header进行规则匹配的原因,所以基本不会使用
序号 | 类型 | 路由匹配规则 | 备注 |
---|---|---|---|
1 | fanout | 消息路由到所有绑定队列 | 交换器队列绑定无需binding,消息发送无需routingKey |
2 | direct | routingKey与binding比较 | 一致性校验路由 |
3 | topic | routingKey与binding比较 | 模糊匹配,binding/routingKey单词层次间用"." 标识,binding绑定关系支持"*" 与"#" 两种模糊规则。如com.message.log.* |
序号 | 模糊规则 | 备注 |
---|---|---|
1 | * | 代表一层,如绑定关系binding为 log.* 则与 routingKey 为 log.error 或 log.info 的消息匹配 |
2 | # | 代表0或多层,如绑定关系binding为 log.# 则与 routingKey 为 log 或 log.message.info 的消息匹配 |
2.3 交换器创建
参数 | 含义 |
---|---|
exchange | 交换器的名字 |
type | 交换器种类,BuiltinExchangeType枚举类封装了上面讲的四类交换器类型 |
durable | 耐磨持久化,也就是当RabbitMQ服务应用重启后该交换器是否还存在 |
autoDelete | 自动删除,当所有与该交换器绑定的交换器或队列不存在时即自动删除 |
internal | true表示该交换器为内置交换器,客户端不能直接发送消息,需要交换器到交换器方式使用 |
arguments | key-value形式的一些交换器特性参数,如alternate-exchange。后续讲解 |
交换器声明的API具备众多重载的形式,只要理解具体参数含义,选用合适的API进行使用即可 |
如下Demo示例创建持久化、自动删除的交换器,打开控制台可以看到D
、AD
标识则标识这两个特性,如下图所示
String exchangeName = "fanoutExchange";
// 持久化、自动删除
boolean durable = true , autoDelete = false;
channel.exchangeDeclare(
exchangeName, BuiltinExchangeType.FANOUT,durable,autoDelete,null
);
2.4 删除交换器
参数 | 含义 |
---|---|
exchange | 交换器名称 |
ifUnused | true表示交换器未使用删除,若正在使用则不删除且抛出异常。false表示必须删除,默认值false |
// 是否校验交换器正在使用
boolean ifUnused = true;
channel.exchangeDelete(exchangeName,true);
2.5 补充API
方法 | 含义 |
---|---|
exchangeDeclareNoWait | 不需要返回值,等于异步执行交换器创建 。慎用!!!! |
exchangeDeleteNoWait | 与上述创建不等待相似 |
exchangeDeclarePassive | 检测交换器存在,不存在抛出异常 |
三:Queue
3.1 队列创建
参数 | 含义 |
---|---|
queue | 队列的名字 |
durable | 耐磨持久化,也就是当RabbitMQ服务应用重启后该队列是否还存在 |
autoDelete | 自动删除,当所有消费者断开与队列连接后自动删除。前提是队列创建后有消费者与其连接过 |
exclusive | 独占,true表示只能创建队列的连接才能使用这个队列。注意是连接,如同一连接创建的信道是可以使用该队列的 |
arguments | key-value形式的一些队列特性参数 |
队列声明的API具备众多重载的形式,只要理解具体参数含义,选用合适的API进行使用即可 |
如下Demo创建一个独占、自动删除的队列。看到效果图可能会奇怪为什么设置了持久化但是没有D
持久化标志,只有代表自动删除的AD
、独占标识Excl
。自动删除的队列还要持久化干嘛?
// 持久化、独占、自动删除
boolean durable = true, exclusive = true, autoDelete = true;
channel.queueDeclare(queueName,durable,exclusive,autoDelete,null);
3.2 队列删除
参数 | 含义 |
---|---|
exchange | 交换器名称 |
ifUnused | true表示队列未使用删除,正在使用则不删除且抛出异常。false表示必须删除,默认值false |
ifEmpty | true表示队列为空删除,不为空则不删除且抛出异常。false表示必须删除,默认值false |
// 校验队列是否正在使用
boolean ifUnused = true;
// 校验队列是否为空
boolean ifEmpty = true;
channel.queueDelete(queueName,ifUnused,ifEmpty);
3.3 补充API
方法 | 含义 |
---|---|
queuePurge | 清空队列但是不删除队列 |
queueDeclarePassive | 与交换器类似检测队列存在 |
queueDeclareNoWait | 与交换器类似不等待返回值创建 |
queueDeleteNoWait | 与交换器类似不等待返回值删除 |
四:Binding
RabbitMQ服务应用通过前面两大节基本就介绍完了,两个重要组成就是交换器和队列。两者之间的绑定是通过Binding实现,同时需要注意的一点就是交换器与交换器之间也可以进行绑定。内置的交换器客户端不能直接发送消息,就需要通过绑定交换器的方式使用
4.1 交换器绑定
需要明确的一点就是当创建两个交换器绑定关系之后,当第二个参数的交换器接收消息之后会将消息发送给第一个参数的交换器
// 创建交换器
String directExchangeA = "directExchangeA",directExchangeB= "directExchangeB";
boolean durable = true,autoDelete = false;
channel.exchangeDeclare(
directExchangeA,BuiltinExchangeType.TOPIC,durable,autoDelete,null
);
channel.exchangeDeclare(
directExchangeB,BuiltinExchangeType.DIRECT,durable,autoDelete,null
);
// 交换器绑定
String exchangeBinding = "com.message.info";
channel.exchangeBind(directExchangeB,directExchangeA,exchangeBinding);
// 队列声明
String queueName1= "queue1" , queueName2 = "queue2";
boolean exclusive = false;
channel.queueDeclare(queueName1,durable,exclusive,autoDelete,null);
channel.queueDeclare(queueName2,durable,exclusive,autoDelete,null);
// B交换器绑定队列
String queueBinding1 = "com.message.info" ,
queueBinding2 = "com.message.error";
channel.queueBind(queueName1,directExchangeB,queueBinding1);
channel.queueBind(queueName2,directExchangeB,queueBinding2);
// 发送消息
String messageBytes = "测试消息", routingKey = "com.message.info";
channel.basicPublish(directExchangeA,routingKey,null,messageBytes.getBytes());
通过控制台查看最后结果,发送到交换器A的消息路由到了交换器B,并最终路由到队列1。基本可以断定其中交换器A – B,交换器B – 队列1使用的routingKey就是最初消息发送的routingKey。可以修改相关binding验证,具体验证过程这里不再给出
4.2 队列绑定
队列绑定在上面例子中其实已经有了对应的API操作,就是将队列与交换器进行绑定。参数中需要定义绑定关系的Binding,当然fanout交换器绑定的时候不需要用到这个关系。借用上面例子截图队列绑定关系如下:
4.3 补充API
方法 | 含义 |
---|---|
exchangeUnbind | 解除交换器绑定 |
queueUnbind | 解除队列绑定 |
queueBindNoWait | 无返回值绑定队列 |
exchangeBindNoWait | 无返回值绑定交换器 |