Skip to content

Commit 71d3d9b

Browse files
committed
* Update dotnet tutorial seven to use GitHub references
1 parent bd0a1bd commit 71d3d9b

File tree

1 file changed

+57
-135
lines changed

1 file changed

+57
-135
lines changed

tutorials/tutorial-seven-dotnet.md

Lines changed: 57 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,8 @@ Publisher confirms are a RabbitMQ extension to the AMQP 0.9.1 protocol,
5050
so they are not enabled by default. Publisher confirms are
5151
enabled at the channel level with the `ConfirmSelect` method:
5252

53-
```csharp
54-
var channel = await connection.CreateModelAsync();
55-
channel.ConfirmSelect();
53+
```csharp reference
54+
https://github.com/rabbitmq/rabbitmq-tutorials/blob/rabbitmq-dotnet-client-7.0.0/dotnet/PublisherConfirms/PublisherConfirms.cs#L29
5655
```
5756

5857
This method must be called on every channel that you expect to use publisher
@@ -68,28 +67,29 @@ while (ThereAreMessagesToPublish())
6867
{
6968
byte[] body = ...;
7069
IBasicProperties properties = ...;
71-
channel.BasicPublish(exchange, queue, properties, body);
70+
await channel.BasicPublishAsync(exchange, queue, properties, body);
7271
// uses a 5 second timeout
73-
channel.WaitForConfirmsOrDie(TimeSpan.FromSeconds(5));
72+
await channel.WaitForConfirmsOrDieAsync(TimeSpan.FromSeconds(5));
7473
}
7574
```
7675

7776
In the previous example we publish a message as usual and wait for its
78-
confirmation with the `Channel#WaitForConfirmsOrDie(TimeSpan)` method.
77+
confirmation with the `IChannel#WaitForConfirmsOrDieAsync(TimeSpan)` method.
7978
The method returns as soon as the message has been confirmed. If the
8079
message is not confirmed within the timeout or if it is nack-ed (meaning
8180
the broker could not take care of it for some reason), the method will
8281
throw an exception. The handling of the exception usually consists
8382
in logging an error message and/or retrying to send the message.
8483

85-
Different client libraries have different ways to synchronously deal with publisher confirms,
86-
so make sure to read carefully the documentation of the client you are using.
84+
Different client libraries have different ways to synchronously deal with
85+
publisher confirms, so make sure to read carefully the documentation of the
86+
client you are using.
8787

88-
This technique is very straightforward but also has a major drawback:
89-
it **significantly slows down publishing**, as the confirmation of a message blocks the publishing
90-
of all subsequent messages. This approach is not going to deliver throughput of
91-
more than a few hundreds of published messages per second. Nevertheless, this can be
92-
good enough for some applications.
88+
This technique is very straightforward but also has a major drawback: it can
89+
**significantly slows down publishing**, as the confirmation of a message
90+
blocks the publishing of all subsequent messages. This approach is not going to
91+
deliver throughput of more than a few hundreds of published messages per
92+
second. Nevertheless, this can be good enough for some applications.
9393

9494
> #### Are Publisher Confirms Asynchronous?
9595
>
@@ -107,25 +107,8 @@ To improve upon our previous example, we can publish a batch
107107
of messages and wait for this whole batch to be confirmed.
108108
The following example uses a batch of 100:
109109

110-
```csharp
111-
var batchSize = 100;
112-
var outstandingMessageCount = 0;
113-
while (ThereAreMessagesToPublish())
114-
{
115-
byte[] body = ...;
116-
IBasicProperties properties = ...;
117-
channel.BasicPublish(exchange, queue, properties, body);
118-
outstandingMessageCount++;
119-
if (outstandingMessageCount == batchSize)
120-
{
121-
channel.WaitForConfirmsOrDie(TimeSpan.FromSeconds(5));
122-
outstandingMessageCount = 0;
123-
}
124-
}
125-
if (outstandingMessageCount > 0)
126-
{
127-
channel.WaitForConfirmsOrDie(TimeSpan.FromSeconds(5));
128-
}
110+
```csharp reference
111+
https://github.com/rabbitmq/rabbitmq-tutorials/blob/rabbitmq-dotnet-client-7.0.0/dotnet/PublisherConfirms/PublisherConfirms.cs#L67-L91
129112
```
130113

131114
Waiting for a batch of messages to be confirmed improves throughput drastically over
@@ -143,7 +126,7 @@ to register a callback on the client to be notified of these confirms:
143126

144127
```csharp
145128
var channel = await connection.CreateModelAsync();
146-
channel.ConfirmSelect();
129+
await channel.ConfirmSelectAsync();
147130
channel.BasicAcks += (sender, ea) =>
148131
{
149132
// code when message is confirmed
@@ -155,91 +138,39 @@ channel.BasicNacks += (sender, ea) =>
155138
```
156139

157140
There are 2 callbacks: one for confirmed messages and one for nack-ed messages
158-
(messages that can be considered lost by the broker). Both callbacks have a corresponding `EventArgs` parameter (`ea`) containing a:
141+
(messages that can be considered lost by the broker). Both callbacks have a
142+
corresponding `EventArgs` parameter (`ea`) containing a:
159143

160144
* delivery tag: the sequence number identifying the confirmed
161145
or nack-ed message. We will see shortly how to correlate it with the published message.
162146
* multiple: this is a boolean value. If false, only one message is confirmed/nack-ed, if
163147
true, all messages with a lower or equal sequence number are confirmed/nack-ed.
164148

165-
The sequence number can be obtained with `Channel#NextPublishSeqNo`
149+
The sequence number can be obtained with `IChannel#NextPublishSeqNo`
166150
before publishing:
167151

168152
```csharp
169153
var sequenceNumber = channel.NextPublishSeqNo;
170-
channel.BasicPublish(exchange, queue, properties, body);
154+
await channel.BasicPublishAsync(exchange, queue, properties, body);
171155
```
172156

173-
A simple way to correlate messages with sequence number consists
174-
in using a dictionary. Let's assume we want to publish strings because they are easy
175-
to turn into an array of bytes for publishing. Here is a code
176-
sample that uses a dictionary to correlate the publishing sequence number
177-
with the string body of the message:
157+
A performant way to correlate messages with sequence number consists in using a
158+
linked list. Let's assume we want to publish strings because they are easy to
159+
turn into an array of bytes for publishing.
178160

179-
```csharp
180-
var outstandingConfirms = new ConcurrentDictionary<ulong, string>();
181-
// ... code for confirm callbacks will come later
182-
var body = "...";
183-
outstandingConfirms.TryAdd(channel.NextPublishSeqNo, body);
184-
channel.BasicPublish(exchange, queue, properties, Encoding.UTF8.GetBytes(body));
185-
```
186-
187-
The publishing code now tracks outbound messages with a dictionary. We need
188-
to clean this dictionary when confirms arrive and do something like logging a warning
161+
The publishing code now tracks outbound messages with a linked list. We need to
162+
clean this list when confirms arrive and do something like logging a warning
189163
when messages are nack-ed:
190164

191-
```csharp
192-
var outstandingConfirms = new ConcurrentDictionary<ulong, string>();
193-
194-
void CleanOutstandingConfirms(ulong sequenceNumber, bool multiple)
195-
{
196-
if (multiple)
197-
{
198-
var confirmed = outstandingConfirms.Where(k => k.Key <= sequenceNumber);
199-
foreach (var entry in confirmed)
200-
{
201-
outstandingConfirms.TryRemove(entry.Key, out _);
202-
}
203-
}
204-
else
205-
{
206-
outstandingConfirms.TryRemove(sequenceNumber, out _);
207-
}
208-
}
209-
210-
channel.BasicAcks += (sender, ea) => CleanOutstandingConfirms(ea.DeliveryTag, ea.Multiple);
211-
channel.BasicNacks += (sender, ea) =>
212-
{
213-
outstandingConfirms.TryGetValue(ea.DeliveryTag, out string body);
214-
Console.WriteLine($"Message with body {body} has been nack-ed. Sequence number: {ea.DeliveryTag}, multiple: {ea.Multiple}");
215-
CleanOutstandingConfirms(ea.DeliveryTag, ea.Multiple);
216-
};
217-
218-
// ... publishing code
165+
```csharp reference
166+
https://github.com/rabbitmq/rabbitmq-tutorials/blob/rabbitmq-dotnet-client-7.0.0/dotnet/PublisherConfirms/PublisherConfirms.cs#L118-L156
219167
```
220168

221-
The previous sample contains a callback that cleans the dictionary when
222-
confirms arrive. Note this callback handles both single and multiple
223-
confirms. This callback is used when confirms arrive (`Channel#BasicAcks`). The callback for nack-ed messages
224-
retrieves the message body and issues a warning. It then re-uses the
225-
previous callback to clean the dictionary of outstanding confirms (whether
226-
messages are confirmed or nack-ed, their corresponding entries in the dictionary
227-
must be removed.)
228-
229-
> #### How to Track Outstanding Confirms?
230-
>
231-
> Our samples use a `ConcurrentDictionary` to track outstanding confirms.
232-
> This data structure is convenient for several reasons. It allows to
233-
> easily correlate a sequence number with a message (whatever the message data
234-
> is) and to easily clean the entries up to a given sequence id (to handle
235-
> multiple confirms/nacks). At last, it supports concurrent access, because
236-
> confirm callbacks are called in a thread owned by the client library, which
237-
> should be kept different from the publishing thread.
238-
>
239-
> There are other ways to track outstanding confirms than with
240-
> a sophisticated dictionary implementation, like using a simple concurrent hash table
241-
> and a variable to track the lower bound of the publishing sequence, but
242-
> they are usually more involved and do not belong to a tutorial.
169+
The previous sample contains a callback that cleans the linked list when
170+
confirms arrive. Note this callback handles both single and multiple confirms.
171+
This callback is used when confirms arrive (`IChannel#BasicAcks`). The callback
172+
for nack-ed messages issues a warning. It then re-uses the previous callback to
173+
clean the linked list of outstanding confirms.
243174

244175
To sum up, handling publisher confirms asynchronously usually requires the
245176
following steps:
@@ -278,53 +209,44 @@ to the constraints in the application and in the overall system. Typical techniq
278209

279210
## Putting It All Together
280211

281-
The [`PublisherConfirms.cs`](https://github.com/rabbitmq/rabbitmq-tutorials/blob/main/dotnet/PublisherConfirms/PublisherConfirms.cs)
212+
The [`PublisherConfirms.cs`](https://github.com/rabbitmq/rabbitmq-tutorials/blob/rabbitmq-dotnet-client-7.0.0/dotnet/PublisherConfirms/PublisherConfirms.cs)
282213
class contains code for the techniques we covered. We can compile it, execute it as-is and
283214
see how they each perform:
284215

285-
```bash
286-
dotnet run
216+
```PowerShell
217+
> dotnet run .\PublisherConfirms.csproj
218+
9/9/2024 11:07:11 AM [INFO] publishing 50,000 messages individually and handling confirms all at once
219+
9/9/2024 11:07:12 AM [INFO] published 50,000 messages individually in 796 ms
220+
9/9/2024 11:07:12 AM [INFO] publishing 50,000 messages and handling confirms in batches
221+
9/9/2024 11:07:13 AM [INFO] published 50,000 messages in batch in 1,034 ms
222+
9/9/2024 11:07:13 AM [INFO] publishing 50,000 messages and handling confirms asynchronously
223+
9/9/2024 11:07:14 AM [INFO] published 50,000 messages and handled confirm asynchronously 763 ms
287224
```
288225

289-
The output will look like the following:
290-
291-
```bash
292-
Published 50,000 messages individually in 5,549 ms
293-
Published 50,000 messages in batch in 2,331 ms
294-
Published 50,000 messages and handled confirms asynchronously in 4,054 ms
295-
```
296-
297-
The output on your computer should look similar if the client and the server sit
298-
on the same machine. Publishing messages individually performs poorly as expected,
299-
but the results for asynchronously handling are a bit disappointing compared to batch publishing.
226+
The output on your computer should look similar if the client and the server
227+
sit on the same machine.
300228

301229
Publisher confirms are very network-dependent, so we're better off
302230
trying with a remote node, which is more realistic as clients
303231
and servers are usually not on the same machine in production.
304232
`PublisherConfirms.cs` can easily be changed to use a non-local node:
305233

306234
```csharp
307-
private static IConnection CreateConnection()
235+
private static Task<IConnection> CreateConnection()
308236
{
309-
var factory = new ConnectionFactory { HostName = "remote-host", UserName = "remote-host", Password = "remote-password" };
310-
return await factory.CreateConnectionAsync();
311-
}
312-
```
313-
314-
Recompile the class, execute it again, and wait for the results:
237+
var factory = new ConnectionFactory
238+
{
239+
HostName = "remote-host",
240+
UserName = "remote-host",
241+
Password = "remote-password"
242+
};
315243

316-
```bash
317-
Published 50,000 messages individually in 231,541 ms
318-
Published 50,000 messages in batch in 7,232 ms
319-
Published 50,000 messages and handled confirms asynchronously in 6,332 ms
244+
return factory.CreateConnectionAsync();
245+
}
320246
```
321247

322-
We see publishing individually now performs terribly. But
323-
with the network between the client and the server, batch publishing and asynchronous handling
324-
now perform similarly, with a small advantage for asynchronous handling of the publisher confirms.
325-
326-
Remember that batch publishing is simple to implement, but does not make it easy to know
327-
which message(s) could not make it to the broker in case of negative publisher acknowledgment.
328-
Handling publisher confirms asynchronously is more involved to implement but provide
329-
better granularity and better control over actions to perform when published messages
330-
are nack-ed.
248+
Remember that batch publishing is simple to implement, but does not make it
249+
easy to know which message(s) could not make it to the broker in case of
250+
negative publisher acknowledgment. Handling publisher confirms asynchronously
251+
is more involved to implement but provide better granularity and better control
252+
over actions to perform when published messages are nack-ed.

0 commit comments

Comments
 (0)