服务总线死信队列概述Overview of Service Bus dead-letter queues

Azure 服务总线队列和主题订阅提供一个名为“死信队列”(DLQ) 的辅助子队列。Azure Service Bus queues and topic subscriptions provide a secondary subqueue, called a dead-letter queue (DLQ). 死信队列不需要显式创建,并且不能删除,也不能以其他方式独立于主实体进行管理。The dead-letter queue doesn't need to be explicitly created and can't be deleted or otherwise managed independent of the main entity.

本文介绍服务总线中的死信队列。This article describes dead-letter queues in Service Bus. GitHub 上的死信队列示例对很多讨论进行了说明。Much of the discussion is illustrated by the Dead-Letter queues sample on GitHub.

死信队列The dead-letter queue

死信队列的用途是存放无法传递给任何接收方的消息或存放无法处理的消息。The purpose of the dead-letter queue is to hold messages that can't be delivered to any receiver, or messages that couldn't be processed. 然后,可从 DLQ 中删除和检查这些消息。Messages can then be removed from the DLQ and inspected. 应用程序可能会在操作员的帮助下,更正问题并重新提交消息,记录出错的实际情况和执行更正操作。An application might, with help of an operator, correct issues and resubmit the message, log the fact that there was an error, and take corrective action.

从 API 和协议的角度看,DLQ 与任何其他队列都极其相似,不同的是,消息只能通过父实体的死信操作提交。From an API and protocol perspective, the DLQ is mostly similar to any other queue, except that messages can only be submitted via the dead-letter operation of the parent entity. 此外,无法查看生存时间,而且不能将 DLQ 中的消息设为死信。In addition, time-to-live isn't observed, and you can't dead-letter a message from a DLQ. 死信队列完全支持扫视锁定传递和事务性操作。The dead-letter queue fully supports peek-lock delivery and transactional operations.

不能自动清理 DLQ。There's no automatic cleanup of the DLQ. 消息将保留在 DLQ 中,直到显式从 DLQ 中检索它们以及对死信消息调用 Complete() 为止。Messages remain in the DLQ until you explicitly retrieve them from the DLQ and call Complete() on the dead-letter message.

DLQ 消息计数DLQ message count

无法在主题级别获取死信队列中的消息计数。It's not possible to obtain count of messages in the dead-letter queue at the topic level. 这是因为消息不会保留在主题级别,除非服务总线引发了内部错误。That's because messages don't sit at the topic level unless Service Bus throws an internal error. 相反,当发送方将消息发送到某个主题时,消息会在数毫秒内转发给该主题的订阅,因此不再位于主题级别。Instead, when a sender sends a message to a topic, the message is forwarded to subscriptions for the topic within milliseconds and thus no longer resides at the topic level. 因此,你可以在与该主题的订阅相关联的 DLQ 中查看消息。So, you can see messages in the DLQ associated with the subscription for the topic. 在以下示例中,服务总线资源管理器显示订阅“test1”的 DLQ 中当前存在 62 条消息。In the following example, Service Bus Explorer shows that there are 62 messages currently in the DLQ for the subscription "test1".

DLQ 消息计数

还可以使用以下 Azure CLI 命令获取 DLQ 消息的计数:az servicebus topic subscription showYou can also get the count of DLQ messages by using Azure CLI command: az servicebus topic subscription show.

将消息移到 DLQMoving messages to the DLQ

服务总线中有几个活动会导致从消息引擎本身将消息推送到 DLQ。There are several activities in Service Bus that cause messages to get pushed to the DLQ from within the messaging engine itself. 应用程序也可以显式将消息移到 DLQ。An application can also explicitly move messages to the DLQ.

如果消息是由代理移动的,在代理对消息调用其内部版本的 DeadLetter 方法时,会将两个属性添加到消息:DeadLetterReasonDeadLetterErrorDescriptionAs the message gets moved by the broker, two properties are added to the message as the broker calls its internal version of the DeadLetter method on the message: DeadLetterReason and DeadLetterErrorDescription.

应用程序可以为 DeadLetterReason 属性定义自己的代码,但系统设置以下值。Applications can define their own codes for the DeadLetterReason property, but the system sets the following values.

条件Condition DeadLetterReasonDeadLetterReason DeadLetterErrorDescriptionDeadLetterErrorDescription
AlwaysAlways HeaderSizeExceededHeaderSizeExceeded 已超过此流的大小配额。The size quota for this stream has been exceeded.
!TopicDescription。!TopicDescription.
EnableFilteringMessagesBeforePublishing 和 SubscriptionDescription。EnableFilteringMessagesBeforePublishing and SubscriptionDescription.
EnableDeadLetteringOnFilterEvaluationExceptionsEnableDeadLetteringOnFilterEvaluationExceptions
exception.GetType().Nameexception.GetType().Name exception.Messageexception.Message
EnableDeadLetteringOnMessageExpirationEnableDeadLetteringOnMessageExpiration TTLExpiredExceptionTTLExpiredException 消息已过期并已设为死信。The message expired and was dead lettered.
SubscriptionDescription.RequiresSessionSubscriptionDescription.RequiresSession 会话 ID 为 null。Session ID is null. 启用会话的实体不允许使用会话标识符为 null 的消息。Session enabled entity doesn't allow a message whose session identifier is null.
!死信队列!dead letter queue MaxTransferHopCountExceededMaxTransferHopCountExceeded 在队列之间转发时允许的最大跃点数。The maximum number of allowed hops when forwarding between queues. 值设置为 4。Value is set to 4.
应用程序显式设为死信Application explicit dead lettering 由应用程序指定Specified by application 由应用程序指定Specified by application

超过 MaxDeliveryCountExceeding MaxDeliveryCount

每个队列和订阅都具有 QueueDescription.MaxDeliveryCountSubscriptionDescription.MaxDeliveryCount 属性;默认值为 10。Queues and subscriptions each have a QueueDescription.MaxDeliveryCount and SubscriptionDescription.MaxDeliveryCount property respectively; the default value is 10. 只要消息在 (ReceiveMode.PeekLock) 锁下传递,但已显式放弃或锁已过期,消息 BrokeredMessage.DeliveryCount 就会递增。Whenever a message has been delivered under a lock (ReceiveMode.PeekLock), but has been either explicitly abandoned or the lock has expired, the message BrokeredMessage.DeliveryCount is incremented. DeliveryCount 超过 MaxDeliveryCount 时,该消息将移到 DLQ,并指定 MaxDeliveryCountExceeded 原因代码。When DeliveryCount exceeds MaxDeliveryCount, the message is moved to the DLQ, specifying the MaxDeliveryCountExceeded reason code.

无法禁止此行为,但可以将 MaxDeliveryCount 设置为一个大数字。This behavior can't be disabled, but you can set MaxDeliveryCount to a large number.

超过 TimeToLiveExceeding TimeToLive

QueueDescription.EnableDeadLetteringOnMessageExpirationSubscriptionDescription.EnableDeadLetteringOnMessageExpiration 属性设置为 true(默认值是 false)时,所有到期的消息将移到 DLQ,并指定 TTLExpiredException 原因代码。When the QueueDescription.EnableDeadLetteringOnMessageExpiration or SubscriptionDescription.EnableDeadLetteringOnMessageExpiration property is set to true (the default is false), all expiring messages are moved to the DLQ, specifying the TTLExpiredException reason code.

如果至少有一个活动的接收器正在从主队列或订阅中拉取内容,则会清除过期的消息并将其移到 DLQ;该行为是设计使然。Expired messages are only purged and moved to the DLQ when there is at least one active receiver pulling from the main queue or subscription; that behavior is by design.

处理订阅规则时出错Errors while processing subscription rules

当为订阅启用了 SubscriptionDescription.EnableDeadLetteringOnFilterEvaluationExceptions 属性时,会在 DLQ 中捕获执行订阅的 SQL 筛选器规则时出现的任何错误以及有问题的消息。When the SubscriptionDescription.EnableDeadLetteringOnFilterEvaluationExceptions property is enabled for a subscription, any errors that occur while a subscription's SQL filter rule executes are captured in the DLQ along with the offending message.

应用程序级死信Application-level dead-lettering

除了系统提供的死信功能外,应用程序也可以使用 DLQ 显式拒绝无法接受的消息。In addition to the system-provided dead-lettering features, applications can use the DLQ to explicitly reject unacceptable messages. 这可能包括由于任何类型的系统问题而导致无法正确处理的消息、存储了格式错误的有效负载的消息,或在使用某种消息级安全方案时未通过身份验证的消息。They can include messages that can't be properly processed because of any sort of system issue, messages that hold malformed payloads, or messages that fail authentication when some message-level security scheme is used.

ForwardTo 或 SendVia 方案中的死信Dead-lettering in ForwardTo or SendVia scenarios

在下列情况下,消息将发送到传输死信队列:Messages will be sent to the transfer dead-letter queue under the following conditions:

  • 消息传过链接在一起的四个以上队列或主题。A message passes through more than four queues or topics that are chained together.
  • 目标队列或主题被禁用或删除。The destination queue or topic is disabled or deleted.
  • 目标队列或主题超出最大实体大小。The destination queue or topic exceeds the maximum entity size.

若要检索这些死信消息,可以使用 FormatTransferDeadletterPath 实用方法创建接收器。To retrieve these dead-lettered messages, you can create a receiver using the FormatTransferDeadletterPath utility method.

示例Example

下面的代码片段会创建一个消息接收器。The following code snippet creates a message receiver. 在主队列的接收循环中,此代码使用 Receive(TimeSpan.Zero) 检索消息,该方法请求代理立即返回随时可用的任何消息或返回空结果。In the receive loop for the main queue, the code retrieves the message with Receive(TimeSpan.Zero), which asks the broker to instantly return any message readily available, or to return with no result. 如果此代码接收到一条消息,则会立即将其放弃,从而使 DeliveryCount 递增。If the code receives a message, it immediately abandons it, which increments the DeliveryCount. 系统将此消息移动到 DLQ 后,ReceiveAsync 返回 null,主队列为空,且循环退出。Once the system moves the message to the DLQ, the main queue is empty and the loop exits, as ReceiveAsync returns null.

var receiver = await receiverFactory.CreateMessageReceiverAsync(queueName, ReceiveMode.PeekLock);
while(true)
{
    var msg = await receiver.ReceiveAsync(TimeSpan.Zero);
    if (msg != null)
    {
        Console.WriteLine("Picked up message; DeliveryCount {0}", msg.DeliveryCount);
        await msg.AbandonAsync();
    }
    else
    {
        break;
    }
}

死信队列的路径Path to the dead-letter queue

可以使用以下语法访问死信队列:You can access the dead-letter queue by using the following syntax:

<queue path>/$deadletterqueue
<topic path>/Subscriptions/<subscription path>/$deadletterqueue

如果使用的是 .NET SDK,则可以通过使用 SubscriptionClient.FormatDeadLetterPath() 方法获取死信队列的路径。If you're using the .NET SDK, you can get the path to the dead-letter queue by using the SubscriptionClient.FormatDeadLetterPath() method. 此方法使用 /$DeadLetterQueue 接受主题名称/订阅名称和后缀。This method takes the topic name/subscription name and suffixes with /$DeadLetterQueue.

后续步骤Next steps

有关服务总线队列的详细信息,请参阅以下文章:See the following articles for more information about Service Bus queues: