1.AMPQ协议
RabbitMQ 是遵从 AMQP 协议的,换句话说, RabbitMQ 就是 AMQP协议的 Erlang 的实现。AMQP 的模型架构和 RabbitMQ 的模型架构是一样的,生产者将消息发送给交换器,交换器和队列绑定。当生产者发送消息时所携带的RoutingKey 与绑定时的 BindingKey 相匹配时,消息即被存入相应的队列之中。消费者可以订阅相应的队列来获取消息。
RabbitMQ 中的交换器、交换器类型、队列、绑定、路由键等都是遵循的 AMQP 协议中相应的概念。
AMQP 协议本身包括三层:
- Module Layer : 位于协议最高层,主要定义了一些供客户端调用的命令,客户端可以利用这些命令实现自己的业务逻辑。例如客户端可以使用Queue.Declare 命令声明一个队列或者使用 Basic.Consume 订阅消费一个队列中的消息。
- Session Layer:位于中间层,主要负责将客户端的命令发送给服务器 , 再将服务端的应答返回给客户端,主要为客户端与服务器之间的通信提供可靠性同步机制和错误处理。
- Transport Layer : 位于最底层,主要传输二进制数据流,提供帧的处理、信道复用、错误检测和数据表示等。
AMQP 说到底还是一个通信协议,通信协议都会涉及报文交互,从 lowlevel来说,AMQP 本身是应用层的协议,其填充TCP 协议层的数据部分。而从 highlevel 来说 , AMQP是通过协议命令进行交互的。 AMQP 协议可以看作一系列结构化命令的集合,这里的命令代表一种操作,类似于 HTTP 中的方法( GET、 POST 、 PUT、 DELETE 等)。
1.1 AMQP 生产者流转过程
看一个最精简的生产者调用图:
Connection connection= factory . newConnection () ; //创建连接
Channel channel= connection.createChannel () ; //创建信道
String message = ” Hello World l ” ;
channel.basicPublish(EXCHANGE NAME , ROUTING KEY ,MessageProperties.PERSISTENT_TEXT_PLAIN ,message.getBytes());
//关闭资源
channel.close();
connection.close();
当客户端与 Broker 建立连接的时候,会调用 factory.newConnection 方法,这个方法会进一步封装成 Protocol Header 0-9-1 的报文头发送给 Broker,以此通知 Broker 本次交互采用的是 AMQP0-9-1协议,紧接着 Broker 返回 Connection.Start 来建立连接,在连接的过程中涉及 Connection.Start/.Start-OK 、 Connection.Tune/.Tune-Ok,Connection.Open/.Open-Ok 这 6 个命令的交互 。
当客户端调用 connection.createChannel 方法准备开启 信道的时候,其包装Channel.Open 命令发送给 Broker,等待 Channel.Open-Ok 命令。
当客户端发送消息的时候,需要调用 channel.basicPublish 方法,对应的 AQMP 命令为 Basic.Publish,注意这个命令和前面涉及的命令略有不同,这个命令还包含了 ContentHeader 和 Content Body 。 Content Header 里面包含的是消息体的属性,例如,投递模式 、优先级等,而 Content Body 包含消息体本身。
当客户端发送完消息需要关闭资源时,涉及 Channel.Close/.Close-Ok 与Connection.Close/.Close-Ok 的命令交互。详细流转过程下图所示 。
1.2 AMQP 消费者流转过程
继续来看消费者的流转过程,还是先看最精简的代码:
connection connection = factory.newConnection(addresses );//创建连接
final Channel channel= connection . createChannel () ; //创建信道
Consumer consumer= new DefaultConsumer(channel) {}//...省略实现
channel .basicQos(64) ;
channel.basicConsume(QUEUE NAME , consumer) ;
//等待回调函数执行完毕之后,关闭资源
TimeUnit . SECONDS . sleep(S) ;
channel . close();
connection.close();
消费者客户端同样需要与 Broker 建立连接,与生产者客户端一样,协议交互同样涉及Connection.Start/.Start-Ok 、Connection.Tune/.Tune-Ok 和 Connection.Open/.Open-Ok 等,下图省略了这些步骤。
紧接着也少不了在 Connection 之上建立 Channel ,和生产者客户端一样,协议涉及Channel .Open/Open-Ok。如果在消费之前调用了 channel . basicQos(int prefetchCount )的方法来设置消费者客户端最大能“保持”的未确认的消息数,那么协议流转会涉及 Basic.Qos/.Qos-Ok 这两个 AMQP 命令。
在真正消费之前,消费者客户端需要向 Broker 发送 Basic.Consume 命令(即调用channel.basicConsume 方法〉将 Channel 置为接收模式,之后 Broker 回执
Basic.Consume-Ok 以告诉消费者客户端准备好消费消息。紧接着 Broker 向消费者客户端推送( Push )消息,即 Basic.Deliver 命令,有意思的是这个和 Basic.Publish 命令一样会携带 Content Header 和 Content Body.消费者接收到消息并正确消费之后,向 Broker 发送确认,即 Basic.Ack 命令。
在消费者停止消费的时候,主动关闭连接,这点和生产者 一 样,涉及Channel.Close/.Close-Ok 和Connection.Close/.Close-Ok 。
2. RabbitMQ 执行流程
根据上面的协议过程和前面说的RabbitMQ的结构,我们分别从生产者和消费者的角度分析执行流程:
生产者发送消息的过程
(1)生产者连接到 RabbitMQ Broker , 建立一个连接( Connection ),开启一个信道( Channel ) 。
(2)生产者声明一个交换器 ,并设置相关属性,比如交换机类型、是否持久化等。
(3)生产者声明 一个队列井设置相关属性,比如是否排他、是否持久化、是否自动删除等 。
(4)生产者通过路由键将交换器和队列绑定起来。
(5)生产者发送消息至 RabbitMQ Broker,其中包含路由键、交换器等信息。
(6)相应的交换器根据接收到的路由键查找相匹配的队列 。
(7)如果找到,则将从生产者发送过来的消息存入相应的队列中。
(8)如果没有找到 ,则根据生产者配置的属性选择丢弃还是回退给生产者。
(9)关闭信道。
消费者接收消息的过程:
(1)消费者连接到 RabbitMQ Broker,建立一个连接( Connection ),开启一个信道( Channel) 。
(2)消费者向 RabbitMQ Broker 请求消费相应队列中的消息,可能会设置相应的回调函数,以及做一些准备工作。
(3)等待 RabbitMQ Broker 回应并投递相应队列中的消息,消费者接收消息。
(4)消费者确认( ack )接收到的消息 。
(5 ) RabbitMQ 从队列中删除相应己经被确认的消息 。
(6)关闭信道。