Rabbit MQ 学习(一)基础入门
简介
RabbitMQ 简介
在介绍 RabbitMQ 之前实现要介绍一下MQ,MQ 是什么?MQ 全称是 Message Queue,可以理解为消息队列的意思,简单来说就是消息以管道的方式进行传递。RabbitMQ 是一个实现了AMQP(Advanced Message Queuing Protocol)高级消息队列协议的消息队列服务,用 Erlang 语言的。
为什么选择 RabbitMQ
1.RabbitMQ 实现了 AMQP 标准的消息服务器;
2.可靠性,RabbitMQ 的持久化支持,保证了消息的稳定性;
3.高并发,RabbitMQ 使用了 Erlang 开发语言,Erlang 是为电话交换机开发的语言,天生自带高并发光环,和高可用特性;
4.集群部署简单,正是应为 Erlang 使得 RabbitMQ 集群部署变的超级简单;
5.社区活跃度高,根据网上资料来看,RabbitMQ 也是首选;
RabbitMQ 的模型架构是什么?
RabbitMQ 整体上是一个生产者与消费者模型,主要负责存储和转发消息。我们可以把 RabbitMQ 想象成一个快递站,我们需要邮寄包裹需要把包裹送到快递站,快递站会暂存并最终通过快递员来把包裹送到收件人的手上。RabbitMQ 就好比是快递站和快递员组成的一个系统。
AMQP 协议是什么?
AMQP 协议本身包括三层
1.Module Layer:位于协议最高层,主要定义客户端调用的命令。例如 Queue.Declare 命令声明一个队列或者使用 Basic.Consume 订阅消费一个队列中的消息。
2.Session Layer:位于中间层,主要负责将客户端的命令发送给服务器,再将服务器的应答返回给客户端。主要为客户端与服务器之间的通信提供可靠性同步机制和错误处理。
3.Transport Layer:位于最底层,主要传输二进制数据流,提供帧的处理、信道复用、错误检测和数据表示等。
AMQP 其本质还是一个通信协议,通信协议都会涉及报文交互,从简单了说 AMQP 本身就是应用层协议是 TCP 协议的数据部分。从复杂了说 AMQP 是通过协议命令进行交互的,可以看作是一系列命令的集合操作。
AMQP 常用命令
名称 | 是否包含内容体 | 对应客户端中的方法 | 简要描述 |
---|---|---|---|
Connection.start | 否 | factory.newConnection | 建立连接相关 |
Connection.Start-Ok | 否 | 同上 | 同上 |
Connection.Tune | 否 | 同上 | 同上 |
Connection.Tune-Ok | 否 | 同上 | 同上 |
Connection.Open | 否 | 同上 | 同上 |
Connection.Open-Ok | 否 | 同上 | 同上 |
Connection.Close | 否 | connection.close | 关闭连接 |
Connection.Close-Ok | 否 | 同上 | 同上 |
Channel.Open | 否 | connection.openChannel | 开启信道 |
Channel.Open-Ok | 否 | 同上 | 同上 |
Cbannel.Close | 否 | channel.close | 关闭信道 |
Channel.Close-Ok | 否 | 同上 | 同上 |
Exchange.Declare | 否 | channel.exchangeDeclare | 声明交换器 |
Exchange.Declare-Ok | 否 | 同上 | 同上 |
Exchange.Delete | 否 | channel.exchangeDelete | 删除交换器 |
Exchange.Delete-Ok | 否 | 向上 | 同上 |
Exchange.Bind | 否 | channel.exchangeBind | 交换器与交换器绑定 |
Exchange.Bind-Ok | 否 | 同上 | 同上 |
Exchange.Unbind | 否 | channel.exchangeUnbind | 交换器与交换器解绑 |
Exchange.Unbind-Ok | 否 | 同上 | 同上 |
Queue.Declare | 否 | channel.queueDeclare | 声明队列 |
Queue. Declare-Ok | 否 | 同上 | 同上 |
Queue.Bind | 否 | channel.queueBind | 队列与交换器绑定 |
Queue.Bind-Ok | 否 | 同上 | 同上 |
Queue.Purge | 否 | channel.queuePurge | 清除队列中的内容 |
Queue.Purge-Ok | 否 | 同上 | 同上 |
Queue.Delete | 否 | channel.queueDelete | 删除队列 |
Queue.Delete-Ok | 否 | 同上 | 向上 |
Queue.Unbind | 否 | channel.queueUnbind | 队列与交换器解绑 |
Queue.Unbind-Ok | 否 | 同上 | 同上 |
Basic.Qos | 否 | channel.basicQos | 设置未被确认消费的个数 |
Basic.Qos-Ok | 否 | 同上 | 同上 |
Basic.Consume | 否 | channel.basicConsume | 消费消息(推模式) |
Ba siιCo nsurne-Ok | 否 | 同上 | 同上 |
Basic.Cancel | 否 | channel.basicCancel | 取消 |
Basic.Cancel-Ok | 否 | 同上 | 同上 |
Basic.Publish | 是 | channel.basicPublish | 发送消息 |
Basic.Return | 是 | 无 | 未能成功路由的消息返回 |
Basic.Deliver | 是 | 无 | Broker 推送消息 |
Basic.Get | 否 | channel.basicGet | 消费消息(拉模式) |
Basic.Get-Ok | 是 | 同上 | 同上 |
Basic.Ack | 否 | channel.basicAck | 确认 |
Basic.Reject | 否 | channel.basicReject | 拒绝(单条拒绝) |
Basic.Recover | 否 | channel.basicRecover | 请求 Broker 重新发送未被确认的消息 |
Basic.Recover-Ok | 否 | 同上 | 同上 |
Basic.Nack | 否 | channel.basicNack | 拒绝(可批量拒绝) |
Tx.Select | 否 | channel. txSelect | 开启事务 |
TX.Select-Ok | 否 | 同上 | 同上 |
Tx.Cornmit | 否 | channel. txCommit | 事务提交 |
TX.Commit-Ok | 否 | 同上 | 同上 |
Tx.Rollback | 否 | channel.txRollback | 事务回滚 |
TX.Rollback-Ok | 否 | 同上 | 同上 |
Confirrn Select | 否 | channel.confinnSelect | 开启发送端确认模式 |
Confinn.Select-Ok | 否 | 同上 | 同上 |
概念
生产者和消费者
Producer:生产者,就是投递消息的一方。
生产者创建消息然后发布到 RabbitMQ 中。消息一般可以包含两部分:标签和消息体。在实际应用中消息体一般是一个带有业务逻辑结构的数据,比如一个 json 字符串。消息的标签用来表述这条消息,比如一个交换器的名称和一个路由键。生产者将消息发布到 RabbitMQ,RabbitMQ 之后会根据标签把消息发送到感兴趣的消费者。
Consumer:消费者,就是接收消息的一方。
消费者连接到 RabbitMQ 服务器并订阅到队列上。当消费者消费一条消息时只是消费的消息体而不是标签,标签在消息路由过程中会丢弃。存入到队列中的只是消息体,消费者只会消费消息体。
Broker:消息中间件的服务节点。
一个 Broker 可以简单的看作是一个 RabbitMQ 服务节点或者 RabbitMQ 服务实例。
队列
Queue:队列,是 RabbitMQ 的内部对象,用于存储消息。
RabbitMQ 中消息只能存储在队列中,主题模式只是每个消费者申请一个队列和交换器绑定了。RabbitMQ 的生产者生产消息并最终投递到队列中,消费者可以从队列中获取消息并消费。
多个消费者也可以订阅到同一个队列,这时队列中的消息会被平均分摊(Round-Robin 轮询)给多个消费者进行处理,而不是每个消费者都收到同样的消息并处理,叫做工作队列。
交换器、路由键、绑定
Exchange:交换器,生产者将消息发送到 Exchange,由交换器将消息路由到一个或者多个队列中。
RabbitMQ 常用交换器类型有:fanout、direct、topic、headers 这四种
1.fanout:它会把所有发送到该交换机的消息路由到所有与该交换机绑定的队列中。
2.direct:它会把消息路由到那些 bindingKey 和 RoutingKey 完全匹配的队列中。
3.topic:topic 类型的交换器在匹配的规则上进行了扩展,与 direct 相似,但是匹配规则有些不同。
3.1.RoutingKey 为一个点号 “.” 分隔的字符串如 “com.rabbitmq.client”、“java.util.lang”、""。
3.2.BindingKey 和 RoutingKey 一样也是点号 “.” 分隔的字符串。
3.3.BindingKey 中可以存在两种特殊字符串 “" 和 “#”,用于做模糊匹配,其中 "” 用于匹配一个单词,"#" 用于匹配多规格单词(可以是零个)。
以上图为例
1.路由键为 “com.rabbitmq.client” 的消息会被路由到 Queue1 和 Queue2;
2.路由键为 “com.hidden.client” 的消息只会被路由到 Queue2 中;
3.路由键为 “com.hidden.demo” 的消息只会被路由到 Queue2 中;
4.路由键为 “java.rabbitmq.demo” 的消息只会路由到 Queue1 中;
5.路由键为 “java.util.concurrent” 的消息将会被丢弃或者返回给生产者(需要设置 mandatory 参数),因为它没有匹配任何路由键;
**RoutingKey:**路由键,生产者将消息发给交换机的时候,一般会指定一个路由键,用来指定消息路由规则,而这个路由键需要与交换机类型和绑定键联合使用才能生效。
**Binging:**绑定,RabbitMQ 中通过绑定将交换器与队列关联起来,在绑定的时候一般会指定一个绑定键,这样 RabbitMQ 就知道如何正确的将消息路由到队列了。
生产者将消息发送给交换器时,需要一个 RoutingKey ,当 BindingKey 和 RoutingKey 相匹配时,消息会被路由到对应的队列中。
RabbitMQ 运转流程,消息从生产者发出到消费者这一过程要经历一些什么?
了解了 RabbitMQ 的架构模型,我们来回顾下整个流程。
1.生产者发送消息
1.1.生产者连接到 RabbitMQ Broker ,建立一个连接(Connection) ,开启一个信道(Channel)
1.2.生产者声明一个交换器,并设置相关属性,比如交换机类型、是否持久化等
1.3.生产者声明一个队列井设置相关属性,比如是否排他、是否持久化、是否自动删除等
1.4.生产者通过路由键将交换器和队列绑定起来
1.5.生产者发送消息至 RabbitMQ Broker ,其中包含路由键、交换器等信息
1.6.相应的交换器根据接收到的路由键查找相匹配的队列
1.7.如果找到,则将从生产者发送过来的消息存入相应的队列中
1.8.如果没有找到,则根据生产者配置的属性选择丢弃还是回退给生产者
1.9.关闭信道
1.10.关闭连接
public class RabbitMqReview {
private static final String EXCHANGE_NAME = "test_exchange_any_type";
private static final String QUEUE_NAME = "hello_word";
public static void main(String[] args) throws IOException, TimeoutException {
// 声明连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 使用连接工厂赋值 userName、password、host、vHost、port 等等
factory.setUsername("userName");
factory.setPassword("userName");
factory.setHost("host");
factory.setVirtualHost("vhost");
factory.setPort(5672);
// 创建连接
Connection conn = factory.newConnection();
System.out.println("RabbitMqReview 获取到的链接是: " + conn);
// 创建通道
Channel channel = conn.createChannel();
System.out.println("RabbitMqReview 获取到的通道是: " + channel);
// 声明一个交换器
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
// 声明一个队列
// channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 发送消息
String message = "Hello Word!";
channel.basicPublish(EXCHANGE_NAME, "routingKey", MessageProperties.TEXT_PLAIN, message.getBytes());
// 关闭资源
channel.close();
conn.close();
}
}
2.消费者接收消息
2.1.消费者连接到 RabbitMQ Broker,建立一个连接(Connection),开启一个信道(Channel)
2.2.消费者向 RabbitMQ Broker 请求消费相应队列中的消息
2.3.等待 RabbitMQ Broker 回应并投递相应队列中的消息,消费者接收消息
2.4.消费者确认(ack) 接收到的消息
2.5.RabbitMQ 从队列中删除相应己经被确认的消息
2.6.关闭信道
2.7.关闭连接
public class RabbitMqRecReview {
private static final String EXCHANGE_NAME = "test_exchange_any_type";
private static final String QUEUE_NAME = "hello_word";
public static void main(String[] args) throws IOException, TimeoutException {
// 声明连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 使用连接工厂赋值 userName、password、host、vHost、port 等等
factory.setUsername("userName");
factory.setPassword("userName");
factory.setHost("host");
factory.setVirtualHost("vhost");
factory.setPort(5672);
// 创建连接
Connection conn = factory.newConnection();
System.out.println("RabbitMqRecReview 获取到的链接是: " + conn);
// 创建通道
Channel channel = conn.createChannel();
System.out.println("RabbitMqRecReview 获取到的通道是: " + channel);
// 声明一个交换器
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
// 绑定对应的队列
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "bindingKey");
// 声明消费回调函数
DeliverCallback deliverCallback = new DeliverCallback() {
@Override public void handle(String s, Delivery delivery) throws IOException {
System.out.println("RabbitMqRecReview receive: " + new String(delivery.getBody(), StandardCharsets.UTF_8));
// 确认(ack)接收到的消息
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
};
channel.basicConsume(QUEUE_NAME, false, deliverCallback, new CancelCallback() {
@Override public void handle(String s) throws IOException {
}
});
}
}
Connection 和 Channel
无论生产者和消费者都需要和 RabbitMQ Broker 建立连接,这个连接就是一条 TCP 连接,也就是 Connection。一旦 TCP 连接建立成功,紧接着需要创建一个 AMQP 的信道(Channel),每条信道都会被指派一个唯一的 ID。信道是建立在 Connection 之上的虚拟连接,RabbitMQ 处理的每条 AMQP 指令都是通过信道完成的。
以上介绍了生产者(Producer ) 、消费者(Consumer) 、队列(Queue) 、交换器(Exchange)、路由键(RoutingKey )、绑定(Binding)、连接(Connection)和信道(Channel) 。