.NET/C# 客户端rabbitMQ API 指南
来源:https://www.rabbitmq.com/dotnet-api-guide.html#major-api-elements
概述
本指南介绍了 RabbitMQ .NET/C# 客户端及其公共 API。 它假定使用客户端的最新主要版本 读者熟悉基础知识。
该指南的关键部分是:
- 依赖
- 公共 API 中的重要接口和类
- 局限性
- 连接到 RabbitMQ
- 连接和通道寿命
- 客户端提供的连接名称
- 使用交换和队列
- 发布消息
- 使用订阅和使用使用者内存安全
- 异步消费者实现
- 并发注意事项和安全性
- 从网络故障中自动恢复
API 参考单独提供。
.NET 版本要求
此库的 6.x 版本系列需要 .NET 4.6.1+ 或 .NET 标准 2.0+ 实现。 对于 5.x 版本,要求是 .NET 4.5.1+ 或 .NET Standard 1.5+ 实现。
许可证
该库是开源的,在 GitHub 上开发,并在
这意味着用户可以考虑将库视为根据上述列表中的任何许可证进行许可。 例如,用户可以选择 Apache 公共许可证 2.0 并将此客户端包含在 商业产品。
依赖
客户端有几个依赖项:
使用相同依赖项的不同版本的应用程序 应使用程序集版本重定向、自动或 明确。
主要命名空间、接口和类
客户端 API 紧密基于 AMQP 0-9-1 协议模型, 具有额外的抽象,易于使用。
API 参考单独提供。
核心 API 接口和类在 RabbitMQ.Client 命名空间中定义:
using RabbitMQ.Client;
核心 API 接口和类是
- IModel:代表一个AMQP 0-9-1通道,并提供大部分操作(协议方法)
- IConnection:表示 AMQP 0-9-1 连接
- ConnectionFactory:构造 IConnection 实例
- IBasicConsumer:表示消息消费者
其他有用的接口和类包括:
- DefaultBasicConsumer:消费者常用的基类
RabbitMQ.Client 以外的公共命名空间包括:
- RabbitMQ.Client.Events:各种事件和事件处理程序 是客户端库的一部分,包括 EventingBasicConsumer, 围绕 C# 事件处理程序构建的使用者实现。
- RabbitMQ.Client.Exceptions:用户可见的异常。
所有其他命名空间都保留用于私有实现详细信息 库,尽管通常创建私有命名空间的成员 可用于使用该库的应用程序,以便允许 开发人员针对故障和差距实施解决方法 在库实现中发现。应用程序不能依赖 任何类、接口、成员变量等。出现在 私有命名空间在库的版本中保持稳定。
局限性
此客户端不支持无符号 64 位整数,表示为 键入乌龙。尝试对 ulong 值进行编码将引发异常。 请注意,支持有符号 64 位整数。
这部分是由于 AMQP 0-9-1 规范中的类型标记不明确, 部分原因是其他流行客户端支持的类型列表。
连接到 RabbitMQ
在应用程序可以使用 RabbitMQ 之前,它必须打开与 RabbitMQ 节点的连接。然后,该连接将用于执行所有后续操作 操作。连接应该是长期存在的。打开连接 对于每个操作(例如发布消息)都会非常低效,并且强烈建议不要这样做。
若要打开与 .NET 客户端的连接,请首先实例化连接工厂并将其配置为使用所需的主机名、虚拟主机、凭据、TLS 设置、 以及所需的任何其他参数。
然后调用 ConnectionFactory.CreateConnection() 方法来打开连接。 可以在服务器日志中观察到成功和不成功的客户端连接事件。
以下两个代码片段使用配置的主机名连接到 RabbitMQ 节点 使用 hostName 属性:
ConnectionFactory factory = new ConnectionFactory();
// "guest"/"guest" by default, limited to localhost connections
factory.UserName = user;
factory.Password = pass;
factory.VirtualHost = vhost;
factory.HostName = hostName;
IConnection conn = factory.CreateConnection();
ConnectionFactory factory = new ConnectionFactory();
factory.Uri = new Uri("amqp://user:pass@hostName:port/vhost");
IConnection conn = factory.CreateConnection();
使用端点列表
可以指定连接时要使用的终结点列表。第一个 将使用可访问的终结点。如果连接失败,请使用 终结点列表使应用程序可以连接到不同的 节点(如果原始节点已关闭)。
要使用多个端点,请向 ConnectionFactory#CreateConnection 提供 AmqpTcpEndpoint的列表。 AmqpTcpEndpoint 表示主机名和端口对。
ConnectionFactory factory = new ConnectionFactory();
factory.UserName = "username";
factory.Password = "s3Kre7";
var endpoints = new System.Collections.Generic.List<AmqpTcpEndpoint> {
new AmqpTcpEndpoint("hostname"),
new AmqpTcpEndpoint("localhost")
};
IConnection conn = factory.CreateConnection(endpoints);
由于 .NET 客户端使用比其他客户端更严格的 AMQP 0-9-1 URI 规范解释,因此在使用 URI 时必须小心。 特别是,主机部分不得省略,虚拟主机 空名称不可寻址。
所有工厂属性都有默认值。如果属性,则将使用该属性的默认值 在创建连接之前保持未分配状态:
财产 | 默认值 |
用户名 | “客人” |
密码 | “客人” |
虚拟主机 | "/" |
主机名 | “本地主机” |
港口 | 5672 用于常规(“纯 TCP”)连接,5671 用于启用 TLS 的连接 |
请注意,默认情况下,用户来宾只能从本地主机连接。 这是为了限制生产系统中的已知凭据使用。
然后,可以使用 IConnection 接口打开通道:
IModel channel = conn.CreateModel();
该通道现在可用于发送和接收消息, 如后续部分所述。
就像连接一样,渠道也是长久的。打开新渠道 因为每个操作都是非常低效的,并且非常不鼓励。渠道 但是,其寿命可能比连接短。例如,某些 协议错误将自动关闭频道。如果应用程序可以恢复 从他们那里,他们可以打开一个新通道并重试操作。
断开与 RabbitMQ 的连接
要断开连接,只需关闭通道和连接:
channel.Close(); conn.Close();
释放通道和连接对象是不够的,必须显式关闭它们 使用上面示例中的 API 方法。
请注意,关闭通道可能被认为是很好的做法,但在这里并不是绝对必要的 - 它会完成 在基础连接关闭时自动执行。
可以在服务器节点日志中观察到客户端断开连接事件。
连接和通道寿命
连接应该是长期存在的。底层协议的设计和优化是针对 长时间运行的连接。这意味着每个操作打开一个新连接, 例如,发布的消息是不必要的,强烈建议不要这样做,因为它会引入很多 网络往返和开销。
通道也意味着长期存在,但由于许多可恢复的协议错误将 导致通道关闭,通道寿命可能短于其连接寿命。 每个操作关闭和打开新通道通常是不必要的,但可以 适当。如有疑问,请考虑先重复使用频道。
通道级异常,例如尝试从 不存在的队列将导致通道关闭。封闭的通道不能 更长的使用时间,并且不会再从服务器接收任何事件(例如 作为消息传递)。通道级异常将由 RabbitMQ 记录 并将启动通道的关断序列(见下文)。
客户端提供的连接名称
RabbitMQ 节点的客户端信息量有限:
- 其 TCP 终结点(源 IP 地址和端口)
- 使用的凭据
仅此信息就可能使识别应用程序和实例变得困难,特别是当凭据可能 共享和客户端通过负载均衡器连接,但无法启用代理协议。
为了更轻松地在服务器日志和管理 UI 中标识客户端, AMQP 0-9-1 客户端连接(包括 RabbitMQ .NET 客户端)可以提供自定义标识符。 如果设置,标识符将在日志条目和管理 UI 中提及。标识符称为 客户端提供的连接名称。该名称可用于标识应用程序或特定组件 在应用程序中。名称是可选的;但是,强烈建议开发人员提供一个 因为它将大大简化某些操作任务。
RabbitMQ .NET 客户端提供了一个连接工厂属性,ConnectionFactory.ClientGivenName, 如果设置,则控制客户端为所有打开的新连接提供的连接名称 由这个工厂。
下面是上面使用的修改后的连接示例,它提供了这样的名称:
ConnectionFactory factory = new ConnectionFactory();
// "guest"/"guest" by default, limited to localhost connections
factory.UserName = user;
factory.Password = pass;
factory.VirtualHost = vhost;
factory.HostName = hostName;
// this name will be shared by all connections instantiated by
// this factory
factory.ClientProvidedName = "app:audit component:event-consumer";
IConnection conn = factory.CreateConnection();
使用交换和队列
客户端应用程序与交换和队列一起工作, 协议的高级构建块。 这些必须“声明”才能 使用。声明任一类型的对象只是确保其中一种 名称存在,如有必要,请创建它。
继续前面的示例,以下代码声明了一个 交换和队列,然后将它们绑定在一起。
channel.ExchangeDeclare(exchangeName, ExchangeType.Direct);
channel.QueueDeclare(queueName, false, false, false, null);
channel.QueueBind(queueName, exchangeName, routingKey, null);
这将主动声明以下对象:
- “直接”类型的非持久、非自动删除交换
- 非持久、非自动删除、非独占队列
可以使用其他参数自定义交换。 然后上面的代码将队列绑定到具有给定的交换 路由密钥。
许多通道 API (IModel) 方法都已重载。方便的 ExchangeDeclare 的簡稱形式使用合理的惡意的預言。有 还有具有更多参数的较长表单,以允许您覆盖这些参数 根据需要进行默认值,并在需要时提供完全控制。
这种“短版本,长版本”模式在整个 API 中使用。
被动声明
队列和交换可以“被动”声明。被动声明只是检查实体是否 具有提供的名称存在。如果是这样,则操作是无操作。对于队列成功 被动声明将返回与非被动声明相同的信息,即 队列中处于就绪状态的使用者和消息。
如果实体不存在,则操作将失败,并出现通道级异常。频道 之后不能使用。应该打开一个新通道。通常使用一次性(临时) 被动声明的通道。
IModel#QueueDeclarePassive 和 IModel#ExchangeDeclarePassive 是 用于被动声明的方法。下面的示例演示 IModel#QueueDeclarePassive:
var response = channel.QueueDeclarePassive("queue-name");
// returns the number of messages in Ready state in the queue
response.MessageCount;
// returns the number of consumers the queue has
response.ConsumerCount;
IModel#ExchangeDeclarePassive的返回值不包含任何有用的信息。因此 如果该方法返回并且没有发生通道异常,则表示交换确实存在。
具有可选响应的操作
一些常见的操作也有“不等待”版本,不会等待服务器 响应。例如,声明一个队列并指示服务器不发送任何 响应,使用
channel.QueueDeclareNoWait(queueName, true, false, false, null);
“无需等待”版本效率更高,但提供较低的安全保证,例如 更依赖于检测信号机制来检测失败的操作。 如有疑问,请从标准版本开始。“无需等待”版本仅在场景中需要 具有高拓扑(队列、绑定)流失。
删除实体和清除消息
可以显式删除队列或交换:
channel.QueueDelete("queue-name", false, false);
仅当队列为空时,才能删除队列:
channel.QueueDelete("queue-name", false, true);
或者如果未使用(没有任何使用者):
channel.QueueDelete("queue-name", true, false);
可以清除队列(删除其所有消息):
channel.QueuePurge("queue-name");
发布消息
要将消息发布到交易所,请使用 IModel.BasicPublish 为 遵循:
byte[] messageBodyBytes = System.Text.Encoding.UTF8.GetBytes("Hello, world!");
channel.BasicPublish(exchangeName, routingKey, null, messageBodyBytes);
对于精细控制,您可以使用重载变体来指定 强制标志,或指定消息属性:
byte[] messageBodyBytes = System.Text.Encoding.UTF8.GetBytes("Hello, world!");
IBasicProperties props = channel.CreateBasicProperties();
props.ContentType = "text/plain";
props.DeliveryMode = 2;
channel.BasicPublish(exchangeName, routingKey, props, messageBodyBytes);
这将发送传递模式为 2(持久)和 内容类型“文本/纯文本”。有关可用消息属性的更多信息,请参阅 IBasic属性接口的定义。
在以下示例中,我们发布一条带有自定义标头的消息:
byte[] messageBodyBytes = System.Text.Encoding.UTF8.GetBytes("Hello, world!");
IBasicProperties props = channel.CreateBasicProperties();
props.ContentType = "text/plain";
props.DeliveryMode = 2;
props.Headers = new Dictionary<string, object>();
props.Headers.Add("latitude", 51.5252949);
props.Headers.Add("longitude", -0.0905493);
channel.BasicPublish(exchangeName, routingKey, props, messageBodyBytes);
下面的代码示例设置消息过期时间:
byte[] messageBodyBytes = System.Text.Encoding.UTF8.GetBytes("Hello, world!");
IBasicProperties props = channel.CreateBasicProperties();
props.ContentType = "text/plain";
props.DeliveryMode = 2;
props.Expiration = "36000000";
channel.BasicPublish(exchangeName, routingKey, props, messageBodyBytes);
按订阅检索消息(“推送 API”)
接收消息的推荐和最方便的方式是使用 IBasicConsumer 界面设置订阅。然后消息将被传递 在他们到达时自动,而不必被请求 主动。
实现使用者的一种方法是使用 便利类 EventingBasicConsumer,它调度 作为 C# 事件的交付和其他使用者生命周期事件:
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (ch, ea) =>
{
var body = ea.Body.ToArray();
// copy or deserialise the payload
// and process the message
// ...
channel.BasicAck(ea.DeliveryTag, false);
};
// this consumer tag identifies the subscription
// when it has to be cancelled
string consumerTag = channel.BasicConsume(queueName, false, consumer);
另一种选择是子类 DefaultBasicConsumer, 根据需要重写方法,或直接实现IBasicConsumer。您通常需要实现核心方法IBasicConsumer.HandleBasicDeliver。
更成熟的消费者将需要进一步实施 方法。特别是,手柄模型关闭陷阱 通道/连接关闭。消费者还可以实现HandleBasicCancelOk来接收取消通知。
DefaultBasicConsumer 的 ConsumerTag 属性可以是 用于检索服务器生成的消费者标记,在以下情况下: 没有提供给原始 IModel.BasicConsumption 调用。
您可以使用 IModel.BasicCancel 取消活跃消费者:
channel.BasicCancel(consumerTag);
调用 API 方法时,您始终通过使用者来引用使用者 消费者标记,可以是客户端或服务器生成的 在 AMQP 0-9-1 规范文档中进行了解释。
消费者内存安全要求
截至版本 6.0 .NET 客户端,消息有效负载使用 System.ReadOnlyMemory<byte> 类型表示。
此库对只读内存跨度何时可以 由应用程序访问。
重要提示:使用者接口实现必须在传递处理程序方法返回之前反序列化或复制传递有效负载。 保留对有效负载的引用是不安全的:为其分配的内存可以随时释放 处理程序返回后。
异步消费者实现
客户端提供面向异步的使用者调度实现。此调度程序只能 与异步消费者一起使用,即 IAsyncBasicConsumer 实现。
为了使用此调度程序,请将 ConnectionFactory.DispatchConsumerAsync 属性设置为 true:
ConnectionFactory factory = new ConnectionFactory();
// ...
// use async-oriented consumer dispatcher. Only compatible with IAsyncBasicConsumer implementations
factory.DispatchConsumersAsync = true;
然后注册一个实现 IAsyncBasicConsumer的使用者,例如 AsyncEventingBasicConsumer 或 AsyncDefaultBasicConsumer:
var consumer = new AsyncEventingBasicConsumer(channel);
consumer.Received += async (ch, ea) =>
{
var body = ea.Body.ToArray();
// copy or deserialise the payload
// and process the message
// ...
channel.BasicAck(ea.DeliveryTag, false);
await Task.Yield();
};
// this consumer tag identifies the subscription
// when it has to be cancelled
string consumerTag = channel.BasicConsume(queueName, false, consumer);
// ensure we get a delivery
bool waitRes = latch.WaitOne(2000);
获取单个消息(轮询或“拉取 API”)
也可以按需检索单个消息(“pull API”,即轮询)。 这种消费方法效率非常低,因为它实际上是轮询 并且应用程序必须反复要求结果,即使绝大多数请求 没有结果。因此,强烈建议不要使用此方法。
若要“拉取”消息,请使用 IModel.BasicGet 方法。 返回值是 BasicGetResult 的一个实例,标头来自该实例 可以提取信息(属性)和消息正文:
bool autoAck = false;
BasicGetResult result = channel.BasicGet(queueName, autoAck);
if (result == null) {
// No message available at this time.
} else {
IBasicProperties props = result.BasicProperties;
ReadOnlyMemory<byte> body = result.Body;
...
上面的示例使用手动确认 (autoAck = false),因此应用程序还必须调用 IModel.BasicAck 来确认处理后的传递:
...
// acknowledge receipt of the message
channel.BasicAck(result.DeliveryTag, false);
}
请注意,使用此 API 获取消息的效率相对较低。如果您愿意 RabbitMQ 要向客户端推送消息,请参见下一节。
使用者的并发注意事项
库用户需要考虑许多与并发相关的主题。
在线程之间共享通道
IModel 实例使用量超过 应避免同时使用一个线程。应用程序代码 应为 IModel 实例保持清晰的线程所有权概念。
这对发布商来说是一个硬性要求:共享频道(IModel 实例) 对于并发发布将导致协议级别的帧交错不正确。 通道实例不得由在其上发布的线程共享。
如果多个线程需要访问特定的 IModel 实例,则应用程序应强制实施互斥。一 实现此目的的方法是让 IModel 的所有用户锁定实例本身:
IModel ch = RetrieveSomeSharedIModelInstance();
lock (ch) {
ch.BasicPublish(...);
}
IModel 操作序列化不正确的症状 包括但不限于:
应避免涉及在线程之间共享通道的消耗 如果可能,但可以安全地完成。
可以是多线程或在内部使用线程池(包括基于 TPL)的使用者 消费者,必须使用相互排斥的确认操作 在共享频道上。
每个连接线程的使用
在当前实现中,每个 IConnection 实例都是 由从套接字读取的单个后台线程支持,并且 将生成的事件调度到应用程序。 如果启用了检测信号,则每个连接将使用一对 .NET 计时器。
因此,通常应用程序中至少有两个线程处于活动状态 使用此库:
- 应用程序线程
- 包含应用程序逻辑,并使 调用 IModel 方法来执行协议操作。
- I/O 活动线程
- 隐藏起来,完全由 IConnection 实例管理。
线程模型的性质可见的一个位置 应用程序位于应用程序向 图书馆。此类回调包括:
- 任何IBasic消费者方法
- IModel 上的 BasicReturn 事件
- IConnection,IModel等上的任何各种关闭事件。
消费者回调和订购
从版本 3.5.0 开始,应用程序回调处理程序可以调用阻塞 操作(如 IModel.QueueDeclare 或 IModel.BasicCancel)。IBasic消费者回调是同时调用的。 但是,将保留每个通道的操作顺序。换句话说,如果消息 A 和 B 已传递 在同一通道上按此顺序处理它们。如果消息 A 和 B 在不同的渠道上交付,它们可以以任何顺序(或并行)处理。 使用者回调在任务计划程序分派的任务中调用。
处理不可路由的消息
如果发布带有“强制”标志的消息 设置,但无法交付,经纪人会将其返回给发送 客户端(通过 basic.return AMQP 0-9-1 命令)。
要收到此类退货的通知,客户端可以订阅 IModel.BasicReturn 事件。如果没有侦听器附加到 事件,则返回的消息将被静默丢弃。
channel.BasicReturn += (sender, ea) => { ... };
BasicReturn 事件将触发,例如,如果客户端 发布将“强制”标志设置为交换的消息 未绑定到队列的“直接”类型。
从网络故障中自动恢复
连接恢复
客户端和 RabbitMQ 节点之间的网络连接可能会失败。 RabbitMQ .NET/C# 客户端支持自动恢复连接 和拓扑(队列、交换、绑定和使用者)。特点 具有本指南后面介绍的某些限制。
自动恢复过程执行以下步骤:
- 重新
- 还原连接侦听器
- 重新打开频道
- 恢复频道侦听器
- 恢复频道基本.qos设置,发布者确认和交易设置
拓扑恢复在完成上述操作后开始。以下步骤是 对连接失败时已知打开的每个通道执行:
- 重新声明交换(预定义交换除外)
- 重新声明队列
- 恢复所有绑定
- 恢复所有使用者
若要启用自动连接恢复,请将 ConnectionFactory.AutomaticRecoveryEnabled 设置为 true:
ConnectionFactory factory = new ConnectionFactory();
factory.AutomaticRecoveryEnabled = true;
// connection that will recover automatically
IConnection conn = factory.CreateConnection();
如果恢复由于异常而失败(例如 RabbitMQ 节点 仍然无法访问),它将在固定时间间隔后重试(默认 为 5 秒)。可以配置间隔:
ConnectionFactory factory = new ConnectionFactory();
// attempt recovery every 10 seconds
factory.NetworkRecoveryInterval = TimeSpan.FromSeconds(10);
何时会触发连接恢复?
自动连接恢复(如果启用)将由以下事件触发:
- 在连接的 I/O 循环中引发 I/O 异常
- 套接字读取操作超时
- 检测到丢失的服务器检测信号
- 在连接的 I/O 循环中引发任何其他意外异常
以先发生者为准。
如果客户端与 RabbitMQ 节点的初始连接失败,则自动连接 复苏不会启动。应用程序开发人员负责重试 此类连接,记录失败的尝试,对数量实施限制 重试次数等。这是一个非常基本的例子:
ConnectionFactory factory = new ConnectionFactory();
// configure various connection settings
try {
IConnection conn = factory.CreateConnection();
} catch (RabbitMQ.Client.Exceptions.BrokerUnreachableException e) {
Thread.Sleep(5000);
// apply retry logic
}
当应用程序通过 Connection.Close 方法关闭连接时, 不会启动连接恢复。
通道级异常不会像往常那样触发任何类型的恢复 指示应用程序中的语义问题(例如,尝试从 不存在的队列)。
对出版的影响
连接断开时使用 IModel.BasicPublish 发布的邮件 会丢失。连接恢复后,客户端不会将它们排队等待传递。 为了确保已发布的消息到达 RabbitMQ,应用程序需要使用发布者确认并说明连接失败。
拓扑恢复
拓扑恢复涉及交换、队列、绑定的恢复 和消费者。默认情况下启用,但可以禁用:
ConnectionFactory factory = new ConnectionFactory();
IConnection conn = factory.CreateConnection();
factory.AutomaticRecoveryEnabled = true;
factory.TopologyRecoveryEnabled = false;
故障检测和恢复限制
自动连接恢复有许多限制和有意 应用程序开发人员需要注意的设计决策。
当连接断开或丢失时,需要时间来检测。 因此,有一个时间窗口,其中 库和应用程序不知道有效 连接失败。在此期间发布的任何消息 时间范围被序列化并写入 TCP 套接字 照常。他们交付给经纪人只能是 通过出版商保证确认:在 AMQP 0-9-1 中发布完全是 异步设计。
当 启用自动恢复的连接,恢复 在可配置的延迟后开始,5 秒后 违约。此设计假设即使很多 网络故障是暂时性的,通常很短 活着,他们不会瞬间消失。连接恢复 尝试将以相同的时间间隔继续,直到 已成功打开新连接。
当连接处于恢复状态时,任何 在其频道上尝试发布将被拒绝 有一个例外。客户端当前未执行 此类传出消息的任何内部缓冲。是的 应用程序开发人员有责任跟踪此类情况 消息,并在恢复成功后重新发布它们。发布者确认是协议扩展 这应该由无法承受消息丢失的发布者使用。
当通道因以下原因而关闭时,连接恢复不会启动 通道级异常。此类异常通常表示应用程序级别 问题。图书馆无法就何时做出明智的决定 案子。
即使在连接恢复启动后,也不会恢复已关闭的频道。 这包括显式关闭的通道和通道级异常 以上案例。
手动确认和自动恢复
使用手动确认时,可能会 消息之间与 RabbitMQ 节点的网络连接失败 交付和确认。连接恢复后, RabbitMQ 将重置所有通道上的交付标签。
这意味着使用旧交付标记的 basic.ack、basic.nack 和 basic.reject 将导致通道异常。为了避免这种情况, RabbitMQ .NET 客户端跟踪和更新传递标记以使其单调 在恢复之间增长。
IModel.BasicAck、IModel.BasicNack 和 IModel.BasicReject 然后将调整后的递送标签转换为 RabbitMQ 使用的标签。
带有过时传递标记的确认不会 送。使用手动确认和自动确认的应用程序 恢复必须能够处理重新交付。