I wanted to get one messages from each session to process as Fan out - azure-servicebus-queues

I have session enabled service bus queue where I am pushing 9 messages (3 messages for 3 sessions, for example).
Now I am trying to use azure durable function where I am using service bus queue trigger as client function and I want to orchestrate 1 messages processing from each session, means 3 activities function runs in parallel.
I have below client function code, but I here I observed that every time only 1 messages coming from single session,
[FunctionName("FanOutIn_Run")]
public static async Task Run(
[ServiceBusTrigger("file-scanner-queue", Connection = "ServiceBusConnectionString",
IsSessionsEnabled = true, AutoCompleteMessages = false)] ServiceBusReceivedMessage[] messages,
ServiceBusMessageActions messageActions, [DurableClient] IDurableOrchestrationClient starter, ILogger log)
{
var serviceBusReceivedMessages = messages.ToList();
// only 1 message from 1 session coming here
}
I have also set maxConcurrentSessions = 10
"extensions": {
"serviceBus": {
"sessionHandlerOptions": {
"autoComplete": false,
"maxConcurrentSessions": 10
}
}
Can I alter this behavior? I want 1 messages from each session?

Related

Masstransit: GetSendEndpoint

I have a producer, which send more than 1000 messages in a minute to a specific endpoint. I’m using Microsoft DI and I’ve configured the send Endpoint as described here https://masstransit-project.com/usage/producers.html#send .
// Masstransit setup
serviceCollection.AddMassTransit(mt =>
{
mt.UsingAzureServiceBus((ctx, cfg) =>
{
cfg.Host(massTransitSettings.TestServiceBusConnectionString);
cfg.ReceiveEndpoint("mytestmessage", e =>
{
e.MaxDeliveryCount = 3; //How many times the transport will redeliver the message on negative acknowledgment
});
});
});
serviceCollection.AddTransient<ITestMessageProducer, TestMessageProducer>();
// Producer setup
public class TestMessageProducer : ITestMessageProducer
{
private readonly ISendEndpointProvider _testEndpoint;
public TestMessageProducer(ISendEndpointProvider testEndpoint)
{
_testEndpoint = testEndpoint;
}
public async Task SendTestMessage(ITestMessage testmessage)
{
var endpoint = await _testEndpoint.GetSendEndpoint(new Uri("queue:mytestmessage"));
await endpoint.Send(testmessage);
}
}
Query:
The SendTestMessage function has been called very frequently as mention above. Will it be ok to call “GetSendEndpoint” everytime? I have read somewhere that GetSendEndpoint creates a new instance of ISendEndpoint everytime.
Will the MaxDeliveryCount still be worked on my sendendpoint?
Thank you.
Send endpoints are cached by address, only a single instance will be created.
MaxDeliveryCount is a receive endpoint concern, but you should not configure a receive endpoint without consumers as all messages will be moved to the _skipped queue.

Successful request/response from saga leaves Canceled message in saga skipped queue

After much toil and trial and error I managed to issue a "request" from my saga and see it handle the response. My jubilation was cut short however by the appearance of a message in my states' skipped queue. (i'm using azure service bus)
It is of type "urn:message:MassTransit.Scheduling:CancelScheduledMessage".
I am a complete newbie at with mass transit and I'm just trying to get a contrived example going.
My saga calls TaxiToRunway/TaxiingComplete. My bit of saga code
Request(()=>TaxiToRunway, config =>
{
config.Timeout = TimeSpan.FromSeconds(30);
});
...
public Request<PlaneState, TaxiToRunway, TaxiingComplete> TaxiToRunway { get; private set; }
...
Initially(
When(ReadyToDepart)
.Then(context =>
{
context.Saga.Altitude = 0;
context.Saga.Speed = 0;
context.Saga.FlightNo = context.Message.FlightNo;
context.Saga.CorrelationId = context.Message.CorrelationId;
Console.WriteLine($"Flight {context.Message.FlightNo} is ready to depart.");
})
.TransitionTo(Taxiing)
.Request(TaxiToRunway,
(context) => context.Init<TaxiToRunway>(new {CorrelationId = context.Saga.CorrelationId}))
...
During(Taxiing,
Ignore(ReadyToDepart),
When(TaxiToRunway.Completed)
.Then(x =>
{
x.ToString();
})
.TransitionTo(TakingOff),
With a debugger attached I hit the x.ToString() line.
The consumer (in a different host):
public class TaxiToRunwayConsumer: IConsumer<TaxiToRunway>
{
public async Task Consume(ConsumeContext<TaxiToRunway> context)
{
await context.RespondAsync<TaxiingComplete>(new
{
context.Message.CorrelationId
});
}
}
Saga startup config:
cfg.AddSagaStateMachine<PlaneStateMachine, PlaneState>()
.MessageSessionRepository();
cfg.AddServiceBusMessageScheduler();
cfg.UsingAzureServiceBus((context, sbCfg) =>
{
var connectionString = appConfig.ServiceBus.ConnectionString;
sbCfg.Host(connectionString);
EndpointConvention.Map<TaxiToRunway>(new Uri("sb://xxx.servicebus.windows.net/taxi-to-runway"));
sbCfg.UseServiceBusMessageScheduler();
sbCfg.ReceiveEndpoint("plane-state", e =>
{
e.UseInMemoryOutbox();
e.RequiresSession = true;
e.PrefetchCount = 50;
e.MaxConcurrentCalls = 50;
e.ConfigureSaga<PlaneState>(context);
});
sbCfg.ConfigureEndpoints(context);
});
I can see this in the log output:
dbug: MassTransit.Messages[0]
SEND sb://dbpdf-us-dev-sam.servicebus.windows.net/plane-state 80d90000-5d7b-2cf0-7a6b-08da0fd3e7b7 MassTransit.Scheduling.CancelScheduledMessage
Am I supposed to be handling this as an event??
Learning curve on this sure is steep! My question is what do I need to do to not have these messages go to skipped?
So, the reason this doesn't work:
The message session saga repository can only correlate by the SessionId, since it's session-stored data.
The requestId, therefore, MUST equal the saga instance correlationId (aka, the SessionId)
The timeout message, sent by the request, gets a tokenId based upon the sequence number of the scheduled message
Which isn't saved anywhere
So the request timeout isn't canceled
The proper approach, in this scenario, is to use a Request/Response that doesn't have a timeout and use a separate Schedule to schedule the timeout yourself.

Warning publishing Azure service bus queue trigger can't find named value in local.settings.json

I have a Azure service bus queue trigger function and when I created it it asked me 3 fields, access rights, connection and queue name.
I put in listen for the access rights. For the connection I used the the 'primary connection' name given in the 'RootManageSharedAccessKey' in the service bus I created.
It looks something like this
Endpoint=sb://yogaband2017.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=JG0gwJ90bkbGl1BU=
and I created a queue in my service bus called yogaband and that is what I used for the queue name as the third parameter.
My function looks like this
public static class PostEventEmails
{
[FunctionName("PostEventEmails")]
public static void Run([ServiceBusTrigger("yogaband2017", AccessRights.Listen, Connection = "Endpoint=sb://yogaband2017.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=gkbGl1BU=")]string myQueueItem, TraceWriter log)
{
log.Info($"C# ServiceBus queue trigger function processed message: {myQueueItem}");
}
}
When I publish the function I get this warning
.nuget\packages\microsoft.net.sdk.functions\1.0.2\build\netstandard1.0\Microsoft.NET.Sdk.Functions.Build.targets(31,5): warning : Function [PostEventEmails]: cannot find value named 'Endpoint=sb://yogaband2017.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=0bkbGl1BU=' in local.settings.json that matches 'connection' property set on 'serviceBusTrigger' [C:\Users\Source\Workspaces\YogaBand2017\YogaBand2017\PostEventEmails\PostEventEmails.csproj]
and in my site I can pass the queue a message and I see the message in the queue in my Azure portal, but the function isn't picking up the message and processing it. So I still see '1 message' in the active message count in the queue. I assume it would be 0 after the function picks it up and processes it and I would see the log trace in the window? But I don't so I think the connection isn't correct or I didn't configure something correctly but I don't know what!
Here is what I put into the local.settings.json file
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "Endpoint=sb://yogaband2017.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=bGl1BU=",
"AzureWebJobsDashboard": ""}}
FYI - here is how I send the mesage to the queue in c#
var queueClient = QueueClient.Create("yogaband2017");
BrokeredMessage message = new BrokeredMessage("some test message");
message.MessageId = newEvent.YogaSpaceEventId.ToString();
queueClient.Send(message);
and in my web.config file I added this
<add key="Microsoft.ServiceBus.ConnectionString" value="Endpoint=sb://yogaband2017.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=0gwJ90bkbGl1BU="/>
Connection property of ServiceBusTrigger should be set to a setting name, not connection string itself:
[ServiceBusTrigger("yogaband2017", AccessRights.Listen, Connection = "MyConn")]
You then define a setting with this name in local.settings.json for local development environment:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "... azure storage connection string ...",
"MyConn": "... service bus connection string ..."
}
}
and in App Settings for Azure deployment.
Note that AzureWebJobsStorage is NOT service bus connection.

MassTransit And Service Fabric Stateful Service?

I've been trying to come up with a demo of a website that uses MassTransit with RabbitMQ to post messages to a service running on Service Fabric as a Stateful service.
Everything was going fine, my client would post a message:
IBusControl bus = BusConfigurator.ConfigureBus();
Uri sendToUri = new Uri($"{RabbitMqConstants.RabbitMqUri}" + $"{RabbitMqConstants.PeopleServiceQueue}");
ISendEndpoint endPoint = await bus.GetSendEndpoint(sendToUri);
await endPoint.Send<ICompanyRequest>(new {CompanyId = id });
My consumer in my service fabric service was defined like:
IBusControl busControl = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
IRabbitMqHost host = cfg.Host(new Uri(RabbitMqConstants.RabbitMqUri), h =>
{
h.Username(RabbitMqConstants.UserName);
h.Password(RabbitMqConstants.Password);
});
cfg.ReceiveEndpoint(host, RabbitMqConstants.PeopleServiceQueue, e =>
{
e.Consumer<PersonInformationConsumer>();
});
});
busControl.Start();
This does allow me to consume the message in my class and I can process it fine. The problem comes when we want to use IReliableDictonary or IReliableQueue or anything that needs to reference the context that is run from the RunAsync function in the service fabric service.
So my question is, how can I configure (is it possible) MassTransit to work within a Stateful Service Fabric Service which knowledge of the service context itself?
Many thanks in advance.
Mike
Update
Ok, I've made some progress on this, if I point the register routines to my message consumer class (eg):
ServiceRuntime.RegisterServiceAsync("ServiceType", context => new PersonInformationConsumer(context)).GetAwaiter().GetResult();
ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(PersonInformationConsumer).Name);
Then in my consumer class for my messages I can do the following:
internal sealed class PersonInformationConsumer : StatefulService, IConsumer<ICompanyRequest>
{
private static StatefulServiceContext _currentContext;
#region Constructors
public PersonInformationConsumer(StatefulServiceContext serviceContext) : base(serviceContext)
{
_currentContext = serviceContext;
}
public PersonInformationConsumer() : base(_currentContext)
{
}
I can now successfully call the service message:
ServiceEventSource.Current.ServiceMessage(this.Context, "Message has been consumed, request Id: {0}", context.Message.CompanyId);
The problem I have now is trying to store something on the IReliableDictionary, doing this causes as "Object reference not set to an instance of an object" error :( ... any ideas would be appreciated (although may not read until new year now!)
public async Task Consume(ConsumeContext<ICompanyRequest> context)
{
ServiceEventSource.Current.ServiceMessage(this.Context, "Message has been consumed, request Id: {0}", context.Message.CompanyId);
using (ITransaction tx = StateManager.CreateTransaction())
{
try
{
var myDictionary = await StateManager.GetOrAddAsync<IReliableDictionary<string, long>>("myDictionary");
This is causing the error.... HELP! :)
You'll need to do a bit more to get MassTransit and stateful services working together, there's a few issues to concern yourself here.
Only the master within a stateful partition (n masters within n partitions) will be able to write/update to the stateful service, all replicas will throw exceptions when trying to write back any state. So you'll need to deal with this issue, on the surface it sounds easy until you take in to consideration the master can move around the cluster due to re-balancing the cluster, the default for general service fabric applications is to just turn off the processing on the replicas and only run the work on the master. This is all done by the RunAsync method (try it out, run 3 stateful services with something noddy in the RunAsync method, then terminate the master).
There is also partitioning of your data to consider, due to stateful services scale with partitions, you'll need to create a way to distributing data to separate endpoint on your service bus, maybe have a separate queue that only listens to a given partition range? Say you have a UserCreated message, you might split this on country UK goes to partition 1, US goes to partition 2 etc...
If you just want to get something basic up and running, I'd limit it to one partition and just try putting your bus creation within the the RunAsync and shutdown the bus once a cancelation is requested on the cancelation token.
protected override async Task RunAsync(CancellationToken cancellationToken)
{
var busControl = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
IRabbitMqHost host = cfg.Host(new Uri(RabbitMqConstants.RabbitMqUri), h =>
{
h.Username(RabbitMqConstants.UserName);
h.Password(RabbitMqConstants.Password);
});
cfg.ReceiveEndpoint(host, RabbitMqConstants.PeopleServiceQueue, e =>
{
// Pass in the stateful service context
e.Consumer(c => new PersonInformationConsumer(Context));
});
});
busControl.Start();
while (true)
{
if(cancellationToken.CancellationRequested)
{
//Service Fabric wants us to stop
busControl.Stop();
cancellationToken.ThrowIfCancellationRequested();
}
await Task.Delay(TimeSpan.FromSeconds(1));
}
}

Async sends in .NET ActiveMQ

I'm looking to increase the performance of a high-throughput producer that I'm writing against ActiveMQ, and according to this useAsyncSend will:
Forces the use of Async Sends which adds a massive performance boost;
but means that the send() method will return immediately whether the
message has been sent or not which could lead to message loss.
However I can't see it making any difference to my simple test case.
Using this very basic application:
const string QueueName = "....";
const string Uri = "....";
static readonly Stopwatch TotalRuntime = new Stopwatch();
static void Main(string[] args)
{
TotalRuntime.Start();
SendMessage();
Console.ReadLine();
}
static void SendMessage()
{
var session = CreateSession();
var destination = session.GetQueue(QueueName);
var producer = session.CreateProducer(destination);
Console.WriteLine("Ready to send 700 messages");
Console.ReadLine();
var body = new byte[600*1024];
Parallel.For(0, 700, i => SendMessage(producer, i, body, session));
}
static void SendMessage(IMessageProducer producer, int i, byte[] body, ISession session)
{
var message = session.CreateBytesMessage(body);
var sw = new Stopwatch();
sw.Start();
producer.Send(message);
sw.Stop();
Console.WriteLine("Running for {0}ms: Sent message {1} blocked for {2}ms",
TotalRuntime.ElapsedMilliseconds,
i,
sw.ElapsedMilliseconds);
}
static ISession CreateSession()
{
var connectionFactory = new ConnectionFactory(Uri)
{
AsyncSend = true,
CopyMessageOnSend = false
};
var connection = connectionFactory.CreateConnection();
connection.Start();
var session = connection.CreateSession(AcknowledgementMode.AutoAcknowledge);
return session;
}
I get the following output:
Ready to send 700 messages
Running for 2430ms: Sent message 696 blocked for 12ms
Running for 4275ms: Sent message 348 blocked for 1858ms
Running for 5106ms: Sent message 609 blocked for 2689ms
Running for 5924ms: Sent message 1 blocked for 2535ms
Running for 6749ms: Sent message 88 blocked for 1860ms
Running for 7537ms: Sent message 610 blocked for 2429ms
Running for 8340ms: Sent message 175 blocked for 2451ms
Running for 9163ms: Sent message 89 blocked for 2413ms
.....
Which shows that each message takes about 800ms to send and the call to session.Send() blocks for about two and a half seconds. Even though the documentation says that
"send() method will return immediately"
Also these number are basically the same if I either change the parallel for to a normal for loop or change the AsyncSend = true to AlwaysSyncSend = true so I don't believe that the async switch is working at all...
Can anyone see what I'm missing here to make the send asynchronous?
After further testing:
According to ANTS performance profiler that vast majority of the runtime is being spent waiting for synchronization. It appears that the issue is that the various transport classes block internally through monitors. In particular I seem to get hung up on the MutexTransport's OneWay method which only allows one thread to access it at a time.
It looks as though the call to Send will block until the previous message has completed, this explains why my output shows that the first message blocked for 12ms, while the next took 1858ms. I can have multiple transports by implementing a connection-per-message pattern which improves matters and makes the message sends work in parallel, but greatly increases the time to send a single message, and uses up so many resources that it doesn't seem like the right solution.
I've retested all of this with 1.5.6 and haven't seen any difference.
As always the best thing to do is update to the latest version (1.5.6 at the time of this writing). A send can block if the broker has producer flow control enabled and you've reached a queue size limit although with async send this shouldn't happen unless you are sending with a producerWindowSize set. One good way to get help is to create a test case and submit it via a Jira issue to the NMS.ActiveMQ site so that we can look into it using your test code. There have been many fixes since 1.5.1 so I'd recommend giving that new version a try as it could already be a non-issue.

Resources