amqp rabbitmq_.NET Core和RabbitMQ第2部分-通过AMQP进行通信

amqp rabbitmq

In part 1 I demonstrated how to create a simple consumer and producer using ASP.NET Core Hosted Services. In this part, I will cover everything that happens under the wraps, the communication between the client and the server, the connection, the message publishing and consuming.

第1部分中,我演示了如何使用ASP.NET Core托管服务创建简单的使用者和生产者。 在这一部分中,我将介绍所有发生的一切,客户端与服务器之间的通信,连接,消息的发布和使用。

AMQP (AMQP)

AMQP is the protocol that RabbitMQ uses to communicate with client applications. Aside from the fact that AMQP defines the wire protocol for RabbitMQ to communicate, it also provides some logical model that comprises of classes and methods which RabbitMQ adopts in its core functionality.

AMQP是RabbitMQ用于与客户端应用程序通信的协议。 除了AMQP定义了RabbitMQ进行通信的有线协议这一事实外,它还提供了一些逻辑模型,其中包括RabbitMQ在其核心功能中采用的类和方法。

In its core, AMQP defines three components that are very crucial in implementing a message base architecture.

AMQP在其核心中定义了三个组件,这些组件对于实现消息库体系结构至关重要。

  1. The exchange, which routes messages to queues. By reading the message properties, the exchange can determine to which queue should route each message.

    交换,将消息路由到队列。 通过读取消息属性,交换机可以确定将每个消息路由到哪个队列。
  2. The queue, which provides a FIFO data structure that stores messages either on memory or disk (persistent).

    该队列提供了FIFO数据结构,该结构将消息存储在内存或磁盘上(持久)。
  3. The binding, which defines a relationship between a queue and an exchange. When a message is published to that exchange, its routing key, or other message properties (depends the routing strategy that’s applied) are evaluated against that binding. The routing key might be the name of the queue or it might be an arbitrary string that can be matched by pattern matching rules. When it’s successfully evaluated, the exchange can determine to which queues it should route its messages.

    绑定,它定义队列和交换之间的关系。 将消息发布到该交换机时,将根据该绑定评估其路由密钥或其他消息属性(取决于所应用的路由策略)。 路由键可以是队列的名称,也可以是可以由模式匹配规则匹配的任意字符串。 成功评估后,交换机可以确定将消息路由到哪个队列。

With AMQP, communication is bi-directional, which means the client talks to the server but also the server can talk directly to the client. These conversations are RPC, as per AMQP specification and are issued whenever the client talks to the server, for example to connect or publish messages or when the server talks to the client, for example when the client consumes messages.

使用AMQP,通信是双向的,这意味着客户端与服务器对话,但是服务器也可以直接与客户端对话。 根据AMQP规范,这些对话是RPC,并且在客户端与服务器对话时(例如,连接或发布消息)或在服务器与客户端对话(例如,客户端使用消息时)时发出。

连接 (Connection)

Let’s look how the first conversation between the client and the server looks like. In the previous post, I’m connecting to the server by calling the CreateConnection method on the connection factory object. This kickstarted the following

让我们看一下客户端和服务器之间的第一次对话的样子。 在上一篇文章中,我通过在连接工厂对象上调用CreateConnection方法来连接到服务器。 这启动了以下

  1. Client sends a protocol header frame

    客户端发送协议头帧
  2. Server responds with a Connection.Start command

    服务器以Connection.Start命令响应

  3. Client responds with a Connection.StartOk command

    客户端以Connection.StartOk命令响应

Image for post

Few things to notice from the above.

上面没有什么要注意的。

The conversation kickstarts by a protocol header frame. This is a special frame (more on them in a while) that is used only during connection.

对话由协议标头框架开始。 这是一个特殊的框架(稍后会详细介绍),仅在连接期间使用。

Next, server issues a command, the Connection.Start. AMQP is a protocol which defines a unique dialect that is usually more or less adopted by implementations of that protocol, in this case RabbitMQ. Its aim is to make a common language between the client and the server. The commands define a class and a method, just like in a OOP language, like C#. The class, groups functionality in a single place and the method executes some task. In the example above, the Connection is the class and Start is the method of that class. The Connection.StartOk is merely a response to the command issued earlier. A command usually carries something, like arguments or some other data. Mostly, these data are encoded and the command is encapsulated in a data structure called frame.

接下来,服务器发出命令Connection.Start 。 AMQP是一种协议,它定义了唯一的方言,该方言通常或多或少地被该协议的实现所采用,在这种情况下为RabbitMQ。 其目的是使客户端和服务器之间使用通用语言。 这些命令定义了一个类和一个方法,就像在C#这样的OOP语言中一样。 该类在单个位置将功能分组,并且该方法执行某些任务。 在上面的示例中, Connection是类,而Start是该类的方法。 Connection.StartOk只是对先前发出的命令的响应。 命令通常包含某些内容,例如参数或其他数据。 通常,这些数据经过编码,命令封装在称为frame的数据结构中。

频道 (Channels)

Connecting to RabbitMQ is not enough if you want your application to be up and running with the broker. It is required to open at least one channel for the connection. Be advised there’s no issue if you need more than one channels open on a single connection, this is totally doable and it’s called multiplexing, however for every open channel, resources are allocated on the server side, so be careful with how many channels you have open as they are costly, resource-wise and might degrade your server’s performance.

如果您要启动应用程序并与代理一起运行,仅连接到RabbitMQ是不够的。 需要打开至少一个用于连接的通道。 请注意,如果您需要在单个连接上打开多个通道,这是没有问题的,这是完全可行的,这称为多路复用,但是对于每个打开的通道,资源都是在服务器端分配的,因此请注意您拥有多少个通道由于它们昂贵,资源节约,因此可能会打开,并且可能会降低服务器的性能。

镜框 (Frames)

A frame is a formal data structure in the AMQP specification which carries information and it’s meaningful for a RabbitMQ server or client. It can contain metadata or actual content. Five different types of frames exist:

框架是AMQP规范中的一种正式数据结构,用于承载信息,对于RabbitMQ服务器或客户端而言是有意义的。 它可以包含元数据或实际内容。 存在五种不同类型的框架:

  1. Protocol header frame

    协议头帧
  2. Method frame

    方法框架
  3. Content header frame

    内容标题框架
  4. Body frame

    车架
  5. Heartbeat frame

    心跳帧

One important thing to note, is that the method frame (2), the content header frame (3) and the body frame (4) are all sent in order, upon publishing or consuming messages.

要注意的一件事是,在发布或使用消息时,方法框架(2),内容标头框架(3)和主体框架(4)均按顺序发送。

I will go through each one of them shortly, but let’s first break down a frame, how it looks like and which parts make it.

我将很快介绍它们中的每一个,但让我们首先分解一下框架,它的外观和组成部件。

车架内部 (Frame internals)

Every single frame is split into 3 parts

每个单帧分为3个部分

  1. Frame header

    框架头
  2. Payload (frame body)

    有效载荷(车架)
  3. End marker

    结束标记
Image for post

框架头 (Frame header)

The header contains 3 parts

标头包含3个部分

  1. The frame type. It’s just a single byte indicating the type of that frame, from the types I mentioned earlier.

    框架类型。 从我之前提到的类型来看,它只是一个字节,指示该帧的类型。
  2. The channel number. This is an integer number which uniquely identifies the channel. RabbitMQ can determine to which channel this frame is destined for.

    通道号。 这是一个唯一标识通道的整数。 RabbitMQ可以确定此帧定向到哪个通道。
  3. Frame size. The total size of the frame in bytes.

    框架尺寸。 帧的总大小(以字节为单位)。
Image for post

有效载荷 (Payload)

It encapsulates the body of the frame. For some frames, this part is encoded, for performance reasons, but for others it just raw data, either binary or string.

它封装了框架的主体。 对于某些帧,出于性能原因,对这一部分进行了编码,而对于另一些帧,则仅对原始数据(二进制或字符串)进行了编码。

结束标记 (End marker)

It indicates the end of the frame, it’s the ASCII value 206.

它指示帧的结束,它是ASCII值206。

协议头帧 (The protocol header frame)

This frame is only sent once and only aims to kickstart the communication between the client and server. This and the heartbeat frame should not worry developers, because the driver abstracts these away.

该帧仅发送一次,并且仅用于启动客户端和服务器之间的通信。 这和心跳框架不应该让开发人员担心,因为驱动程序将它们抽象化了。

On the example shown in previous post, this happens on RabbitMqClientBase, line 32, when I create a connection with RabbitMQ.

在上RabbitMqClientBase文章中显示的示例中,当我与RabbitMQ创建连接时,这发生在RabbitMqClientBase第32行。

心跳帧 (The heartbeat frame)

This is also a frame that’s abstracted away by the client library. It is like a healthcheck, sent from and to RabbitMQ to ensure both sides are able to communicate. If the client application does not respond to the heartbeat request from RabbitMQ, the connection will be closed. The default interval is 600 seconds, which of course is configurable via the rabbitmq.config file.

这也是客户端库抽象的框架。 就像健康检查一样,从RabbitMQ发送或发送给RabbitMQ,以确保双方都能进行通信。 如果客户端应用程序不响应来自RabbitMQ的心跳请求,则连接将关闭。 默认间隔为600秒,当然可以通过rabbitmq.config文件进行配置。

方法框架 (The method frame)

During publishing or consuming, the method frame is the first frame that’s sent. It carries the command and required parameters related to that command. For example, the Basic.Publish command, which is about message publishing, would require the exhange name, routing key, mandatory and immediate flag parameters, which of course will be carried in the same method frame.

在发布或使用期间,方法框架是发送的第一个框架。 它包含命令和与该命令相关的必需参数。 例如,有关消息发布的Basic.Publish命令将需要交换名称,路由键,强制和立即标记参数,这些参数当然将在同一方法框架中携带。

RabbitMQ will extract the command with its parameters from the frame and execute it. Let’s see how a method frame looks like.

RabbitMQ将从帧中提取带有其参数的命令并执行它。 让我们看看方法框架的样子。

Image for post

From the above, for that specific frame, we see that it starts with the class (Basic) and method (Publish) ids. Next, it contains parameter information, such as the exchange name and routing key, which will help RabbitMQ to determine to which queue to publish the message to. The mandatory flag says that the message must be delivered to a queue. If the message cannot be routed to a queue, either because the queue or the binding does not exist, then it returns a Basic.Return request back to client application. If you set that flag to true, you should handle the Basic.Return RPC send by the server in your code. The immediate flag tells the server how to react if the message cannot be routed to a queue consumer immediately. If this flag is set, the server will return an undeliverable message with a Basic.Return method. If this flag is false, the server will queue the message, but with no guarantee that it will ever be consumed.

从上面开始,对于该特定框架,我们看到它以类(Basic)和方法(Publish)ID开头。 接下来,它包含参数信息,例如交换名称和路由密钥,这将帮助RabbitMQ确定将消息发布到的队列。 mandatory标志表示必须将消息传递到队列。 如果由于队列或绑定不存在而导致消息无法路由到队列,则它将Basic.Return请求返回给客户端应用程序。 如果将该标志设置为true,则应在代码中处理服务器的Basic.Return RPC发送。 immediate标记告诉服务器如果消息不能立即路由到队列使用者,该如何React。 如果设置了此标志,则服务器将使用Basic.Return方法返回无法传递的消息。 如果此标志为假,则服务器将对消息进行排队,但不保证它将被使用。

In ProducerBase class, on the code example, on line 31, I'm issuing a Basic.Publish command to RabbitMQ which is going to send the 3 frames (starting with the method frame as showed above).

ProducerBase类中,在代码示例的第31行上,我向RabbitMQ发出了Basic.Publish命令,该命令将发送3个帧(从上面显示的方法帧开始)。

I’ve captured the RPC command that’s sent using tcpdump and then opened it using Wireshark. In the image below we can see the method frame on the request packet.

我已经捕获了使用tcpdump发送的RPC命令,然后使用Wireshark将其打开。 在下图中,我们可以看到请求数据包上的方法框架。

Image for post

内容标题框架 (The content header frame)

That’s the next frame in order that’s sent after the method frame. This one contains metadata information that describe the message and it’s uber useful for other applications too, as these attributes can tell the code how to treat the message, leading to more intelligent consumers which define strict contracts for communication with other producers.

这是在方法帧之后按顺序发送的下一帧。 其中包含描述消息的元数据信息,它对于其他应用程序也非常有用,因为这些属性可以告诉代码如何处理消息,从而导致更聪明的消费者定义了与其他生产者进行通信的严格合同。

This frame contains information coming from the Basic.Properties table. In code, I would need to set the properties manually and add them to the RPC request, e.g. on Channel.BasicPublish method call which contains overloads that accept an IBasicProperties object. In the example on previous post, I did set few properties on Basic.Properties table, such as the app Id, content type, delivery mode and timestamp. Of course there are plenty of other properties available, check the table below which lists all properties. For more comprehensive list check the IBasicProperties specification. Please note, the reply-to-address is not listed in AMQP specification, it's a convenience property provided by the .NET driver.

该框架包含来自Basic.Properties表的信息。 在代码中,我需要手动设置属性,并将其添加到RPC请求中,例如在Channel.BasicPublish方法调用上,该方法调用包含接受IBasicProperties对象的重载。 在上一篇文章的示例中,我确实在Basic.Properties表上设置了一些属性,例如应用程序ID,内容类型,交付方式和时间戳。 当然,还有许多其他属性可用,请查看下表,其中列出了所有属性。 有关更全面的列表,请查看IBasicProperties 规范 。 请注意,AMQP规范中未列出reply-to-address ,这是.NET驱动程序提供的便利属性。

app-id cluster-id (deprecated content-encoding content-type correlation-id delivery-mode expiration headers message-id persistent priority reply-to timestamp reply-to-address* type user-id

app-id cluster-id(已弃用的内容编码内容类型相关性id传递模式到期标头消息ID持久优先级答复时间戳答复地址*类型用户ID

The producer’s code, sets the properties using the CreateBasicProperties method of channel object.

生产者的代码使用通道对象的CreateBasicProperties方法设置属性。

Let’s break down the previous content header frame sent by the producer.

让我们分解生产者发送的先前的内容头帧。

Image for post

One thing to note here is the body size, which is the first property of the content header frame. If that exceeds the max frame size defined by RabbitMQ, then the contents of the message will split in several body frames and each will be sent in order. The next property defined is the property flags that tells RabbitMQ which properties have been set.

这里要注意的一件事是主体大小,它是内容标题框架的第一个属性。 如果超过了RabbitMQ定义的最大帧大小,则消息的内容将拆分为多个正文帧,并且将按顺序发送每个内容。 定义的下一个属性是告诉RabbitMQ已设置哪些属性的属性标志。

The rest of the properties set are pretty self-explanatory and it all depends on which properties are set by the application code. Following is the captured package for the Basic.Publish command, showing the content header frame with its message properties defined.

属性集的其余部分很容易解释,这完全取决于应用程序代码设置的属性。 以下是Basic.Publish命令的捕获包,其中显示了内容标头框架及其定义的消息属性。

Image for post

车身框架 (The body frame)

That’s the final frame sent during producing/consuming and the most important one, as it holds all the beef, i.e. the actual content of the message, which might be binary or text.

那是生产/消费过程中发送的最后一帧,也是最重要的一帧,因为它包含所有内容,即消息的实际内容,可以是二进制或文本。

Image for post

In similar fashion with the above, Wireshark can help me inspect the body frame of the same Basic.Publish command. The payload is serialized into JSON format.

与上述类似,Wireshark可以帮助我检查同一Basic.Publish命令的主体框架。 有效负载被序​​列化为JSON格式。

Image for post

发布和使用消息时进行通信 (Communication while publishing and consuming messages)

To publish or consume messages to/from RabbitMQ, an application needs to

要向RabbitMQ发布消息或从RabbitMQ使用消息,应用程序需要

  1. Declare an exchange

    声明交换
  2. Declare a queue

    宣告队列
  3. Bind the queue to the exchange

    将队列绑定到交易所

Let’s see how this communication unfolds. In RabbitMqClientBase class, which is common for both my producer and consumer apps, I'm creating an exchange with the following code.

让我们看看这种交流是如何展开的。 在我的生产者和消费者应用程序都通用的RabbitMqClientBase类中,我正在使用以下代码创建一个交换。

Doing this, the client sents an Exchange.Declare command to the server, with the latter replying with an Exchange.DeclareOk response. The command tells RabbitMQ to create an exchange, with the name provided in the arguments. If that exchange already exists, RabbitMQ will do nothing.

这样做,客户端将Exchange.Declare命令发送到服务器,后者通过Exchange.DeclareOk响应进行响应。 该命令告诉RabbitMQ使用参数中提供的名称创建一个交换。 如果该交换已经存在,RabbitMQ将不执行任何操作。

Image for post

Following is the captured traffic in Wireshark.

以下是Wireshark中捕获的流量。

Image for post

Next, the queue must be created. Similarly, a Queue.Declare command is sent from the client, telling RabbitMQ to create a queue. This request is idempotent as well, meaning, if the queue already exists, no action will be taken. RabbitMQ responds with Queue.DeclareOk.

接下来,必须创建队列。 同样,从客户端发送Queue.Declare命令,告诉RabbitMQ创建队列。 该请求也是等幂的,这意味着,如果队列已经存在,则将不执行任何操作。 RabbitMQ以Queue.DeclareOk响应。

The code above initiates the following conversation.

上面的代码启动以下对话。

Image for post

And here’s the captured traffic in Wireshark, confirming the above.

这是Wireshark中捕获的流量,确认以上内容。

Image for post

Finally, the queue should bind to the exchange. The same communication pattern occurs here as well, a Queue.Bind command is sent by the client, and the server replies with a Queue.BindOk response. This is idempotent too.

最后,队列应绑定到交换。 此处也发生相同的通信模式,客户端发送Queue.Bind命令,服务器回复Queue.BindOk响应。 这也是幂等的。

Calling the QueueBind method on the .NET client, produces the following conversation.

在.NET客户端上调用QueueBind方法,将产生以下对话。

Image for post

And here’s what I’ve captured in Wireshark.

这就是我在Wireshark中捕获的内容。

Image for post

准备消费者 (Preparing the consumer)

To start consuming, the .NET client must handle the Received event of the consumer object and call the BasicConsume method on the channel.

若要开始使用,.NET客户端必须处理使用方对象的Received事件,并在通道上调用BasicConsume方法。

The latter sends a Basic.Consume command to the server, telling RabbitMQ to deliver any incoming messages from that specific queue, defined in the arguments, back to that consumer. RabbitMQ responds with a Basic.ConsumeOk after it executes the Consume method.

后者向服务器发送Basic.Consume命令,告诉RabbitMQ将参数中定义的特定队列中的所有传入消息传递回该使用者。 RabbitMQ执行Consume方法后Basic.ConsumeOk进行响应。

Image for post

And the capture on Wireshark

还有在Wireshark上的捕获

Image for post

New messages are published to a queue with the Basic.Publish command. When the server receives that, it inspects the method frame to extract the exchange name and routing key. With that information in hand, it can match the exchange and evaluate the bindings in the exchange. With the bindings been evaluated, the broker knows to which queues this exchange is attached to, so the only thing that is left is to match the routing key for each queue with the routing key provided in the message properies. If the queue is matched, then the message is dequeued from that queue and sent to the consumer(s), with the broker issuing a Basic.Deliver command to interested parties.

使用Basic.Publish命令将Basic.Publish发布到队列中。 服务器收到该消息后,将检查方法框架以提取交换名称和路由密钥。 有了这些信息,它就可以匹配交换并评估交换中的绑定。 通过评估绑定,代理可以知道此交换连接到的队列,因此剩下的唯一事情就是将每个队列的路由密钥与消息属性中提供的路由密钥进行匹配。 如果队列匹配,则消息从该队列中出队并发送给消费者,而代理向感兴趣的各方发出Basic.Deliver命令。

The consumer will be receiving messages until it is disconnected or until a Basic.Cancel command is sent to the broker. In the code snippet above, I've set the autoAck property to false, which means the consumer must send a Basic.Ack command to the broker.

使用者将一直接收消息,直到断开连接或将Basic.Cancel命令发送到代理为止。 在上面的代码片段中,我将autoAck属性设置为false,这意味着使用者必须将Basic.Ack命令发送给代理。

It is required to acknowledge the message, so the broker knows if it should be dequeued or not. If the autoAck property was true, it wouldn't be needed to return an acknowledgment, the message would be acknowledged automatically the moment it was consumed. This of course depends on your application needs, if you need more control, set the property to false and manage acknowledgements in the code.

需要确认该消息,以便代理知道是否应该将其出队。 如果autoAck属性为true,则不需要返回确认,则消息将在消耗后立即自动确认。 当然,这取决于您的应用程序需求,如果需要更多控制,请将属性设置为false并管理代码中的确认。

Something also worth noting here, is the delivery-tag, which is passed as an argument with the Basic.Ack command. I've picked this delivery tag from the Basic.Deliver request and I've provided it to the Ack method. This is required, because RabbitMQ will use the channel id along with the delivery tag to handle acknowledgments.

在这里还需要注意的是交付标记,它是Basic.Ack命令作为参数传递的。 我已从Basic.Deliver请求中选择了此交付标签,并将Basic.Deliver提供给Ack方法。 这是必需的,因为RabbitMQ将使用通道ID和传递标签来处理确认。

Image for post

发布消息 (Publishing a message)

The producer is now ready to publish some messages, same goes for the consumer, which is up and running and is listening for messages on the queue. Let’s see how it looks like, on the wire, when a Basic.Publish command is sent and how the 3 components of the demo system (producer, server and consumer) interact with each other.

生产者现在准备发布一些消息,对于消费者来说也是如此,它已经启动并正在运行,并且正在侦听队列中的消息。 让我们看看发送Basic.Publish命令时的Basic.Publish ,以及演示系统的3个组件(生产者,服务器和使用者)如何相互影响。

Image for post

From the above, we see that the server, as soon as it enqueues a freshly published message to its queue, it looks to deliver it to the corresponding consumer, making a Basic.Deliver RPC request to the client. The following capture in Wireshark proves that point.

从上面可以看到,服务器将新发布的消息加入队列后,便会立即将其传递给相应的使用者,从而向客户端发出Basic.Deliver RPC请求。 Wireshark中的以下捕获证明了这一点。

Image for post

The Basic.Publish command originates from 172.20.0.4, which is the producer application. Its destination is the RabbitMQ server, which address is 172.20.0.2.

Basic.Publish命令起源于生产者应用程序172.20.0.4。 它的目的地是RabbitMQ服务器,其地址是172.20.0.2。

Next, the server from the same 172.20.0.2 address, sends a Basic.Deliver command to the consumer application, on address 172.20.0.3.

接下来,服务器从相同的172.20.0.2地址向地址172.20.0.3的使用者应用程序发送Basic.Deliver命令。

Finally, the consumer client application sends a Basic.Ack back to the server.

最后,消费者客户端应用程序将Basic.Ack发送回服务器。

摘要 (Summary)

The book RabbitMQ In Depth has been a great inspiration for this post, it’s an excellent guide, I would say the most comprehensive source for RabbitMQ out there. I definitely recommend it if you are interested in learning or currently using RabbitMQ for enterprise level applications.

RabbitMQ In Depth 》一书对本文的启发很大,它是一个很好的指南,我想说的是RabbitMQ的最全面资料。 如果您有兴趣学习或当前将RabbitMQ用于企业级应用程序,我绝对会推荐它。

For a detailed guide on the RabbitMQ .NET client, please check the API documentation.

有关RabbitMQ .NET客户端的详细指南,请查看API文档

Full code of the sample producer and consumer applications can be found on my GitHub repository.

样本生产者和消费者应用程序的完整代码可以在我的GitHub 存储库中找到。

If you liked this blog, please like and share! For more, follow me on Twitter.

如果您喜欢此博客,请喜欢并分享! 有关更多信息,请在Twitter上关注我。

Please find a link to the original post here.

在此处找到原始帖子的链接。

翻译自: https://medium.com/@giorgos.dyrrahitis/net-core-and-rabbitmq-part-2-communication-via-amqp-35c5518cb64

amqp rabbitmq

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值