Getting RabbitMq to behave as I want (dead letter and re-queue on errors) - rabbitmq

I have a simple rabbit set up that is currently doing what I want...
It publishes messages based on the type of message. Each type gets its own queue.
When messages are published they sit on the queue even if there is no consumer to consume them (sit there forever if no consumer arrives).
When a consumer is there (there is only one!) it eats the messages.
If for some reason it cannot handle a message (eg it gets a sub message before the parent has arrived) it nacks the message back onto the queue.
If it sees the same message six times it nacks the message.
This all works, but currently after six attempts it drops the message.
What I would like is for the message to pass to a 'dead letter queue' and after some time (say 5 mins) re-queue that message at the end of the particular queue it came from.
I am definitely cargo cult programing and I don't quite understand all the exchange/queue/binding/routing keys and other arcania involved... hand holding is appreciated!
public void PublishEntity<T>(T message) where T : class, ISendable
{
logger.Info($"publishing {message.UniqueId}");
var factory = new ConnectionFactory
{
HostName = appSettings.RabbitHostName,
UserName = appSettings.RabbitUsername,
Password = appSettings.RabbitPassword
};
try
{
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
Console.WriteLine($"Setting up queues for: {typeof(T).Name}");
channel.QueueDeclare($"App_{typeof(T).Name}",
true,
false,
false,
null);
var json = JsonConvert.SerializeObject(message);
var body = Encoding.UTF8.GetBytes(json);
channel.TxSelect();
var properties = channel.CreateBasicProperties();
properties.Persistent = true;
properties.Headers = new Dictionary<string, object>
{
{ "Id", Guid.NewGuid().ToString() }
};
channel.BasicPublish("",
$"App_{typeof(T).Name}",
properties,
body);
Data.MarkAsSent(message);
channel.TxCommit();
}
}
}
ISendable just make sure the message has some properties used in Data.MarkAsSent(message); to mark in a database where we have got too.
The receiver has a similar lump of code to handle each type. As I say this is working.
What do I need to do to add the dead letter queue things?
My attempt like so created the dead letter queues, but nothing ever moves to them.
public void PublishEntity<T>(T message) where T : class, ISendable
{
logger.Info($"publishing {message.UniqueId}");
var factory = new ConnectionFactory
{
HostName = appSettings.RabbitHostName,
UserName = appSettings.RabbitUsername,
Password = appSettings.RabbitPassword
};
try
{
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
Console.WriteLine($"Setting up queues for: {typeof(T).Name}");
channel.ExchangeDeclare("App.Dead.Letter", "direct", true);
var args = new Dictionary<string, object>
{
{ "x-dead-letter-exchange", "App.Dead.Letter" },
{
"x-dead-letter-routing-key", $"DLQ.App_{typeof(T).Name}"
}
};
channel.QueueDeclare($"App_{typeof(T).Name}",
true,
false,
false,
args);
channel.QueueDeclare($"DLQ.App_{typeof(T).Name}",
true,
false,
false,
null);
var json = JsonConvert.SerializeObject(message);
var body = Encoding.UTF8.GetBytes(json);
channel.TxSelect();
var properties = channel.CreateBasicProperties();
properties.Persistent = true;
properties.Headers = new Dictionary<string, object>
{
{ "Id", Guid.NewGuid().ToString() }
};
channel.BasicPublish("",
$"App_{typeof(T).Name}",
properties,
body);
Data.MarkAsSent(message);
channel.TxCommit();
}
}
}
In my reciever I have this magic
catch (Exception ex)
{
var attemptsToHandle = MarkFailedToHandleMessage(logId, ex);
if (attemptsToHandle > 5)
{
//If we have seen this message many times then don't re-que.
channel.BasicNack(ea.DeliveryTag, false, false);
return;
}
// re-que so we can re-try later.
channel.BasicNack(ea.DeliveryTag, false, true);
return;
}
Phew... lot of code. Thanks if you have made it this far....
I am asking what are the glaring issues in my code to make things fall to the dead letter queue.
and what extra do I need to add so that things in the dlq will bounce back to the main queue after some time.
Additionally this sets up a dlq for each types queue... is this required or should there be a single queue to hold the errored messages?

So I think I got this working as I intend. Hard to test all this stuff though!
public void PublishEntity<T>(T message) where T : class, ISendable
{
logger.Info($"publishing {message.UniqueId}");
var factory = new ConnectionFactory
{
HostName = appSettings.RabbitHostName,
UserName = appSettings.RabbitUsername,
Password = appSettings.RabbitPassword
};
try
{
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
Console.WriteLine($"Setting up queues for: {typeof(T).Name}");
// Declair dead letter queue for this type
channel.ExchangeDeclare("App.Dead.Letter", "direct", true);
var queueArgs = new Dictionary<string, object>
{
{ "x-dead-letter-exchange", "App" },
{
"x-dead-letter-routing-key", $"App_{typeof(T).Name}"
}
,{ "x-message-ttl", 30000 }
};
channel.QueueDeclare($"DLQ.App_{typeof(T).Name}",
true,
false,
false,
queueArgs);
channel.QueueBind($"DLQ.App_{typeof(T).Name}", "App.Dead.Letter", $"DLQ.App_{typeof(T).Name}", null);
// declair queue for this type
channel.ExchangeDeclare("App", "direct", true);
var args = new Dictionary<string, object>
{
{ "x-dead-letter-exchange", "App.Dead.Letter" },
{
"x-dead-letter-routing-key", $"DLQ.App_{typeof(T).Name}"
}
};
channel.QueueDeclare($"App_{typeof(T).Name}",
true,
false,
false,
args);
channel.QueueBind($"App_{typeof(T).Name}", "App", $"App_{typeof(T).Name}", null);
I added and exchange for my main queues to live in and actually bound the queues to the exchanges. I still don't know why I need to do this as it was working without this extra complexity.. I guess some magic was hiding it from me before?

Related

ASP.NET CORE combined with RabbitMq message queue

I am using asp.net core to create a project, I want to combine rabbitmq to realize the sending and receiving of message queue.I installed rabbitmq using docker and it runs successfully.But the queue I registered in the project is not displayed on the RabbitMq management interface.
ConnectionFactory factory = new ConnectionFactory
{
UserName = "guest",
Password = "guest",
HostName = "*******"
};
var connection = factory.CreateConnection();
var channel = connection.CreateModel();
channel.QueueDeclare("test", false, false, false, null);
Console.WriteLine("\nRabbitMQ enter exit to exit!");
string input;
do
{
input = Console.ReadLine();
var sendBytes = Encoding.UTF8.GetBytes(input);
channel.BasicPublish("", "test", null, sendBytes);
} while (input.Trim().ToLower()!="exit");
channel.Close();
connection.Close();
The above is my message production code.But there is no display on my RabbitMq dashboard.
enter image description here
You did not specify the port number to connect to and the VirtualHost to connect to, I am not sure if you are caused by this. Do you have a specific error? Or your project can run normally. I have written a demo for sending messages here, you can refer to it, it may be useful to you.
class Program
{
static void Main(string[] args)
{
try
{
var factory = new ConnectionFactory();
factory.VirtualHost = "/";
factory.HostName = "localhost";
factory.Port = 5672;
factory.UserName = "guest";//Default username guest
factory.Password = "guest";//Default password guest
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.QueueDeclare(queue: "hello",
durable: false,
exclusive: false,
autoDelete: false,
arguments: null);
string message = "Hello World!";
var body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange: "",
routingKey: "hello",
basicProperties: null,
body: body);
Console.WriteLine(" [x] Sent {0}", message);
}
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}
catch (Exception ex)
{
Console.Write(string.Format("RabbitMQ connection error :{0}\n", ex.ToString()));
}
}
}
Refer to the official document example:
https://www.rabbitmq.com/tutorials/tutorial-one-dotnet.html
If you run this code and get this error:
Check if your corresponding virtual hosts match, I set "/" here, if you are not, please add your hosts here:
Result:
console:
rabbitmq:

Code to get single message from the queue in RabbitMQ using C#

How to receive single message from the queue in RabbitMQ using C#
I am using the RabbitMQ to maintain my Queue.When I am read my queue messages its shows all massage present in the queue.
I want to retrieve single message as per First come first out criteria.
Please provide me code to get the single message from the queue by using RabbitMQ
var connection = connectionFactory.CreateConnection();
var channel = connection.CreateModel();
// accept only one unack-ed message at a time
// uint prefetchSize, ushort prefetchCount, bool global
channel.BasicQos(0, 1, false);
MessageReceiver messageReceiver = new MessageReceiver(channel);
channel.BasicConsume("viewer_Queues", false, messageReceiver);
//channel.QueueDeclare("viewer_Queues", false, false, false, null);
var consumer = new QueueingBasicConsumer(channel);
channel.BasicConsume("viewer_Queues", true, consumer);
BasicDeliverEventArgs ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
Console.WriteLine(message + " Received.");
Console.ReadLine();
I think you need something like this, from the website Tutorials in the worker Queues Section:
`using System;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;
using System.Threading;
class Worker
{
public static void Main()
{
var factory = new ConnectionFactory() { HostName = "localhost" };
using(var connection = factory.CreateConnection())
using(var channel = connection.CreateModel())
{
channel.QueueDeclare(queue: "task_queue",
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);
Console.WriteLine(" [*] Waiting for messages.");
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
Console.WriteLine(" [x] Received {0}", message);
int dots = message.Split('.').Length - 1;
Thread.Sleep(dots * 1000);
Console.WriteLine(" [x] Done");
channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
};
channel.BasicConsume(queue: "task_queue",
autoAck: false,
consumer: consumer);
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}
}
}`
the prefetch argument is important if you want to get only one message at a time you can read about it in the official website https://www.rabbitmq.com/

.NET Core 1.1 Receiving RabbitMQ Messages

Here's a simple .net core 1.1 console app. Call it with a -r parameter and it reads all the messages in the rabbitmq Queue, call it with any other parameters, and each parameter is enqueued as a message.
Here's the problem, I can enqueue the messages fine, but all attempts to read the messages result in no messages being read. Clearly I'm not consuming the queue correctly, and would appreciate some guidance.
Thanks!
using System;
using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
namespace RabbitMqDemo
{
class Program
{
static void Main(string[] args)
{
var client = new MessagingClient();
if (args.Length == 1 && args[0].ToLower() == "-r")
{
Console.WriteLine("Reading Messages from Queue.");
var messages = client.ReceiveMessages();
Console.WriteLine($"Read {messages.Length} message(s) from queue.");
foreach(var msg in messages)
Console.WriteLine(msg);
}
else
{
foreach (var msg in args)
{
client.SendMessage(msg);
}
Console.WriteLine($"Enqueued {args.Length} Message.");
}
}
}
internal class MessagingClient
{
private readonly ConnectionFactory connectionFactory;
private string ExchangeName => "defaultExchange";
private string RoutingKey => "";
private string QueueName => "Demo";
private string HostName => "localhost";
public MessagingClient()
{
this.connectionFactory = new ConnectionFactory {HostName = this.HostName};
}
public void SendMessage(string message)
{
using (var connection = this.connectionFactory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
this.QueueDeclare(channel, this.QueueName);
var properties = this.SetMessageProperties(channel, message);
string messageJson = JsonConvert.SerializeObject(message);
var body = Encoding.UTF8.GetBytes(messageJson);
channel.BasicPublish(exchange: this.ExchangeName, routingKey: this.RoutingKey, basicProperties: properties, body: body);
}
}
}
public string[] ReceiveMessages()
{
var messages = new List<string>();
using (var connection = this.connectionFactory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
this.QueueDeclare(channel, this.QueueName);
channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
string bodystring = Encoding.UTF8.GetString(ea.Body);
messages.Add(bodystring);
// ReSharper disable once AccessToDisposedClosure
channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
};
channel.BasicConsume(queue: this.QueueName, autoAck: false, consumer: consumer);
}
}
return messages.ToArray();
}
private void QueueDeclare(IModel channel, string queueName)
{
channel.ExchangeDeclare(ExchangeName, type: ExchangeType.Direct,
durable: true,
autoDelete: false,
arguments: null);
var queueDeclared = channel.QueueDeclare(queue: queueName,
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
channel.QueueBind(queueName, ExchangeName, RoutingKey);
}
private IBasicProperties SetMessageProperties(IModel channel, object message)
{
var properties = channel.CreateBasicProperties();
properties.ContentType = "application/json";
properties.Persistent = true;
return properties;
}
}
}
First, use the management UI to ensure that your exchange and queue are set up correctly and that messages have been published to it.
Second, ReceiveMessages() and thus your reader probably returns immediately with an empty array before the event has a chance to fire. You have no code to wait while the consumer receives messages from RabbitMQ. Notice in the tutorial how Console.ReadLine() is used. In your example, you can use a synchronization object (ManualResetEvent) to prevent ReceiveMessages() from returning until a certain message count is read.

RabbitMQ .NET Client and connection timeouts

I'm trying to test the AutomaticRecoveryEnabled property of the RabbitMQ ConnectionFactory. I'm connecting to a RabbitMQ instance on a local VM and on the client I'm publishing messages in a loop. The problem is if I intentionally break the connection, the client just waits forever and doesn't time out. How do I set the time out value? RequestedConnectionTimeout doesn't appear to have any effect.
I'm using the RabbitMQ client 3.5.4
Rudimentary publish loop:
// Client is a wrapper around the RabbitMQ client
for (var i = 0; i < 1000; ++i)
{
// Publish sequentially numbered messages
client.Publish("routingkey", GetContent(i)));
Thread.Sleep(100);
}
The Publish method inside the wrapper:
public bool Publish(string routingKey, byte[] body)
{
try
{
using (var channel = _connection.CreateModel())
{
var basicProps = new BasicProperties
{
Persistent = true,
};
channel.ExchangeDeclare(_exchange, _exchangeType);
channel.BasicPublish(_exchange, routingKey, basicProps, body);
return true;
}
}
catch (Exception e)
{
_logger.Log(e);
}
return false;
}
The connection and connection factory:
_connectionFactory = new ConnectionFactory
{
UserName = _userName,
Password = _password,
HostName = _hostName,
Port = _port,
Protocol = Protocols.DefaultProtocol,
VirtualHost = _virtualHost,
// Doesn't seem to have any effect on broken connections
RequestedConnectionTimeout = 2000,
// The behaviour appears to be the same with or without these included
// AutomaticRecoveryEnabled = true,
// NetworkRecoveryInterval = TimeSpan.FromSeconds(10),
};
_connection = _connectionFactory.CreateConnection();
It appears this is a bug in version 3.5.4. Version 3.6.3 does not wait indefinitely.

Need help in creating RabbitMQ consumer as windows service?

This code works fine with console application but it doesnt work with Windows service application.
//code to receive message from queue
public void ReceiveMessage()
{
const string EXCHANGE_NAME = "EXCHANGE3";
ConnectionFactory factory = new ConnectionFactory();
//Rabbit MQ connection
using (IConnection connection = factory.CreateConnection())
{
using (IModel channel = connection.CreateModel())
{
channel.QueueDeclare("task_queue", true, false, false, null);
channel.BasicQos(0, 1, false);
var consumer = new QueueingBasicConsumer(channel);
channel.BasicConsume("task_queue", false, consumer);
//Waiting in loop to get message
while (true)
{
var ea =
(BasicDeliverEventArgs)consumer.Queue.Dequeue();
Object message = Desserialize(ea.Body);
//Acknowledge one or more delivered message
channel.BasicAck(ea.DeliveryTag, false);
}
}
}
}