I want to limit no of retry in Azure ServiceBus queue receiver.
Sending messages using console application with MaxRetryCount:3
private static async Task MainAsync()
{
string connectionString = ConfigurationManager.AppSettings["ServiceBusConnection"];
QueueClient queueClient = QueueClient.CreateFromConnectionString(connectionString, QueueName);
queueClient.RetryPolicy = new RetryExponential(
minBackoff: TimeSpan.FromSeconds(0),
maxBackoff: TimeSpan.FromSeconds(30),
maxRetryCount: 3);
string tradeData = File.ReadAllText("TradeSchemaDemo.json");
var message = new BrokeredMessage(tradeData);
await queueClient.SendAsync(message);
await queueClient.CloseAsync();
}
Another side I have Azure function to receive message,
public static void run([ServiceBusTrigger("TestQueue", AccessRights.Manage, Connection = "servicebusconnection")]string myqueueitem, TraceWriter log)
{
retry++;
System.Console.WriteLine($"Retry attempt {retry}");
throw new System.Exception("Human error");
log.Info($"c# servicebus queue trigger function processed message: {myqueueitem}");
}
Still, my function calling 10 times. Why??
In this case, RetryPolicy defines the amount of the retries for send operation, not on receiving side.
Receiver retry amount is defined by Queue property Max Delivery Count. You can set it on queue level with a tool like Service Bus Explorer or programmatically while creating the queue:
var namespaceManager = NamespaceManager.CreateFromConnectionString(connectionString);
var queue = new QueueDescription(queueName);
queue.MaxDeliveryCount = 3;
if (!namespaceManager.QueueExists(queueName))
namespaceManager.CreateQueue(queue);
In case, anyone want to see code
private static async Task MainAsync()
{
string connectionString = ConfigurationManager.AppSettings["ServiceBusConnection"];
var nm = NamespaceManager.CreateFromConnectionString(connectionString);
var queue = new QueueDescription(QueueName);
queue.MaxDeliveryCount = 3;
if (!nm.QueueExists(QueueName))
await nm.CreateQueueAsync(queue);
QueueClient queueClient = QueueClient.CreateFromConnectionString(connectionString, QueueName);
string tradeData = File.ReadAllText("TradeSchemaDemo.json");
var message = new BrokeredMessage(tradeData);
await queueClient.SendAsync(message);
await queueClient.CloseAsync();
}
Related
I created a .NET core 6 project. I added Azure.Messaging.ServiceBus as the dependency. I am using below code to send message to service bus topic.
// See https://aka.ms/new-console-template for more information
using Azure.Messaging.ServiceBus;
using System.Dynamic;
using System.Net;
using System.Text;
using System.Text.Json;
Console.WriteLine("Hello, World!");
Sender t = new Sender();
Sender.Send();
class Sender
{
public static async Task Send()
{
string connectionString = "Endpoint=sb://sb-test-one.servicebus.windows.net/;SharedAccessKeyName=manage;SharedAccessKey=8e+6SWp3skB3AeDlwH6ufGEainEs45353435JzDywz5DU=;";
string topicName = "topicone";
string subscriptionName = "subone";
// The Service Bus client types are safe to cache and use as a singleton for the lifetime
try
{
await using var client = new ServiceBusClient(connectionString, new ServiceBusClientOptions
{
TransportType = ServiceBusTransportType.AmqpWebSockets
});
// create the sender
ServiceBusSender sender = client.CreateSender(topicName);
dynamic data = new ExpandoObject();
data.name = "Abc";
data.age = 6;
// create a message that we can send. UTF-8 encoding is used when providing a string.
var messageBody = JsonSerializer.Serialize(data);
ServiceBusMessage message = new ServiceBusMessage(messageBody);
// send the message
await sender.SendMessageAsync(message);
var s = 10;
}
catch (Exception e)
{
var v = 10;
}
//// create a receiver for our subscription that we can use to receive the message
//ServiceBusReceiver receiver = client.CreateReceiver(topicName, subscriptionName);
//// the received message is a different type as it contains some service set properties
//ServiceBusReceivedMessage receivedMessage = await receiver.ReceiveMessageAsync();
//// get the message body as a string
//string body = receivedMessage.Body.ToString();
//Console.WriteLine(body);
Console.WriteLine("Press any key to end the application");
Console.ReadKey();
}
}
Issue: When I call await sender.SendMessageAsync(message); after this line get executed, the program is actually terminating. It not awating. The whole execution stops after this line.
System is not throwing any exception and service bus is not receiving any message.
I just noticed that all other samples I saw had a default SharedAccessPolicy called RootManageSharedAccessKey policy available by default in the azure portal. For me, I had to create this policy. To my policy I have given Manage, Send, ReceiveAccess.
Needed to change Sender.Send(); to Sender.Send().GetAwaiter().GetResult();
I'm saving a row to my db (class with teacher/students, time, date, etc), once I have the id I create a message on my Azure service bus where the unique id of the row from my db is used as the message body of the service bus message. I'm creating scheduled messages so I can notify the students before the class and after the class is over so they can rate/review their teacher.
QUESTION - I'd like to know how to roll back or an easy way to remove the db row by not allowing it to fully save if the message to the Azure service bus fails to save?
Currently I'm using a generic repository with UnitOfWork to save to my db and I'm catching the exception from my service bus service if it fails, then deleting the row that was just saved, but it's sloppy looking and I can see it will lead to problems.
Here is what I'm doing now in the controller.
[HttpPost("create")]
public async Task<IActionResult> OnCreating(OnCreatingEventDto onCreatingDto)
{
var userFromRepo = await _userManager.FindByEmailFromClaimsPrinciple(HttpContext.User);
if (userFromRepo == null)
return Unauthorized(new ApiResponse(401));
var newEvent = _mapper.Map<ClassEvent>(onCreatingDto);
_unitOfWork.Repository<ClassEvent>().Add(newEvent);
var success = await _unitOfWork.Complete();
if (success > 0) {
try {
var sequenceNUmber = await _serviceBusProducer.SendMessage(newEvent.Id.ToString(), newEvent.eventTime.addDays(1), queueName);
newEvent.ServiceBusSequenceNumber = sequenceNUmber;
_unitOfWork.Repository<ClassEvent>().Update(newEvent);
var secondSuccess = await _unitOfWork.Complete();
if (secondSuccess > 0) {
return Ok();
}
} catch(Exception ex) {
_logger.LogError("error saving to service bus");
_unitOfWork.Repository<ClassEvent>().Delete(newEvent);
var deleteSuccess = await _unitOfWork.Complete();
if (deleteSuccess > 0) {
}
return BadRequest(new ApiResponse(400, "Problem Creating Event"));
}
}
return BadRequest(new ApiResponse(400, "Problem Creating Event"));
}
Here is the method from my service that creates the message on the queue
public async Task<long> SendMessage(string messageBody, DateTimeOffset scheduledEnqueueTime, string queueName)
{
await using (ServiceBusClient client = new ServiceBusClient(_config["ServiceBus:Connection"]))
{
ServiceBusSender sender = client.CreateSender(_config["ServiceBus:" + queueName]);
ServiceBusMessage message = new ServiceBusMessage(messageBody);
var sequenceNumber =
await sender.ScheduleMessageAsync(message, scheduledEnqueueTime);
return sequenceNumber;
}
}
I have below code which works fine but when function got trigger it only read one message at a time.
to get more than 1 message i have given count 10 into ReceiveAsync(10) , but
still getting only one message.
public static async System.Threading.Tasks.Task RunAsync([TimerTrigger("0 */2 * * * *")]TimerInfo myTimer, ILogger log)
{
log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
try
{
var deadQueuePath = EntityNameHelper.FormatDeadLetterPath("demo/subscriptions/demo");
MessageReceiver deadletterReceiver = new MessageReceiver(Environment.GetEnvironmentVariable("ConnectionStringSettingName"), deadQueuePath, ReceiveMode.PeekLock,RetryPolicy.Default);
MessageSender sender = new MessageSender(Environment.GetEnvironmentVariable("ConnectionStringSettingName"), "demo",RetryPolicy.Default);
var deadLetter = await deadletterReceiver.ReceiveAsync(10);
if (deadLetter != null)
{
log.LogInformation($"got new message");
Message newMessage = new Message(deadLetter.Body)
{
ContentType = deadLetter.ContentType,
CorrelationId = deadLetter.CorrelationId
};
//Send the message to the Active Queue
await sender.SendAsync(newMessage);
await deadletterReceiver.CompleteAsync(item.SystemProperties.LockToken); //Unlock the message and remove it from the DLQ
}
}
catch (Exception ex)
{
log.LogInformation($"Exception: {ex}");
}
}
ReceiveAsync(10) - This method is used for setting the sequence number of the message to receive and not the count. Look at the details on ReceiveAsync here
In order to fetch multiple messages, you need to set Prefetch property to the receiver.
Make use of the below constructor to initialize your MessageReceiver Class:
public MessageReceiver (Microsoft.Azure.ServiceBus.ServiceBusConnection serviceBusConnection, string entityPath, Microsoft.Azure.ServiceBus.ReceiveMode receiveMode = Microsoft.Azure.ServiceBus.ReceiveMode.PeekLock, Microsoft.Azure.ServiceBus.RetryPolicy retryPolicy = null, int prefetchCount = 0);
public static async Task DoMessage()
{
const int numberOfMessages = 10;
queueClient = new QueueClient(ConnectionString, QueueName);
await SendMessageAsync(numberOfMessages);
await queueClient.CloseAsync();
}
private static async Task SendMessageAsync(int numOfMessages)
{
try
{
for (var i = 0; i < numOfMessages; i++)
{
var messageBody = $"Message {i}";
var message = new Message(Encoding.UTF8.GetBytes(messageBody));
message.SessionId = i.ToString();
await queueClient.SendAsync(message);
}
}
catch (Exception e)
{
}
}
This is my sample code to send message to the service bus queue with session id.
My question is if I call DoMessage function 2 times: Let's name it as MessageSet1 and MessageSet2, respectively. Will the MessageSet2 be received and processed by the received azure function who dealing with the receiving ends of the message.
I want to handle in order like MessageSet1 then the MessageSet2 and never handle with MessageSet2 unless MessageSet1 finished.
There are a couple of issues with what you're doing.
First, Azure Functions do not currently support sessions. There's an issue for that you can track.
Second, the sessions you're creating are off. A session should be applied on a set of messages using the same SessionId. Meaning your for loop should be assigning the same SessionId to all the messages in the set. Something like this:
private static async Task SendMessageAsync(int numOfMessages, string sessionID)
{
try
{
var tasks = new List<Task>();
for (var i = 0; i < numOfMessages; i++)
{
var messageBody = $"Message {i}";
var message = new Message(Encoding.UTF8.GetBytes(messageBody));
message.SessionId = sessionId;
tasks.Add(queueClient.SendAsync(message));
}
await Task.WhenAll(tasks).ConfigureAwait(false);
}
catch (Exception e)
{
// handle exception
}
}
For ordered messages using Sessions, see documentation here.
I am calling an async method InsertOperation from an async method ConfigureConnectionString. Am I using the client.OnMessage call correctly? I want to process the messages in a queue asynchronously and then store them to the queue storage.
private static async void ConfigureConnectionString()
{
var connectionString =
"myconnstring";
var queueName = "myqueue";
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString"));
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable table = tableClient.GetTableReference("test");
table.CreateIfNotExists();
Stopwatch sw = Stopwatch.StartNew();
await Task.Run(() => InsertOperation(connectionString, queueName, table));
sw.Stop();
Console.WriteLine("ElapsedTime " + sw.Elapsed.TotalMinutes + " minutes.");
}
private static async Task InsertOperation(string connectionString, string queueName, CloudTable table)
{
var client = QueueClient.CreateFromConnectionString(connectionString, queueName);
client.OnMessage(message =>
{
var bodyJson = new StreamReader(message.GetBody<Stream>(), Encoding.UTF8).ReadToEnd();
var myMessage = JsonConvert.DeserializeObject<VerifyVariable>(bodyJson);
Console.WriteLine();
var VerifyVariableEntityObject = new VerifyVariableEntity()
{
ConsumerId = myMessage.ConsumerId,
Score = myMessage.Score,
PartitionKey = myMessage.ConsumerId,
RowKey = myMessage.Score
};
});
}
OnMessageAsync method provides async programming model, it enables us to process a message asynchronously.
client.OnMessageAsync(message =>
{
return Task.Factory.StartNew(() => ProcessMessage(message));
//you could perofrm table and queue storage in ProcessMessage method
}, options);
Without understanding the actual logic you want to achieve, it looks like you are not using OnMessage correctly.
OnMessage is a way to set up the queue client behavior for a long running client. It makes sense, for example, if you have a singleton instance in your application. In that case, you are specifing to the client how you want to handle any messages that are put in the queue.
In your example, however, you create the client, set up the OnMessage, and don't persist the client, so it effectively doesn't get anything accomplished.