I'm implementing RPC procedures RabbitMQ message bus, but in their example code for the RPC Client class ...:
public RpcClient()
{
var factory = new ConnectionFactory() { HostName = "localhost" };
connection = factory.CreateConnection();
channel = connection.CreateModel();
// declare a server-named queue
replyQueueName = channel.QueueDeclare(queue: "").QueueName;
consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
if (!callbackMapper.TryRemove(ea.BasicProperties.CorrelationId, out TaskCompletionSource<string> tcs))
return;
var body = ea.Body.ToArray();
var response = Encoding.UTF8.GetString(body);
tcs.TrySetResult(response);
};
channel.BasicConsume(
consumer: consumer,
queue: replyQueueName,
autoAck: true);
}
the event listener is declared inside the constructor, as you can see, and I want only one instance of this class.
Also, I've read that it is not very good practice to put event listeners in a constructor.
So, is this done so, only for the sake of the example and how should I use event listener?
Should it be a class, which inherits the BackgroundService class?
Related
The following is the exception i receive .Net core and rabbitMQ :
The AMQP operation was interrupted: AMQP close-reason, initiated by Peer, code=405, text='RESOURCE_LOCKED - cannot obtain exclusive access to locked queue 'demo-queue' in vhost '/'. It could be originally declared on another connection or the exclusive property value does not match that of the original declaration.', classId=50, methodId=10
Producer :
var factory = new ConnectionFactory
{
Uri = new Uri("amqp://guest:guest#localhost:5672")
};
using var connection= factory.CreateConnection();
using var channel= connection.CreateModel();
channel.QueueDeclare("demo-queue",durable:true,exclusive:true,autoDelete:false, arguments:null);
var message = new
{
Name = "Producer",
Message = "Hello!"
};
var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message));
channel.BasicPublish("", "demo-queue",null,body);
Consumer:
var factory = new ConnectionFactory
{
Uri = new Uri("amqp://guest:guest#localhost:5672")
};
using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();
channel.QueueDeclare("demo-queue", durable: true,
exclusive: true, autoDelete: false,
arguments: null);
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (sender, e) =>
{
var body = e.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
Console.WriteLine(message);
};
channel.BasicConsume("demo-queue",true,consumer);
Is it the right practice or right way IF I Change the name of the queue that I have given in producer or consumer? which is the right way to handle this?
In the QueueDeclare section, you said properties that are different from the queue you created.
Edit:
All your mistakes are in this line of code. Because you created a queue. Now you want to open it with the wrong properties. I think exclusive: false will solve the problem
//channel.QueueDeclare("demo-queue",durable:true,exclusive:true,autoDelete:false, arguments:null);
channel.QueueDeclare(queue: "demo-queue",
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
Exchange and queu need to be connected. If you do not do this, the message cannot know the queue address it will go to. After QueueDeclare, it must be bind with the exchange address. I have implemented it this way. If you want, you can look at the repo I created here.
https://github.com/oguzhanKomcu/RabbitMQ_Sample
I think this is the missing function.
public void BindQueu(string exName, string queuName, string routingKey)
{
channel.QueueBind(queuName,exName, routingKey);
}
Looking at the example code from Rabbit MQ's site for a consumer...
var factory = new ConnectionFactory() { HostName = "localhost" };
using(var connection = factory.CreateConnection())
using(var channel = connection.CreateModel())
{
channel.QueueDeclare(queue: "hello",
durable: false,
exclusive: false,
autoDelete: false,
arguments: null);
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);
};
channel.BasicConsume(queue: "hello",
autoAck: true,
consumer: consumer);
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}
It initially looked like the messages were coming off the queue 1 by 1 in order and handled consecutively by the code inside the Received section.
However the result I am now seeing suggests that they are probably coming off 1 by 1 in order, but being handle concurrently, is this correct?
Regards
Tom
Using RabbitMQ it is possible to control how many messages that should be processed concurrent by calling:
channel.BasicQos(0, <MaxConcurrentConsumerThreads>, false);
So if chronology is important calling channel.BasicQos(0, 1, false); ensures only 1 message is processed at a time.
At The moment im learning how to work with the RabbitMQ.
Sending works. But Recieving doesn't work. This is my code:
var factory = new ConnectionFactory() { HostName = hostName };
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.QueueDeclare(queue: queueName,
durable: false,
exclusive: false,
autoDelete: false,
arguments: null);
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
Console.WriteLine("Recieved: {0}", message);
};
consumer.Shutdown += (o, e) =>
{
Console.WriteLine("Error with RabbitMQ: {0}", e.Cause);
createConnection(hostName, queueName);
};
channel.BasicConsume(queueName, true, consumer);
}
This is copied from the Tutorial. If I start the Application, consumer.Shutdown is directly called and I get:
{AMQP close-reason, initiated by Application, code=200, text="Goodbye", classId=0, methodId=0, cause=}
Can anyone help me?
channel.BasicConsume is non-blocking call, which means it will return immediately. What happens next in your example is your channel and connection are getting disposed (because of using statement), and so you see immediate shutdown. In the example you copied this code from, there is Console.ReadLine statement right after channel.BasicConsume. This prevents channel and connection from disposing until user press key in console.
As I understand message brokers like RabbitMQ facilitates different applications written in different language/platform to communicate with each other. So since celery can use RabbitMQ as message broker, I believe we can queue task from any application to Celery, even though the producer isn't written in Python.
Now I am trying to figure out how I can queue a task to Celery from an application written in C# via RabbitMQ. But I could not find any such example yet.
The only information close to this I found is this SO question
Where the accepted answer suggests to use the Celery message format protocol to queue messages to RabbitMQ from Java. However, the link given in the answer does not have any example, only the message format.
Also, the message format says task id (UUID) is required to communicate in this protocol. How is my C# application supposed to know the task id of the celery task? As I understand it can only know about the task name, but not the task id.
I don't know whether the question is still relevant, but hopefully the answer will help others.
Here is how I succeeded in queening a task to Celery example worker.
You'll need to establish connection between your producer(client) to RabbitMQ as described here.
ConnectionFactory factory = new ConnectionFactory();
factory.UserName = username;
factory.Password = password;
factory.VirtualHost = virtualhost;
factory.HostName = hostname;
factory.Port = port;
IConnection connection = factory.CreateConnection();
IModel channel = connection.CreateModel();
In default RabbitMQ configuration there is only Guest user which can only be used for local connections (from 127.0.0.1). An answer to this question explains how to define users in RabbitMQ.
Next - creating a callback to get results. This example is using Direct reply-to, so an answer listener will look like:
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var ansBody = ea.Body;
var ansMessage = Encoding.UTF8.GetString(ansBody);
Console.WriteLine(" [x] Received {0}", ansMessage);
Console.WriteLine(" [x] Done");
};
channel.BasicConsume(queue: "amq.rabbitmq.reply-to", noAck: true, consumer: consumer);
Creating a task message that Celery will consume:
IDictionary<string, object> headers = new Dictionary<string, object>();
headers.Add("task", "tasks.add");
Guid id = Guid.NewGuid();
headers.Add("id", id.ToString());
IBasicProperties props = channel.CreateBasicProperties();
props.Headers = headers;
props.CorrelationId = (string)headers["id"];
props.ContentEncoding = "utf-8";
props.ContentType = "application/json";
props.ReplyTo = "amq.rabbitmq.reply-to";
object[] taskArgs = new object[] { 1, 200 };
object[] arguments = new object[] { taskArgs, new object(), new object()};
MemoryStream stream = new MemoryStream();
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(object[]));
ser.WriteObject(stream, arguments);
stream.Position = 0;
StreamReader sr = new StreamReader(stream);
string message = sr.ReadToEnd();
var body = Encoding.UTF8.GetBytes(message);
And finally, publishing the message to RabbitMQ:
channel.BasicPublish(exchange: "",
routingKey: "celery",
basicProperties: props,
body: body);
Celery comes with Flower. Flower provides a REST API to managing tasks. https://flower.readthedocs.io/en/latest/api.html#post--api-task-async-apply-(.+)
In most cases this will be much simpler and robust to use than creating tasks manually and inserting them on the MQ.
According to this article, celery .Net client uses default TaskScheduler that comes with .Net Framework. This knows how to generate ID for your task. This article also points to some example here.
I am using sample code to implement Publish/Subscribe using "fanout" exchange type. But as in below code subscriber is not displaying 'Hello Word' message which is published.
Publisher.cs
var factory = new ConnectionFactory() { HostName = "localhost" };
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.ExchangeDeclare(exchange: "logs", type: "fanout");
var message = GetMessage(args);
var body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange: "logs",
routingKey: "",
basicProperties: null,
body: body);
Console.WriteLine(" [x] Sent {0}", message);
}
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}
private static string GetMessage(string[] args)
{
return ((args.Length > 0)
? string.Join(" ", args)
: "info: Hello World!");
}
Subscriber.cs
var factory = new ConnectionFactory() { HostName = "localhost" };
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.ExchangeDeclare(exchange: "logs", type: "fanout");
var queueName = channel.QueueDeclare().QueueName;
channel.QueueBind(queue: queueName,
exchange: "logs",
routingKey: "");
Console.WriteLine(" [*] Waiting for logs.");
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
Console.WriteLine(" [x] {0}", message);
};
channel.BasicConsume(queue: queueName,
noAck: true,
consumer: consumer);
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}
Code Ref: https://www.rabbitmq.com/tutorials/tutorial-three-dotnet.html
As you figured out it actually works, you just need to start the subscriber first. Why? The answer is also on the link you provided. I'll quote one part here:
But that's not the case for our logger. We want to hear about all log
messages, not just a subset of them. We're also interested only in
currently flowing messages not in the old ones. To solve that we need
two things.
Firstly, whenever we connect to Rabbit we need a fresh, empty queue.
To do this we could create a queue with a random name, or, even better
- let the server choose a random queue name for us.
Secondly, once we disconnect the consumer the queue should be
automatically deleted.
This basically means that the queue is created only when you start the subscriber, and only at that point, the exchange has a queue to actually put the message in. Since you fist start the publisher, there is no queue for the message to end up in.