RabbitMQ in Depth札记——AMQ协议
RPC传输
作为AMQP的实现,RabbitMQ使用RPC(remote procedure call)模式进行远程会话。而不同于一般的RPC会话——客户端发出指令,服务端响应,但服务端不会向客户端发出指令;在AMQP规范中,服务端与客户端皆会发出指令。
对于AMQP,客户端首先发送protocol header至服务端。不过其并不能被当作一条请求指令,RabbitMQ的第一条指令是响应该信息的Connection.Start指令。其后客户端通过Connection.StartOK回应此RPC请求。
为了建立客户端与服务端的AMQP连接(connection),需要一连串的三次同步RPC请求,才能开始(start),调整(tune),打开(open)一条连接。一旦此过程完成,RabbitMQ便为应用程序即将发出的请求作好准备了。
AMQP规范里定义了信道(channel)的概念用于通信。信道使用AMQP连接作为通信的媒介,而其本身有着隔离会话的功能。一个AMQP连接可以包含多个信道,允许客户端与服务端之间的多个会话同时发生,从技术上讲,这被称作复用,它对于处理多个任务的多线程或异步应用十分有用。
AMQP的PRC帧结构
当指令发送至RabbitMQ及由其发出时,所需的参数皆被封装为“帧”(frame)的数据结构——你可以将其想象成火车的运货车厢。AMQP的帧由五个部分组成:
- 帧的类型
- 信道编号
- 帧的大小
- 帧的载荷(payload)
- 结束字节标志(ASCII值206)
AMQP规范定义了五种帧类型:协议header帧,方法帧,内容header帧,body帧以及心跳帧。每一种帧类型都有其特定功能。
- 协议header帧仅在连接RabbitMQ时使用一次。
- 方法帧在发送至RabbitMQ或由其发送的请求或应答中使用。
- 内容header帧包含消息的大小与属性。
- body帧包含消息的内容。
- 心跳帧用于检查确认连接双方是否可用且工作正常。
当向RabbitMQ发送一条消息,方法帧,header帧与body帧会被用到。首先被发送的是方法帧,其携带指令及所需的参数。之后的内容header帧包含消息属性及body大小。AMQP会有最大帧大小的限制(默认为131KB),如果消息的body超过这个大小,内容会被分隔成多个body帧。这些帧始终以同样顺序发送:一个方法帧,内容header帧,和一或多个body帧。
如上图所示,当发送一条消息,方法帧内包含Basic.Publish指令,其后内容header帧包含着消息属性。这些属性被封装在AMQP规范的数据结构——Basic.Properties。最后消息的内容被整理成一定数量的body帧。
为了减小数据大小提高传输效率,方法帧与内容header帧内的数据都经过压缩。但body帧里的数据是未被压缩的,并且对于RabbitMQ而言这部分数据也是不会被检验的(相对于方法帧与内容header帧里的数据)。
AMQP协议的使用
在发送消息之前,至少需要完成三步配置,设置交换机(Exchange)及队列(Queue),并将二者绑定(Binding)。
交换机通过Exchange.Declare指令创建。一旦RabbitMQ完成创建,一个Exchange.DeclareOK的方法帧会被发送以作为响应。若是有任何缘由导致创建指令失败,RabbitMQ会关闭相应的信道。
队列由Queue.Declare指令创建。同样地,若创建失败,相应信道会被关闭。
当交换机与队列都完成创建后,通过Queue.Bind指令,可以绑定一个队列至一个交换机。
RabbitMQ接收一条消息时,会从方法帧开始检测。Basic.Publish方法帧会包含交换机名称,路由键值等关键信息。当RabbitMQ评估这些数据时,会尝试用交换机名称与所配置的交换机相匹配。当找到合适的交换机,它会再评估其中的绑定关系,通过路由键值以找到正确的队列。如果能找到符合条件的队列,RabbitMQ服务器会以先进先出(FIFO)的顺序对消息进行排列。加入队列的并非实际的消息数据,而是其引用。这样做可以大幅提升性能,尤其当消息需要发布到多个队列时。只有所有队列中所引用的消息皆被投递或移除,实际的消息数据才会被RabbitMQ从内存中移除。
在消费(consume)消息时,需要注意Basic.Consume指令中no_ack参数的设置。如果no_ack标置设为true时,RabbitMQ将会持续发送消息,除非消费侧发送Basic.Cancel指令或者消费侧断开连接;若设置为false时,消费侧必须在每次接收到消息时发送Basic.Ack请求。这种情形下,消费侧必须在Basic.Deliver方法帧中携带delivery tag参数。RabbitMQ使用这个投递标签与信道一并作为通信的唯一标识,以用于消息应答,拒绝及否定应答。