Skip to content

Commit 33a5660

Browse files
committed
More work getting done
1 parent 1868269 commit 33a5660

File tree

1 file changed

+138
-34
lines changed

1 file changed

+138
-34
lines changed

articles/service-bus-messaging/service-bus-performance-improvements.md

Lines changed: 138 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ ms.service: service-bus-messaging
1111
ms.topic: article
1212
ms.date: 03/11/2020
1313
ms.author: aschhab
14-
1514
---
1615

1716
# Best Practices for performance improvements using Service Bus Messaging
@@ -32,53 +31,149 @@ Service Bus enables clients to send and receive messages via one of three protoc
3231

3332
AMQP and SBMP are more efficient, because they maintain the connection to Service Bus as long as the messaging factory exists. It also implements batching and prefetching. Unless explicitly mentioned, all content in this article assumes the use of AMQP or SBMP.
3433

34+
## Choosing the correct Service Bus .NET SDK
35+
36+
There are two supported Azure Service Bus .NET SDKs. Their APIs are very similar, and it can be confusing which one to choose. Refer to the following table to help guide your decision. We suggest the Microsoft.Azure.ServiceBus SDK as it is more modern, performant, and is cross-platform compatible.
37+
38+
| NuGet Package | Primary Namespace | Platform(s) |
39+
|---------------|-----------|-------------|
40+
| <a href="https://www.nuget.org/packages/Microsoft.Azure.ServiceBus" target="_blank">Microsoft.Azure.ServiceBus <span class="docon docon-navigate-external x-hidden-focus"></span></a> | `Microsoft.Azure.ServiceBus` | .NET Core 2.0<br>.NET Framework 4.6.1<br>Mono 5.4<br>Xamarin.iOS 10.14<br>Xamarin.Mac 3.8<br>Xamarin.Android 8.0<br>Universal Windows Platform 10.0.16299 |
41+
| <a href="https://www.nuget.org/packages/WindowsAzure.ServiceBus" target="_blank">WindowsAzure.ServiceBus <span class="docon docon-navigate-external x-hidden-focus"></span></a> | `Microsoft.ServiceBus.Messaging` | .NET Framework 4.6.1 |
42+
3543
## Reusing factories and clients
3644

37-
Service Bus client objects, such as [QueueClient][QueueClient] or [MessageSender][MessageSender], are created through a [MessagingFactory][MessagingFactory] object, which also provides internal management of connections. It is recommended that you do not close messaging factories or queue, topic, and subscription clients after you send a message, and then re-create them when you send the next message. Closing a messaging factory deletes the connection to the Service Bus service, and a new connection is established when recreating the factory. Establishing a connection is an expensive operation that you can avoid by reusing the same factory and client objects for multiple operations. You can safely use these client objects for concurrent asynchronous operations and from multiple threads.
45+
# [Microsoft.Azure.ServiceBus SDK](#tab/net-standard-sdk)
46+
47+
Service Bus client objects, such as implementations of [`IQueueClient`][QueueClient] or [`IMessageSender`][MessageSender], should be registered for dependency injection as singletons (or instantiated once and shared). It is recommended that you do not close messaging factories or queue, topic, and subscription clients after you send a message, and then re-create them when you send the next message. Closing a messaging factory deletes the connection to the Service Bus service, and a new connection is established when recreating the factory. Establishing a connection is an expensive operation that you can avoid by reusing the same factory and client objects for multiple operations. You can safely use these client objects for concurrent asynchronous operations and from multiple threads.
48+
49+
# [WindowsAzure.ServiceBus SDK](#tab/net-framework-sdk)
50+
51+
Service Bus client objects, such as `QueueClient` or `MessageSender`, are created through a [MessagingFactory][MessagingFactory] object, which also provides internal management of connections. It is recommended that you do not close messaging factories or queue, topic, and subscription clients after you send a message, and then re-create them when you send the next message. Closing a messaging factory deletes the connection to the Service Bus service, and a new connection is established when recreating the factory. Establishing a connection is an expensive operation that you can avoid by reusing the same factory and client objects for multiple operations. You can safely use these client objects for concurrent asynchronous operations and from multiple threads.
52+
53+
---
3854

3955
## Concurrent operations
4056

41-
Performing an operation (send, receive, delete, etc.) takes some time. This time includes the processing of the operation by the Service Bus service in addition to the latency of the request and the reply. To increase the number of operations per time, operations must execute concurrently.
57+
Performing an operation (send, receive, delete, etc.) takes some time. This time includes the processing of the operation by the Service Bus service in addition to the latency of the request and the response. To increase the number of operations per time, operations must execute concurrently.
4258

4359
The client schedules concurrent operations by performing asynchronous operations. The next request is started before the previous request is completed. The following code snippet is an example of an asynchronous send operation:
44-
45-
```csharp
46-
Message m1 = new BrokeredMessage(body);
47-
Message m2 = new BrokeredMessage(body);
48-
49-
Task send1 = queueClient.SendAsync(m1).ContinueWith((t) =>
60+
61+
# [Microsoft.Azure.ServiceBus SDK](#tab/net-standard-sdk)
62+
63+
```csharp
64+
var messageOne = new BrokeredMessage(body);
65+
var messageTwo = new BrokeredMessage(body);
66+
67+
var sendFirstMessageTask =
68+
queueClient.SendAsync(messageOne).ContinueWith(_ =>
69+
{
70+
Console.WriteLine("Sent message #1");
71+
});
72+
var sendSecondMessageTask =
73+
queueClient.SendAsync(messageTwo).ContinueWith(_ =>
74+
{
75+
Console.WriteLine("Sent message #2");
76+
});
77+
78+
await Task.WhenAll(sendFirstMessageTask, sendSecondMessageTask);
79+
Console.WriteLine("All messages sent");
80+
```
81+
82+
# [WindowsAzure.ServiceBus SDK](#tab/net-framework-sdk)
83+
84+
```csharp
85+
var messageOne = new Message(body /* byte[] */);
86+
var messageTwo = new Message(body /* byte[] */);
87+
88+
var sendFirstMessageTask =
89+
queueClient.SendAsync(messageOne).ContinueWith(_ =>
90+
{
91+
Console.WriteLine("Sent message #1");
92+
});
93+
var sendSecondMessageTask =
94+
queueClient.SendAsync(messageTwo).ContinueWith(_ =>
95+
{
96+
Console.WriteLine("Sent message #2");
97+
});
98+
99+
await Task.WhenAll(sendFirstMessageTask, sendSecondMessageTask);
100+
Console.WriteLine("All messages sent");
101+
```
102+
103+
---
104+
105+
The following code is an example of an asynchronous receive operation.
106+
107+
# [Microsoft.Azure.ServiceBus SDK](#tab/net-standard-sdk)
108+
109+
See the GitHub repository for full <a href="https://github.com/Azure/azure-service-bus/blob/master/samples/DotNet/Microsoft.Azure.ServiceBus/SendersReceiversWithQueues" target="_blank">source code examples <span class="docon docon-navigate-external x-hidden-focus"></span></a>:
110+
111+
```csharp
112+
var receiver = new MessageReceiver(connectionString, queueName, ReceiveMode.PeekLock);
113+
114+
static Task LogErrorAsync(Exception exception)
115+
{
116+
Console.WriteLine(exception);
117+
return Task.CompletedTask;
118+
};
119+
120+
receiver.RegisterMessageHandler(
121+
async (message, cancellationToken) =>
50122
{
51-
Console.WriteLine("Sent message #1");
123+
Console.WriteLine("Handle message");
124+
await receiver.CompleteAsync(message.SystemProperties.LockToken);
125+
},
126+
new MessageHandlerOptions(e => LogErrorAsync(e.Exception))
127+
{
128+
AutoComplete = false,
129+
MaxConcurrentCalls = 1
52130
});
53-
Task send2 = queueClient.SendAsync(m2).ContinueWith((t) =>
131+
```
132+
133+
The `MessageReceiver` object is instantiated with the connection string, queue name, and a peek-look receive mode. Next, the `receiver` instance is used to register the message handler.
134+
135+
# [WindowsAzure.ServiceBus SDK](#tab/net-framework-sdk)
136+
137+
```csharp
138+
var factory = MessagingFactory.CreateFromConnectionString(connectionString);
139+
var receiver = await factory.CreateMessageReceiverAsync(queueName, ReceiveMode.PeekLock);
140+
141+
// Register the handler to receive messages asynchronously
142+
receiver.OnMessageAsync(
143+
async message =>
144+
{
145+
Console.WriteLine("Handle message");
146+
await message.CompleteAsync();
147+
},
148+
new OnMessageOptions
54149
{
55-
Console.WriteLine("Sent message #2");
150+
AutoComplete = false,
151+
MaxConcurrentCalls = 1
56152
});
57-
Task.WaitAll(send1, send2);
58-
Console.WriteLine("All messages sent");
59-
```
60-
61-
The following code is an example of an asynchronous receive operation. See the full program [here](https://github.com/Azure/azure-service-bus/blob/master/samples/DotNet/Microsoft.Azure.ServiceBus/SendersReceiversWithQueues):
62-
63-
```csharp
64-
var receiver = new MessageReceiver(connectionString, queueName, ReceiveMode.PeekLock);
65-
var doneReceiving = new TaskCompletionSource<bool>();
66-
67-
receiver.RegisterMessageHandler(...);
68-
```
153+
```
154+
155+
The `MessagingFactory` created a `factory` instance from the connection string. With the `factory` instance, a `MessageReceiver` instance is created. Next, the `receiver` instance is used to register the on-message handler.
156+
157+
---
69158

70159
## Receive mode
71160

72-
When creating a queue or subscription client, you can specify a receive mode: *Peek-lock* or *Receive and Delete*. The default receive mode is [PeekLock][PeekLock]. When operating in this mode, the client sends a request to receive a message from Service Bus. After the client has received the message, it sends a request to complete the message.
161+
When creating a queue or subscription client, you can specify a receive mode: *Peek-lock* or *Receive and Delete*. The default receive mode is `PeekLock`. When operating in the default mode, the client sends a request to receive a message from Service Bus. After the client has received the message, it sends a request to complete the message.
73162

74-
When setting the receive mode to [ReceiveAndDelete][ReceiveAndDelete], both steps are combined in a single request. These steps reduce the overall number of operations, and can improve the overall message throughput. This performance gain comes at the risk of losing messages.
163+
When setting the receive mode to `ReceiveAndDelete`, both steps are combined in a single request. These steps reduce the overall number of operations, and can improve the overall message throughput. This performance gain comes at the risk of losing messages.
75164

76165
Service Bus does not support transactions for receive-and-delete operations. In addition, peek-lock semantics are required for any scenarios in which the client wants to defer or [dead-letter](service-bus-dead-letter-queues.md) a message.
77166

78167
## Client-side batching
79168

80169
Client-side batching enables a queue or topic client to delay the sending of a message for a certain period of time. If the client sends additional messages during this time period, it transmits the messages in a single batch. Client-side batching also causes a queue or subscription client to batch multiple **Complete** requests into a single request. Batching is only available for asynchronous **Send** and **Complete** operations. Synchronous operations are immediately sent to the Service Bus service. Batching does not occur for peek or receive operations, nor does batching occur across clients.
81170

171+
# [Microsoft.Azure.ServiceBus SDK](#tab/net-standard-sdk)
172+
173+
Batching functionality for the .NET Standard SDK, does not yet exist.
174+
175+
# [WindowsAzure.ServiceBus SDK](#tab/net-framework-sdk)
176+
82177
By default, a client uses a batch interval of 20 ms. You can change the batch interval by setting the [BatchFlushInterval][BatchFlushInterval] property before creating the messaging factory. This setting affects all clients that are created by this factory.
83178

84179
To disable batching, set the [BatchFlushInterval][BatchFlushInterval] property to **TimeSpan.Zero**. For example:
@@ -101,6 +196,8 @@ Batching does not affect the number of billable messaging operations, and is ava
101196
> ```
102197
> Here the combined size of the messages must be less than the maximum size supported by the pricing tier.
103198
199+
---
200+
104201
## Batching store access
105202
106203
To increase the throughput of a queue, topic, or subscription, Service Bus batches multiple messages when it writes to its internal store. If enabled on a queue or topic, writing messages into the store will be batched. If enabled on a queue or subscription, deleting messages from the store will be batched. If batched store access is enabled for an entity, Service Bus delays a store write operation regarding that entity by up to 20 ms.
@@ -113,15 +210,17 @@ Additional store operations that occur during this interval are added to the bat
113210
When creating a new queue, topic or subscription, batched store access is enabled by default. To disable batched store access, set the [EnableBatchedOperations][EnableBatchedOperations] property to **false** before creating the entity. For example:
114211
115212
```csharp
116-
QueueDescription qd = new QueueDescription();
117-
qd.EnableBatchedOperations = false;
118-
Queue q = namespaceManager.CreateQueue(qd);
213+
var queueDescription = new QueueDescription
214+
{
215+
EnableBatchedOperations = false
216+
};
217+
var queue = namespaceManager.CreateQueue(qd);
119218
```
120219
121220
Batched store access does not affect the number of billable messaging operations, and is a property of a queue, topic, or subscription. It is independent of the receive mode and the protocol that is used between a client and the Service Bus service.
122221

123222
## Prefetching
124-
223+
''
125224
[Prefetching](service-bus-prefetch.md) enables the queue or subscription client to load additional messages from the service when it performs a receive operation. The client stores these messages in a local cache. The size of the cache is determined by the [QueueClient.PrefetchCount][QueueClient.PrefetchCount] or [SubscriptionClient.PrefetchCount][SubscriptionClient.PrefetchCount] properties. Each client that enables prefetching maintains its own cache. A cache is not shared across clients. If the client initiates a receive operation and its cache is empty, the service transmits a batch of messages. The size of the batch equals the size of the cache or 256 KB, whichever is smaller. If the client initiates a receive operation and the cache contains a message, the message is taken from the cache.
126225

127226
When a message is prefetched, the service locks the prefetched message. With the lock, the prefetched message cannot be received by a different receiver. If the receiver cannot complete the message before the lock expires, the message becomes available to other receivers. The prefetched copy of the message remains in the cache. The receiver that consumes the expired cached copy will receive an exception when it tries to complete that message. By default, the message lock expires after 60 seconds. This value can be extended to 5 minutes. To prevent the consumption of expired messages, the cache size should always be smaller than the number of messages that can be consumed by a client within the lock time-out interval.
@@ -240,16 +339,21 @@ To maximize throughput, try the following steps:
240339
* Leave batched store access enabled. This access increases the overall rate at which messages can be written into the topic.
241340
* Set the prefetch count to 20 times the expected receive rate in seconds. This count reduces the number of Service Bus client protocol transmissions.
242341

342+
<!-- .NET Standard SDK, Microsoft.Azure.ServiceBus -->
243343
[QueueClient]: /dotnet/api/microsoft.azure.servicebus.queueclient
244344
[MessageSender]: /dotnet/api/microsoft.azure.servicebus.core.messagesender
245-
[MessagingFactory]: /dotnet/api/microsoft.servicebus.messaging.messagingfactory
246345
[PeekLock]: /dotnet/api/microsoft.azure.servicebus.receivemode
247346
[ReceiveAndDelete]: /dotnet/api/microsoft.azure.servicebus.receivemode
248-
[BatchFlushInterval]: /dotnet/api/microsoft.servicebus.messaging.messagesender.batchflushinterval
249-
[EnableBatchedOperations]: /dotnet/api/microsoft.servicebus.messaging.queuedescription.enablebatchedoperations
250347
[QueueClient.PrefetchCount]: /dotnet/api/microsoft.azure.servicebus.queueclient.prefetchcount
251348
[SubscriptionClient.PrefetchCount]: /dotnet/api/microsoft.azure.servicebus.subscriptionclient.prefetchcount
349+
350+
<!-- .NET Framework SDK, Microsoft.Azure.ServiceBus -->
351+
[MessagingFactory]: /dotnet/api/microsoft.servicebus.messaging.messagingfactory
352+
[BatchFlushInterval]: /dotnet/api/microsoft.servicebus.messaging.messagesender.batchflushinterval
353+
[EnableBatchedOperations]: /dotnet/api/microsoft.servicebus.messaging.queuedescription.enablebatchedoperations
252354
[ForcePersistence]: /dotnet/api/microsoft.servicebus.messaging.brokeredmessage.forcepersistence
253355
[EnablePartitioning]: /dotnet/api/microsoft.servicebus.messaging.queuedescription.enablepartitioning
254-
[Partitioned messaging entities]: service-bus-partitioning.md
255356
[TopicDescription.EnableFilteringMessagesBeforePublishing]: /dotnet/api/microsoft.servicebus.messaging.topicdescription.enablefilteringmessagesbeforepublishing
357+
358+
<!-- Local links -->
359+
[Partitioned messaging entities]: service-bus-partitioning.md

0 commit comments

Comments
 (0)