导出 (0) 打印
全部展开

处理暂时性通信错误

更新时间: 2014年9月

为了提高使用 Microsoft Azure Service Bus .NET 托管的中转消息传送 API 的解决方案的可靠性,建议你采用一致的方法来处理暂时性故障和间歇性错误,在解决方案与 Service Bus 提供的多租户基于云的排队和发布/订阅消息传送服务基础结构通信时,可能出现这些故障和错误。

为了提高使用 Service Bus .NET 托管的中转消息传送 API 的解决方案的可靠性,建议你采用一致的方法来处理暂时性故障和间歇性错误,在解决方案与 Service Bus 提供的多租户基于云的排队和发布/订阅消息传送服务基础结构通信时,可能出现这些故障和错误。

考虑检测暂时性状况的特定方法时,你可能要重用现有技术解决方案(如暂时性故障处理框架)或构建你自己的解决方案。在这两种情况下,你都要确保在从相应故障恢复前,仅将一部分通信异常视为暂时性状况。

此处所示的表列出了可通过实现重试逻辑来解决的那些异常:

 

异常类型

建议

ServerBusyException

此异常可能是由 Service Bus 消息传送服务基础结构中的间歇性故障导致的,此时基础结构因为某一时刻的负载过大而无法处理请求。客户端可尝试在一段时间后重试。最好采用退让的延迟,以防止给服务器增加不必要的负担。

MessagingCommunicationException

此异常指示可以自身显示的通信错误,此时消息传送客户端无法成功建立与 Service Bus 基础结构的连接。在多数情况下,如果网络连接正常,此错误可视为暂时性错误。客户端可尝试重试导致此类异常的操作。还建议你验证域名解析服务 (DNS) 是否正常,因为此错误可能指示无法解析目标主机名。

TimeoutException

此异常指示 Service Bus 消息传送服务基础结构在指定的时间内(由 OperationTimeout 设置控制)未响应请求的操作。请求的操作可能已完成,但是因为网络或其他基础结构延迟,响应信息可能无法及时送达客户端。必须谨慎处理此类异常。如果消息已递送到队列但是响应超时,重发原始消息可能导致重复。

有关 Service Bus 消息传送 API 可能报告的不同异常类型的更详细信息,请参见消息传送异常主题。

note备注
处理暂时性通信错误时,请注意暂时性异常可能被其他类型的外部异常遮盖。例如,超时错误可能以通信错误形式返回给调用方,而隐藏了作为内部异常的原始超时错误。因此建议你以递归方式检查指定异常对象的所有内部异常,以可靠检测暂时性通信错误。暂时性故障处理框架中的 ServiceBusTransientErrorDetectionStrategy 类提供你应如何操作的示例。

以下代码段演示如何将消息异步发送到 Service Bus 主题,同时确保所有已知的暂时性故障将通过重试解决。请注意此代码示例维护对暂时性故障处理框架的依赖关系。

var credentials = TokenProvider.CreateSharedSecretTokenProvider(issuerName, issuerSecret);
var address = ServiceBusEnvironment.CreateServiceUri("sb", serviceNamespace, String.Empty);
var messagingFactory = MessagingFactory.Create(address, credentials);
var topicClient = messagingFactory.CreateTopicClient(topicPath);
var retryPolicy = new RetryPolicy<ServiceBusTransientErrorDetectionStrategy>(RetryPolicy.DefaultClientRetryCount);

// Create an instance of the object that represents message payload.
var payload = XDocument.Load("InventoryFile.xml");

// Declare a BrokeredMessage instance outside so that it can be reused across all 3 delegates below.
BrokeredMessage msg = null;

// Use a retry policy to execute the Send action in an asynchronous and reliable fashion.
retryPolicy.ExecuteAction
(
    (cb) =>
    {
        // A new BrokeredMessage instance must be created each time we send it. Reusing the original BrokeredMessage instance may not 
        // work as the state of its BodyStream cannot be guaranteed to be readable from the beginning.
        msg = new BrokeredMessage(payload.Root, new DataContractSerializer(typeof(XElement)));

        // Send the event asynchronously.
        topicClient.BeginSend(msg, cb, null);
    },
    (ar) =>
    {
        try
        {
            // Complete the asynchronous operation. 
            // This may throw an exception that will be handled internally by the retry policy.
            topicClient.EndSend(ar);
        }
        finally
        {
            // Ensure that any resources allocated by a BrokeredMessage instance are released.
            if (msg != null)
            {
                msg.Dispose();
                msg = null;
            }
        }
    },
    (ex) =>
    {
        // Always dispose the BrokeredMessage instance even if the send 
        // operation has completed unsuccessfully.
        if (msg != null)
        {
            msg.Dispose();
            msg = null;
        }

        // Always log exceptions.
        Trace.TraceError(ex.Message);
    }
);

下一代码示例显示如何可靠创建新的 Service Bus 主题或检索现有主题。此代码还维护对暂时性故障处理框架的依赖关系,该框架在由于间歇性连接问题或其他暂时性问题导致无法成功完成操作时自动重试相应的管理操作:

public TopicDescription GetOrCreateTopic(string issuerName, string issuerSecret, string serviceNamespace, string topicName)
{
    // Must validate all input parameters here. Use Code Contracts or build your own validation.
    var credentials = TokenProvider.CreateSharedSecretTokenProvider(issuerName, issuerSecret);
    var address = ServiceBusEnvironment.CreateServiceUri("sb", serviceNamespace, String.Empty);
    var nsManager = new NamespaceManager(address, credentials);
    var retryPolicy = new RetryPolicy<ServiceBusTransientErrorDetectionStrategy>(RetryPolicy.DefaultClientRetryCount);

    TopicDescription topic = null;
    bool createNew = false;

    try
    {
        // First, let's see if a topic with the specified name already exists.
        topic = retryPolicy.ExecuteAction<TopicDescription>(() => { return nsManager.GetTopic(topicName); });

        createNew = (topic == null);
    }
    catch (MessagingEntityNotFoundException)
    {
        // Looks like the topic does not exist. We should create a new one.
        createNew = true;
    }

    // If a topic with the specified name doesn't exist, it will be auto-created.
    if (createNew)
    {
        try
        {
            var newTopic = new TopicDescription(topicName);

            topic = retryPolicy.ExecuteAction<TopicDescription>(() => { return nsManager.CreateTopic(newTopic); });
        }
        catch (MessagingEntityAlreadyExistsException)
        {
            // A topic under the same name was already created by someone else, 
            // perhaps by another instance. Let's just use it.
            topic = retryPolicy.ExecuteAction<TopicDescription>(() => { return nsManager.GetTopic(topicName); });
        }
    }

    return topic;
}

总之,我们建议你评估发生故障的可能性并确定提高系统容错能力的可行性。实际上所有消息传送操作都可能出现暂时性故障。因此,在调用中介的消息传送 API 时,建议你采取适当措施来保证系统可从间歇性故障中恢复。

显示:
© 2014 Microsoft