RabbitMQ Listener is not fired - c#

I am working a asp.netcore 6.0 project
I am using RabbitMQ to cart implementation. i.e: After payment is success, Booking should be placed.
first I'm creating queue:
var factory = new ConnectionFactory
{
Uri = new Uri(_config.GetValue<string>("AmpqUrl")),
};
try
{
using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();
channel.QueueDeclare(queue: "confirmed_payments", durable: true, exclusive: false, autoDelete: false, arguments: null);
var data = new
{
transactionId,
paymentConfirmedAt = DateTime.UtcNow,
};
var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data));
channel.BasicPublish(exchange: "", routingKey: "confirmed_payments", basicProperties: null, body: body);
}
catch (RabbitMQ.Client.Exceptions.BrokerUnreachableException ex)
{
Console.WriteLine("ex.ToString()");
}
and listener of it (another project):
public Task ListenPaymentConfimations(CancellationToken cancellationToken)
{
var factory = new ConnectionFactory
{
Uri = new Uri(_configuration.GetValue<string>("AmpqUrl")),
};
_connection = factory.CreateConnection();
_channel = _connection.CreateModel();
var confimedPaymentsConsumer = new EventingBasicConsumer(_channel);
confimedPaymentsConsumer.Received += async (model, ea) =>
{
var body = ea.Body.ToArray();
var response = Encoding.UTF8.GetString(body);
var data = JsonConvert.DeserializeAnonymousType(response,
new { transactionId = "", paymentConfirmedAt = "" }
);
var date = DateTime.Parse(data.paymentConfirmedAt).ToUniversalTime();
using var scope = _serviceProvider.CreateScope();
var dbService =
scope.ServiceProvider.GetRequiredService<ITechneDbService>();
var isPaymentConfimed = await dbService.UpdateCartPaymentConfirmedAt(data.transactionId, date);
_logger.LogInformation("Transaction - {0}", data.transactionId);
_logger.LogInformation("Transaction - {0}", date);
_logger.LogInformation("Payment Confirmed - {0}", isPaymentConfimed);
if (isPaymentConfimed)
{
// handle booking
}
};
_channel.BasicConsume(queue: "confirmed_payments",
autoAck: true,
consumer: confimedPaymentsConsumer);
return Task.CompletedTask;
}
When I try to make hotel booking, sometimes Listener is hitted and I can made booking successfully.
But Sometimes listener in not hitted (There is no exception thrown when creating queue).
I don't know why this happens.
Anyone can find the issue,
Please help me.

Change the constructor for your connection to indicate that consumers should be dispatched asynchronously.
var factory = new ConnectionFactory
{
Uri = new Uri(_configuration.GetValue<string>("AmpqUrl")),
DispatchConsumersAsync = true // <---- this
};
You'll probably also want to change over to using AsyncEventingBasicConsumer instead of EventingBasicConsumer.

Related

Unable to deserialize the body bytes into object after upgrade the RabbitMQ Client from 5.x.x to 6.x.x

I am using RabbitMQ.Client for messaging and using dead letter pattern to implement retry mechanism, everything works fine with RabbitMQ.Client with version :5.1.1 .
The pseudo-code is below:
public async Task Handle(BasicDeliverEventArgs basicDeliverEventArgs)
{
try
{
var bodyBytes = basicDeliverEventArgs.Body.ToArray();
var bodyString = Encoding.UTF8.GetString(bodyBytes);
// extension method with newtonsoft.json behind
bodyString.TryDeserializeObject<T>(out var message);
// do somesting
}
catch (Exception ex)
{
var properties = basicDeliverEventArgs.BasicProperties;
if (properties.Headers == null)
{
properties.Headers = new Dictionary<string, object>();
}
var bodyBytes = basicDeliverEventArgs.Body.ToArray();
var bodyString = Encoding.UTF8.GetString(bodyBytes);
// retrive the x-death-count by header
var retryTimes = GetRetryTimes(properties.Headers);
if (retryTimes <= _retryConfiguration.MaxCount)
{
using var channel = _conn.CreateModel();
var delay = _retryConfiguration.GetNextDelay(retryTimes);
properties.Expiration = delay.TotalMilliseconds.ToString();
string deadLetterExchangeName = $"ErrorExchange_{_rabbitSubscriptionConfiguration.Queue}";
string deadLetterQueueName = $"{_rabbitSubscriptionConfiguration.Queue}.Deadletter";
string deadLetterRoutingKey = $"{_rabbitSubscriptionConfiguration.RoutingKey}.Deadletter";
channel.ExchangeDeclare(deadLetterExchangeName, "direct", true, true);
channel.QueueDeclare(deadLetterQueueName, true, false, false, new Dictionary<string, object>()
{
{ "x-dead-letter-exchange", deadLetterExchangeName },
{ "x-dead-letter-routing-key",_rabbitSubscriptionConfiguration.RoutingKey}
});
channel.QueueBind(_rabbitSubscriptionConfiguration.Queue, deadLetterExchangeName, _rabbitSubscriptionConfiguration.RoutingKey);
channel.QueueBind(deadLetterQueueName, deadLetterExchangeName, deadLetterRoutingKey);
channel.BasicPublish(deadLetterExchangeName, deadLetterRoutingKey, false, properties, bodyBytes);
}
else
{
var messageStorage = _serviceProvider.GetService<IMessageStorage>();
await messageStorage.StoreDeadLetter(bodyString, ex);
}
}
}
However, when I upgrade the RabbitMQ.Client to 6.2.4,there is some probability that the body bytes of message unable deserialize to origin object when I receiving the message again from dead letter queue.
When the retry ended,I inspect the dead letter in the database,some of them failed to convert to a normal string
By the way,the RabbitMQ Server is 3.8.5

Rabbit MQ Queues

I have one queue-publisher and one hundred queue-subscribers. Subscribers bind to one queue. The Rabbit`s guide said that to each queue Rabbit creates single thread.
I want to send all messages to subscribers through one queue and save all unsended messages in the same queue if destination subscriber is offline.
I have two solutions:
I can send all messages through one queue to all subscribers by declaring exchange type "Direct" and bind to one QueueName. But if routingKey in publishing message not equals queue name, it doesnt save in queue, if direct consumer is offline.
suscriber`s code
static void Main(string[] args)
{
List<string> serevities1 = new List<string>() { "qwerty.red" };
List<string> serevities2 = new List<string>() { "asdfgh.green" };
string exchange = "topic_logs";
//string direction = ExchangeType.Topic;
string direction = ExchangeType.Direct;
var consumer1 = new MqDll.MqConsumer(exchange, direction, serevities1);
MqDll.MqConsumer consumer2;
Task.Run(() => {
Thread.Sleep(10000);
consumer2 = new MqDll.MqConsumer(exchange, direction, serevities2);
});
Console.ReadLine();
}
public class MqConsumer
{
private IConnection connection;
private IModel channel;
//ExchangeType.Fanout
public MqConsumer(string exchange, string direction, List<string> severities = null)
{
var factory = new ConnectionFactory() { HostName = "localhost" };
connection = factory.CreateConnection();
channel = connection.CreateModel();
channel.ExchangeDeclare(exchange, direction);
string queueName = "task_queue";
// queueName= channel.QueueDeclare().QueueName;
channel.QueueDeclare(queue: queueName,
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
Bind(queueName, exchange, severities);
var consumer = new EventingBasicConsumer(channel);
consumer.Received += MsgReceived;
channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer);
Console.WriteLine("Consumer created");
}
~MqConsumer()
{
channel.Close();
connection.Close();
}
private void Bind(string queuename, string exchange, List<string> severities)
{
if (severities != null)
{
severities.ForEach(x =>
{
channel.QueueBind(queue: queuename,
exchange: exchange,
routingKey: x);
});
}
else
{
channel.QueueBind(queue: queuename,
exchange: exchange,
routingKey: "");
}
}
private void MsgReceived(object model, BasicDeliverEventArgs ea)
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
var rkey = ea.RoutingKey;
Console.WriteLine($" [x] Received {message} {rkey}");
Console.WriteLine($" [x] Done {DateTime.Now} | {this.GetHashCode()}");
}
}
publisher`s code
static void Main(string[] args)
{
List<string> serevities = new List<string>() { "qwerty.red", "asdfgh.green" };
string exchange = "topic_logs";
//string direction = ExchangeType.Topic;
string direction = ExchangeType.Direct;
var publisher = new MqDll.MqPublisher(exchange, direction);
Console.WriteLine("Publisher created");
var msg = Console.ReadLine();
while (msg != "q")
{
serevities.ForEach(x =>
{
publisher.Publish("SomeMsg..", "topic_logs", x);
});
msg = Console.ReadLine();
}
}
public class MqPublisher
{
private IConnection connection;
private IModel channel;
public MqPublisher(string exchange, string type)
{
var factory = new ConnectionFactory() { HostName = "localhost" };
connection = factory.CreateConnection();
channel = connection.CreateModel();
channel.QueueDeclare(queue: "task_queue",
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
channel.ExchangeDeclare(exchange, type);
}
~MqPublisher()
{
channel.Close();
connection.Close();
}
public void Publish(string msg, string exchange = "logs", string severity = "", bool isPersistent = true)
{
var properties = channel.CreateBasicProperties();
properties.Persistent = isPersistent;
channel.BasicPublish(exchange: exchange,
routingKey: severity,
basicProperties: properties,
body: Encoding.UTF8.GetBytes(msg));
Console.WriteLine(" [x] Sent {0}", msg);
}
}
Or I can create queues to each subscriber and remove binding. Source code is simple and equals code in giude.
Is there a way to combine these two solutions and make one queue to all subscribers, bind subscribers to unique routing key and save messages if direct subscriber (bound to direct routing key) is offline?
Have you considered maybe a DB for users that are not online..
Queue => consumer is offline => send to DB
Consumer is online => Publisher checks db for any messages that might have been missed => then directs them to Consumber

Why is my RabbitMQ consumer receiving all messages when I'm specifying the topic I want to receive?

I have different message types on the queue and I'm trying to create multiple C# consumers that each just take a certain type off the queue.
Apparently, I'm supposed to be able to specify the routing key, like shown here, to get just those messages:
channel.QueueBind(queue: queueName, exchange: "SandboxEventBus", routingKey: "MessageEvent");
No matter why I try, I get all of the messages from the queue and not just the ones with that routing key. What am I missing?
This is the class:
public static class RabbitReceiver
{
public static void PullFromQueue(string caller, int sleepTimeInMilliseconds)
{
string hostName = "localhost";
string queueName = "Sandbox.v1";
var factory = new ConnectionFactory { HostName = hostName, UserName = "guest", Password = "guest", Port = 6003 };
var connection = factory.CreateConnection();
var channel = connection.CreateModel();
var arguments = new Dictionary<string, object>();
arguments.Add("x-message-ttl", 864000000); // Not sure why I have to specify this. Got an exception if I didn't.
//arguments.Add("topic", "MessageEvent");
channel.BasicQos(0, 1, false);
// Enabling these lines doesn't change anything. It was part of my effort of trying different things.
//channel.ExchangeDeclare("SandboxEventBus", "topic", durable: true); // topic is case-sensitive
//channel.QueueDeclare(queue: queueName, durable: true, exclusive: false, autoDelete: false, arguments: arguments);
channel.QueueBind(queue: queueName, exchange: "SandboxEventBus", routingKey: "MessageEvent");
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, eventArgs) =>
{
var body = eventArgs.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
Console.WriteLine($"Message: {message}");
Console.WriteLine($"{caller} - Pausing {sleepTimeInMilliseconds} ms before getting next message (so we can watch the Rabbit dashboard update).");
Thread.Sleep(sleepTimeInMilliseconds);
// This should probably go in a finally block.
channel.BasicAck(eventArgs.DeliveryTag, false);
};
channel.BasicConsume(queue: queueName, autoAck: false, consumer: consumer);
}
}
Here is the publishing code. I'm simply publishing two different message types.
public class MessagePublisherService : BackgroundService
{
private readonly ICapPublisher _capBus;
public MessagePublisherService(ICapPublisher capPublisher)
{
_capBus = capPublisher;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// This publishes messages. To get the messages off the queue, run the RabbitMQ project in this solution.
try
{
// To have a different event, add this.
await _capBus.PublishAsync(nameof(SnuhEvent), new SnuhEvent());
Console.WriteLine($"Published message {nameof(SnuhEvent)}.");
for (int i = 1; i <= 10; i++)
{
//_capBus.Publish(nameof(MessageEvent), new MessageEvent(i));
await _capBus.PublishAsync(nameof(MessageEvent), new MessageEvent(i));
Console.WriteLine($"Published message {i}.");
}
}
catch (Exception ex)
{
Console.WriteLine("In catch block of publisher.");
Console.WriteLine(ex);
}
}
}
And here is the queue. Notice each message type shows as a different routing key.
Here is the RMQ code in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddCap(x =>
{
x.UseSqlServer("Server=localhost; Database=Sandbox; Integrated Security=true;");
x.DefaultGroup = "Sandbox";
x.FailedRetryCount = 3;
x.FailedRetryInterval = 2; // CAP will invoke the subscriber again if not finished in N seconds.
x.UseRabbitMQ(conf =>
{
conf.ExchangeName = "SandboxEventBus";
conf.ConnectionFactoryOptions = x =>
{
x.Uri = BuildRabbitUri();
};
});
});
services.AddHostedService<MessagePublisherService>();
}
public static Uri BuildRabbitUri()
{
string protocol = "amqp";
string userName = "guest";
string password = "guest";
string host = "localhost";
string port = "6003";
return new Uri(protocol + "://" + userName + ":" + password + "#" + host + ":" + port);
}

RabbitMQ not receiving messages when used with TopShelf as a Windows Service

I'm trying to convert my RabbitMQ micro-service to a windows service. I have used TopShelf for the conversion. My RabbitMQ micro-service works perfectly fine on its own but when I run it as a service it no longer receives messages. In my public static void Main(string[] args) I have:
HostFactory.Run(host =>
{
host.Service<PersonService>(s =>
{
s.ConstructUsing(name => new PersonService());
s.WhenStarted(tc => tc.Start());
s.WhenStopped(tc => tc.Stop());
});
host.SetDescription("Windows service that provides database access totables.");
host.SetDisplayName("Service");
host.SetServiceName("Service");
});
}
Then in my PersonService class I have
public void Start() {
ConsumeMessage();
}
And finally my ConsumeMessage function:
private static void ConsumeMessage() {
MessagingConfig.SetInstance(new MessagingConstants());
IMessageFactory pmfInst = MessageFactory.Instance;
//message worker
var factory = new ConnectionFactory() {
HostName = MessagingConfig.Instance.GetBrokerHostName(),
UserName = MessagingConfig.Instance.GetBrokerUserName(),
Password = MessagingConfig.Instance.GetBrokerPassword()
};
var connection = factory.CreateConnection();
using (var channel = connection.CreateModel()) {
channel.QueueDeclare(queue: MessagingConfig.Instance.GetServiceQueueName(),
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
channel.BasicQos(0, 1, false);
var consumer = new EventingBasicConsumer(channel);
channel.BasicConsume(queue: MessagingConfig.Instance.GetServiceQueueName(),
noAck: false,
consumer: consumer);
Console.WriteLine("Service.");
Console.WriteLine(" [x] Awaiting RPC requests");
// Code Below Is Not Executed In Service
consumer.Received += (model, ea) => {
string response = null;
var body = ea.Body;
var props = ea.BasicProperties;
var replyProps = channel.CreateBasicProperties();
replyProps.CorrelationId = props.CorrelationId;
string receivedMessage = null;
try {
receivedMessage = Encoding.UTF8.GetString(body);
response = ProcessMessage(receivedMessage);
}
catch (Exception e) {
// Received message is not valid.
WinLogger.Log.Error(
"Errror Processing Message: " + receivedMessage + " :" + e.Message);
response = "";
}
finally {
var responseBytes = Encoding.UTF8.GetBytes(response);
channel.BasicPublish(exchange: "", routingKey: props.ReplyTo,
basicProperties: replyProps, body: responseBytes);
channel.BasicAck(deliveryTag: ea.DeliveryTag,
multiple: false);
}
};
Console.ReadLine();
}
Looking at A similar SO question it looks like it has something to do with the return the Windows Service is wanting, but I'm not sure of how to call ConsumeMessage so consumer.Received += (model, ea) => {...}; is executed.
EDIT: It looks like my blocking mechanism Console.ReadLine(); is ignored by the service so it just continues on and disposes of the message consumer. So how do I block there for messages to be received?
You code uses using construct, which means when your OnStart method returns, your channel will actually be disposed. The docs suggest to do your initialization on OnStart, so create your channel and consumer there, but don't use using:
this.connection = factory.CreateConnection();
this.channel = connection.CreateModel();
this.consumer = new EventingBasicConsumer(this.channel);
Then those objects will continue to exist after OnStart method is finished. You should dispose of them in the OnStop method.

C#\RabbitMq : method refactoring breaks functionality?

I am learning RabbitMq with .NET. According to the tutorial, simplest implementation of consumer looks like :
public class Receive
{
public static void Main()
{
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",
noAck: true,
consumer: consumer);
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}
}
}
And it works correctly. However, I wanted to refactor it: let define functionality of receiver in separate method. It looks like :
public class Recieve
{
private ConnectionFactory factory;
public void ConsumeSimpleMessage(string queueName = "default")
{
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(message);
};
channel.BasicConsume(queue: queueName,
noAck: true,
consumer: consumer);
}
}
}
public Recieve(string hostName = "localhost")
{
factory = new ConnectionFactory() { HostName = hostName };
}
}
And when I try to call this method in Main():
class Program
{
static void Main(string[] args)
{
Recieve reciever = new Recieve();
reciever.ConsumeSimpleMessage();
Console.ReadLine();
}
}
here it doesn't work. It show nothing. However, messages will be deleted, meaning that they were recieved. Why does it happen? Is there anything I didn't know about Event Handing?
Try to see if it works without doing using, Or if you want to use the using statement keep the Console.Read() inside the using statement and see if that works. You can keep the connection and channel open and don't have to close it manually.
If you really want to debug then you can put the breakpoint on the consumer.Receive and see if you can see the message staying unacked. That way you know when the message will be deleted.
Also I usually recommend using tracer for rabbitmq as it logs all the messages coming in the server which makes it easy to trace.

Categories

Resources