导出 (0) 打印
全部展开
此主题尚未评级 - 评价此主题

使用 Service Bus 中转消息传递改善性能的最佳实践

更新时间: 2014年4月

注:本页面内容可能不完全适用中国大陆地区运营的 Windows Azure服务。如要了解不同地区 Windows Azure 服务的差异, 请参考本网站.

本主题介绍如何使用 Windows Azure Service Bus 优化交换中转消息时的性能。本主题前半部分介绍了各种不同的有助于提高性能的机制。后半部分则指导用户如何针对给定方案以能够提供最佳性能的方式使用 Service Bus。

在本主题中,术语“客户端”是指任何可访问 Service Bus 的实体。客户端可以充当发送方或接收方的角色。术语“发送方”用于将消息发送到 Service Bus 队列或主题的 Service Bus 队列或主题客户端。术语“接收方”是指从 Service Bus 队列或订阅接收消息的 Service Bus 队列或订阅客户端。

机制

本部分介绍 Service Bus 用来帮助提高性能的各种概念。

协议

Service Bus 允许客户端通过两个协议发送和接收消息:Service Bus 客户端协议和 HTTP。Service Bus 客户端协议效率更高,因为只要存在消息工厂,它就会保持与 Service Bus 服务的连接。它还实现了批处理和预提取。Service Bus 客户端协议可用于使用 .NET 托管 API 的 .NET 应用程序。

除非明确提到,否则本主题中的所有内容都假定使用 Service Bus 客户端协议。

重复使用工厂和客户端

Service Bus 客户端对象,如 QueueClientMessageSender,是通过 MessagingFactory 对象创建的,该对象还提供连接的内部管理。你不应在发送消息后关闭消息工厂或队列、主题和订阅客户端,然后在发送下一条消息时重新创建它们。关闭消息工厂时将删除与 Service Bus 服务的连接,而在重新创建工厂时,将建立新的连接。建立连接是一种开销很大的操作,可通过对多个操作重复使用同一工厂和客户端对象来避免。

并发操作

执行操作(发送、接收、删除等)需要一定的时间。这些时间包括 Service Bus 服务操作的处理时间,以及请求和答复的延迟。要增加单位时间内的操作数,操作必须同时执行。有多种不同方法可实现此目标:

  • 异步操作:客户端通过执行异步操作,以管道方式传输操作。在前一个请求完成之前就开始下一个请求。以下是异步发送操作的示例:

    BrokeredMessage m1 = new BrokeredMessage(body);
    BrokeredMessage m2 = new BrokeredMessage(body);
    queueClient.BeginSend(m1, processEndSend, queueClient); // Send message 1.
    queueClient.BeginSend(m2, processEndSend, queueClient); // Send message 2.
    
    void processEndSend(IAsyncResult result)
    {
        QueueClient qc = result.AsyncState as QueueClient;
        qc.EndSend(result);
        Console.WriteLine("Message sent");
    }
    
    以下是异步接收操作的示例:

    queueClient.BeginReceive(processEndReceive, queueClient); // Receive message 1.
    queueClient.BeginReceive(processEndReceive, queueClient); // Receive message 2.
    
    void processEndReceive(IAsyncResult result) 
    {
        QueueClient qc = result.AsyncState as QueueClient;
        BrokeredMessage m = qc.EndReceive(result);
        m.BeginComplete(processEndComplete, m);
        Console.WriteLine("Received message " + m.Label);
    }
    
    void processEndComplete(IAsyncResult result)
    {
        BrokeredMessage m = result.AsyncState as BrokeredMessage;
        m.EndComplete(result);
        Console.WriteLine("Completed message " + m.Label);
    }
    
  • 多个工厂:由同一工厂创建的所有客户端(发送方和接收方)共享一个 TCP 连接。最大消息吞吐量受可以通过此 TCP 连接执行的操作数限制。单个工厂可以获得的吞吐量因 TCP 往返时间和消息大小而大不相同。要获取更高的吞吐率,应使用多个消息工厂。

接收模式

创建队列或订阅客户端时,可以指定接收模式:扫视-锁定接收并删除。默认接收模式是 PeekLock。在此模式下操作时,客户端会发送从 Service Bus 接收消息的请求。客户端收到消息后,会发送完成消息的请求。

将接收模式设置为 ReceiveAndDelete 时,将在单个请求中合并这两个步骤。这减少了总操作数,并可以提高总消息吞吐量。这样提高性能有丢失消息的风险。

Service Bus 不支持“接收并删除”操作的事务。此外,对于客户端要推迟消息或使消息成为死信的所有方案,扫视-锁定语义是必需的。

客户端批处理

使用客户端批处理功能,队列或主题客户端可以将消息的发送延迟一段特定的时间。如果在这段时间内发送其他消息,客户端会按单个批处理的方式来传输这些消息。客户端批处理还会导致队列/订阅客户端将多个完成请求集成到一个请求中进行批处理。批处理只能用于异步发送完成操作。同步操作会立即发送到 Service Bus 服务。扫视或接收操作不会进行批处理,客户端之间也不会进行批处理。

如果批处理超过最大消息大小,则会从批处理中删除最后一条消息,然后客户端就会立即发送批处理。最后一条消息将成为下个批处理的第一条消息。默认情况下,客户端使用的批处理时间间隔为 20 毫秒。在创建消息工厂前,可以通过设置 BatchFlushInterval 属性,更改批处理时间间隔。此设置会影响该工厂创建的所有客户端。若要禁用批处理,请将 BatchFlushInterval 属性设置为 TimeSpan.Zero。例如:

MessagingFactorySettings mfs = new MessagingFactorySettings();
mfs.TokenProvider = tokenProvider;
mfs.NetMessagingTransportSettings.BatchFlushInterval = TimeSpan.FromSeconds(0.05);
MessagingFactory messagingFactory = MessagingFactory.Create(namespaceUri, mfs);

批处理不会影响计费的消息传递操作数,并且只能用于 Service Bus 客户端协议。HTTP 协议不支持批处理。

对存储访问进行批处理

为了增加队列/主题/订阅的吞吐量,Service Bus 服务在写入其内部存储时会对多条消息进行批处理。如果对队列或主题启用此功能,则会将消息成批写入存储。如果对队列或订阅启用此功能,则会从存储中成批删除消息。如果对某个实体启用批量存储访问,Service Bus 会延迟该实体的相关存储写操作,时间长达 20 毫秒。在此时间间隔期间发生的其他存储操作将添加到同一批处理中。批量存储访问仅影响发送完成操作;接收操作不受影响。批量存储访问是实体的属性。将对启用批量存储访问的所有实体进行批处理。

创建新队列、主题或订阅时,会默认启用批量存储访问。要禁用批量存储访问,请在创建实体之前将 EnableBatchedOperations 属性设置为 false。例如:

QueueDescription qd = new QueueDescription();
qd.EnableBatchedOperations = false;
Queue q = namespaceManager.CreateQueue(qd);

批量存储访问不会影响计费的消息传递操作数,并且是队列、主题或订阅的属性。它与接收模式以及客户端和 Service Bus 服务之间使用的协议无关。

预提取

预提取允许队列或订阅客户端在执行接收操作时,从服务加载附加消息。客户端将这些消息存储在本地缓存中。缓存的大小由 PrefetchCountPrefetchCount 属性确定。每个启用预提取的客户端均需维护自己的缓存。缓存不能在客户端之间共享。如果客户端启动接收操作并且其缓存为空,则服务将传输一批消息。批次的大小等于缓存的大小或 256 KB(具体取决于哪个值较小)。如果客户端启动接收操作并且缓存包含一条消息,则该消息将从缓存中取出。

预提取消息时,服务将锁定预提取的消息。这样一来,预提取的消息将无法由其他接收方接收。如果接收方无法在锁定到期之前完成消息,则消息将变为对其他接收方可用。预提取消息的副本会保留在缓存中。使用过期的缓存副本的接收方在尝试完成该消息时将收到异常。默认情况下,消息锁定在 60 秒后过期。此值可以延长到 5 分钟。为防止使用过期消息,缓存大小应始终小于客户端在锁定超时时间间隔内能够使用的消息数。

使用 60 秒这个默认锁定过期时间时,最适合 SubscriptionClient.PrefetchCount 的值为工厂所有接收方最高处理速率的 20 倍。例如,某个工厂创建了 3 个接收方。每个接收方每秒最多可以处理 10 条消息。预提取计数不应超过 20*3*10 = 600。默认情况下,QueueClient.PrefetchCount 设置为 0,这意味着不从服务提取任何额外的消息。

预提取消息会增加队列或订阅的总吞吐量,因为它减少了消息操作总数或往返总次数。不过,提取第一条消息时将花费更长的时间(由于消息大小增加)。接收预提取的消息时速度会更快,因为客户端已下载过这些消息。

服务器向客户端发送消息时,会检查该消息的生存时间 (TTL) 属性。客户端接收消息时,不检查消息的 TTL 属性。相反,即使客户端缓存消息时该消息的 TTL 已过,也可以接收该消息。

预提取不会影响计费的消息传递操作数,并且只能用于 Service Bus 客户端协议。HTTP 协议不支持预提取。预提取可用于同步和异步接收操作。

使用分区队列或主题

在内部,Service Bus 使用同一节点和消息存储来处理和存储消息实体(队列或主题)的所有消息。与此相反,分区队列或主题却分布于多个消息节点和消息存储之间。分区队列和主题不仅比常规队列和主题产生更高的吞吐量,还表现出更高的可用性。要创建分区实体,将 EnablePartitioning 属性设置为 true,如下例中所示。有关分区实体的更多信息,请参阅分区消息实体

// Create partitioned queue.
QueueDescription qd = new QueueDescription(QueueName);
qd.EnablePartitioning = true;
namespaceManager.CreateQueue(qd);

使用多个队列

如果无法使用分区队列或主题,或所需负载无法由单个分区队列或主题处理,则必须使用多个消息实体。使用多个实体时,应对每一个实体创建一个专用客户端,而不是对所有实体都使用同一客户端。

方案

以下各部分介绍了典型的消息传递方案,并概述了首选 Service Bus 设置。吞吐率分为小(< 1 条消息/秒)、中等(≥ 1 条消息/秒,< 100 条消息/秒)和高(≥100 条消息/秒)。客户端数分为少 (≤5)、中等(>5,≤20)和多 (>20)。

高吞吐量队列

目标:最大限度地提高单个队列的吞吐量。发送方和接收方的数量少。

  • 使用分区队列提高性能和可用性。

  • 将消息发送到队列时,要增加总发送率,请使用多个消息工厂创建发送方。对于每个发送方,可使用异步操作或多个线程。

  • 从队列接收消息时,要增加总接收率,请使用多个消息工厂创建接收方。

  • 使用异步操作可利用客户端批处理功能。

  • 将批处理时间间隔设置为 50 毫秒以减少 Service Bus 客户端协议传输次数。如果使用多个发送方,可将批处理时间间隔增加到 100 毫秒。

  • 让批量存储访问保持启用状态。这会增加将消息写入队列的总速率。

  • 将预提取计数设置为工厂所有接收方最大处理速率的 20 倍。这将减少 Service Bus 客户端协议传输次数。

多个高吞吐量队列

目标:最大限度地提高多个队列的总吞吐量。单个队列的吞吐量是中等或高。

要获得跨多个队列的最大吞吐量,请使用概述的设置最大限度地提高单个队列的吞吐量。此外,还可以使用不同工厂创建多个客户端,向不同队列发送消息或从不同队列接收消息。

低延迟队列

目标:最大限度地减少队列或主题的端到端延迟。发送方和接收方的数量少。队列的吞吐量小或中等。

  • 使用分区队列提高可用性。

  • 禁用客户端批处理。客户端会立即发送消息。

  • 禁用批量存储访问。服务会立即将消息写入存储。

  • 如果使用单个客户端,请将预提取计数设置为接收方处理速率的 20 倍。如果多条消息同时到达队列,Service Bus 客户端协议会同时传输它们。当客户端接收下一条消息时,该消息已存在于本地缓存中。缓存应小。

  • 如果使用多个客户端,请将预提取计数设置为 0。这样一来,第二个客户端就可以在第一个客户端仍在处理第一条消息时接收第二个消息。

包含大量发送方的队列

目标:最大限度地提高包含大量发送方的队列或主题的吞吐量。每个发送方均以中等速率发送消息。接收方的数量少。

Service Bus 允许最多有 100 个到实体的并发连接。对队列来说,该数字是在发送方和接收方之间共享的。如果发送方需要全部 100 个连接,则应将队列替换为主题和单个订阅。主题最多可接受来自发送方的 100 个并发连接,而订阅则可接受来自接收方的另外 100 个并发连接。如果需要的并发发送方多于 100 个,则这些发送方应通过 HTTP 向 Service Bus 协议发送消息。

要最大限度地提高吞吐量,请执行以下操作:

  • 使用分区队列提高性能和可用性。

  • 如果每个发送方驻留在不同进程中,则每个进程只使用单个工厂。

  • 使用异步操作可利用客户端批处理功能。

  • 使用默认的 20 毫秒的批处理时间间隔以减少 Service Bus 客户端协议传输次数。

  • 让批量存储访问保持启用状态。这会增加将消息写入队列或主题的总速率。

  • 将预提取计数设置为工厂所有接收方最大处理速率的 20 倍。这将减少 Service Bus 客户端协议传输次数。

包含大量接收方的队列

目标:最大限度地提高包含大量接收方的队列或订阅的接收速率。每个接收方均以中等速率接收消息。发送方的数量少。

Service Bus 允许最多有 100 个到实体的并发连接。如果队列需要的接收方超过 100 个,则应将队列替换为主题和多个订阅。每个订阅最多可支持 100 个并发连接。此外,也可由接收方通过 HTTP 协议访问队列。

要最大限度地提高吞吐量,请执行以下操作:

  • 使用分区队列提高性能和可用性。

  • 如果每个接收方驻留在不同进程中,则每个进程只使用单个工厂。

  • 接收方可以使用同步或异步操作。如果单个接收方只有中等接收速率,则“完成”请求的客户端批处理不会影响接收方的吞吐量。

  • 让批量存储访问保持启用状态。这将减少实体的总负载。这还将降低将消息写入队列或主题的总速率。

  • 将预提取计数设置为较小的值(例如,PrefetchCount = 10)。这可以防止接收方在其他接收方有大量缓存的消息时进入空闲状态。

具有少量订阅的主题

目标:最大限度地提高具有少量订阅的主题的吞吐量。由多个订阅接收消息,这意味着所有订阅的组合接收速率比发送速率要大。发送方的数量少。每个订阅的接收方的数量少。

要最大限度地提高吞吐量,请执行以下操作:

  • 使用分区主题提高性能和可用性。

  • 将消息发送到主题时,要增加总发送率,请使用多个消息工厂创建发送方。对于每个发送方,可使用异步操作或多个线程。

  • 从订阅接收消息时,要增加总接收率,请使用多个消息工厂创建接收方。对于每个接收方,可使用异步操作或多个线程。

  • 使用异步操作可利用客户端批处理功能。

  • 使用默认的 20 毫秒的批处理时间间隔以减少 Service Bus 客户端协议传输次数。

  • 让批量存储访问保持启用状态。这会增加将消息写入主题的总速率。

  • 将预提取计数设置为工厂所有接收方最大处理速率的 20 倍。这将减少 Service Bus 客户端协议传输次数。

具有大量订阅的主题

目标:最大限度地提高具有大量订阅的主题的吞吐量。由多个订阅接收消息,这意味着所有订阅的组合接收速率比发送速率要大得多。发送方的数量少。每个订阅的接收方的数量少。

如果所有消息都路由到所有订阅,具有大量订阅的主题通常显示较低的总吞吐量。这是因为,每条消息都会被多次接收,且包含在主题及其所有订阅中的所有消息都存储在同一存储中。这里假设每个订阅的发送方数目和接收方数目都很少。Service Bus 支持每个主题最多 2,000 个订阅。

要最大限度地提高吞吐量,请执行以下操作:

  • 使用分区主题提高性能和可用性。

  • 使用异步操作可利用客户端批处理功能。

  • 使用默认的 20 毫秒的批处理时间间隔以减少 Service Bus 客户端协议传输次数。

  • 让批量存储访问保持启用状态。这会增加将消息写入主题的总速率。

  • 将预提取计数设置为预期接收速率(单位:秒)的 20 倍。这将减少 Service Bus 客户端协议传输次数。

本文是否对您有所帮助?
(1500 个剩余字符)
感谢您的反馈

社区附加资源

添加
Microsoft 正在进行一项网上调查,以了解您对 MSDN 网站的意见。 如果您选择参加,我们将会在您离开 MSDN 网站时向您显示该网上调查。

是否要参加?
显示:
© 2014 Microsoft. 版权所有。