Which is the best configuration for a Kafka consumer to increase throughput - .net-core

I have a bunch of services that are integrated via Apache Kafka, and each of the services has their consumers and producers, but im facing slowing consuming rate like there's something slowing the consuming when get so much load into the topic.
Here's an example of my kafka consumer implementation:
public class Consumer : BackgroundService
{
private readonly KafkaConfiguration _kafkaConfiguration;
private readonly ILogger<Consumer> _logger;
private readonly IConsumer<Null, string> _consumer;
private readonly IMediator _mediator;
public Consumer(
KafkaConfiguration kafkaConfiguration,
ILogger<Consumer> logger,
IServiceScopeFactory provider
)
{
_logger = logger;
_kafkaConfiguration = kafkaConfiguration;
_mediator = provider.CreateScope().ServiceProvider.GetRequiredService<IMediator>();
var consumerConfig = new ConsumerConfig
{
GroupId = "order-service",
BootstrapServers = kafkaConfiguration.ConnectionString,
SessionTimeoutMs = 6000,
ConsumeResultFields = "none",
QueuedMinMessages = 1000000,
SecurityProtocol = SecurityProtocol.Plaintext,
AutoOffsetReset = AutoOffsetReset.Earliest,
EnableAutoOffsetStore = false,
FetchWaitMaxMs = 100,
AutoCommitIntervalMs = 1000
};
_consumer = new ConsumerBuilder<Null, string>(consumerConfig).Build();
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
new Thread(() => StartConsumingAsync(stoppingToken)).Start();
return Task.CompletedTask;
}
public async Task StartConsumingAsync(CancellationToken cancellationToken)
{
_consumer.Subscribe("orders");
while (!cancellationToken.IsCancellationRequested)
{
try
{
var consumedResult = _consumer.Consume(cancellationToken);
if (consumedResult == null) continue;
var messageAsEvent = JsonSerializer.Deserialize<OrderReceivedIntegrationEvent>(consumedResult.Message.Value);
await _mediator.Publish(messageAsEvent, CancellationToken.None);
}
catch (Exception e)
{
_logger.LogCritical($"Error {e.Message}");
}
}
}
and here's an example of my producer:
public class Producer
{
protected readonly IProducer<Null, string> Producer;
protected Producer(string host)
{
var producerConfig = new ProducerConfig
{
BootstrapServers = host,
Acks = Acks.Leader
};
Producer = new ProducerBuilder<Null, string>(producerConfig).Build();
}
public void Produce(InitialOrderCreatedIntegrationEvent message)
{
var messageSerialized = JsonSerializer.Serialize(message);
Producer.Produce("orders", new Message<Null, string> {Value = messageSerialized});
}
}
As you can see, the consumer only reads the message from kafka topic and deserialize the message into a MediatR INotification object and then publish to the handler
the handler works with databases transactions, redis cache read/write, and push notifications
an example of my handler:
public override async Task Handle(OrderReceivedIntegrationEvent notification, CancellationToken cancellationToken)
{
try
{
// Get order from database
var order = await _orderRepository.GetOrderByIdAsync(notification.OrderId.ToString());
order.EditOrder(default, notification.Price);
order.ChangeOrderStatus(notification.Status, notification.RejectReason);
// commit the transaction
if (await _uow.Commit())
{
var cacheModificationRequest = _mapper.Map<CacheOrdersModificationRequestedIntegrationEvent>(order);
// send mediatr notification to change cache information in Redis
await _bus.Publish(cacheModificationRequest, cancellationToken);
}
}
catch (Exception e)
{
_logger.LogInformation($"Error {e.Message}");
}
}
but when i run a load test with 2000 requests with a ramp up of 15 seconds, the consumer starts to slow, something like 2 ~ 5 minutes to consume all the 2000 requests.
I was wondering if i remove the MediatR layer and start handling the process in the Consumer class it will improove the performance
or if there is some Kafka configuration that improove the throughput, something like remove the Acks of the In Sync topic replicas, or commit the offset after a bunch of time.
First i have implemented the kafka using the MassTransit library, and after there a find this slow consuming rate, i tried to change the library to the Confluet.Kafka just to remove the MassTransit abstraction layer if it will have a improovement, but still the same:
<PackageReference Include="Confluent.Kafka" Version="1.7.0" />
Anyone already faced the same problem can help me?
OBS: My Kafka are running in Cluster with 3 brokers in Kubernetes, and the topics each one has 6 partitions with 3 replication factor

Related

Different threads using same instance of DbContext, called from within DelegatingHandler

I have .Net Core 3.1 application that is using EF Core 3.1.9. During a specific process I am getting the following error:
A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913
I am using Dependency Injection for the DbContext and have gone through all the flows to make sure everything is properly and immediately await'ed.
The error occurs within LtiUserRepository.cs which will be shown below.
That process starts with an external http call using an HttpClient that has a custom MessageHandler, registered in Startup.cs:
services.AddHttpClient<MyRepository>("MyCustomUserClient", client =>
{
var canvasUrl = Configuration.GetSection("Urls:Removed").Value ?? "https://example.com/";
client.BaseAddress = new System.Uri(removed);
}).AddHttpMessageHandler<LtiUserApiAuthenticationHttpClientHandler>();
The code that initiates the HTTP Call is:
public async Task<PlatformQuizSubmissions> GetUserQuiz(string courseId, string quizId)
{
var path = $"api/v1/courses/{courseId}/quizzes/{quizId}/submission";
var response = await _myCustomUserClient.GetAsync(path);
// some stuff
var responseContent = await response.Content.ReadAsStringAsync();
// Some other stuff
}
The purpose of the custom MessageHandler is to check for a header, get some data, and append a query parameter to each request
public sealed class LtiUserApiAuthenticationHttpClientHandler : DelegatingHandler
{
private readonly IHttpContextAccessor _accessor;
private readonly ILtiUserService _userService;
public LtiUserApiAuthenticationHttpClientHandler(IHttpContextAccessor accessor, ILtiUserService ltiUserService)
{
_accessor = accessor;
_userService = ltiUserService;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var obo = _accessor.HttpContext.Request.Headers["QT-OBO"];
// THIS IS THE PART THAT QUERIES THE DATABASE
var user = await _userService.Get(new Guid(obo));
var uriBuilder = new UriBuilder(request.RequestUri);
if (string.IsNullOrEmpty(uriBuilder.Query))
{
uriBuilder.Query = $"as_user_id={user.PlatformUserId}";
}
else
{
uriBuilder.Query = $"{uriBuilder.Query}&as_user_id={user.PlatformUserId}";
}
request.RequestUri = uriBuilder.Uri;
return await base.SendAsync(request, cancellationToken);
}
}
You can see above that the MessageHandler calls _userservice.Get, which is this:
public async Task<LtiUser> Get(Guid guid)
{
return await _ltiUserRepository.Get(guid);
}
That simply returns from the repository, which is this:
public class LtiUserRepository : ILtiUserRepository
{
private readonly SqlDbContext _db;
private readonly IMapper _mapper;
private readonly ILogger<LtiUserRepository> _logger;
public LtiUserRepository(SqlDbContext sqlDbContext, IMapper mapper, ILoggerFactory logger)
{
_db = sqlDbContext;
_mapper = mapper;
_logger = logger != null ? logger.CreateLogger<LtiUserRepository>() : throw new ArgumentNullException(nameof(logger));
}
public async Task<LtiUser> Get(Guid guid)
{
try
{
return await _db.LtiUsers
.AsNoTracking()
.Where(l => l.UUID == guid)
.ProjectTo<LtiUser>(_mapper.ConfigurationProvider)
.SingleOrDefaultAsync();
}
catch (Exception ex)
{
// This is where the error is caught.
_logger.LogCritical($"Could not get LtiUser via (UUID) {guid} : {ex.Message}");
return null;
}
}
}
The database is registered in Startup.cs with:
protected virtual void ConfigureDatabaseServices(IServiceCollection services)
{
services.AddDbContext<SqlDbContext>(
o => o.UseSqlServer(Configuration.GetConnectionString("DbConnectionString")),
ServiceLifetime.Transient);
}
When I hit this endpoint using ApacheBench with 20 requests, concurrency of 2 I get this error anywhere from 2 to 10 times. However, looking at the following snippet from the MessageHandler (LtiUserApiAuthenticationHttpClientHandler) again:
var user = await _userService.Get(new Guid(obo));
if (string.IsNullOrEmpty(uriBuilder.Query))
{
uriBuilder.Query = $"as_user_id={user.PlatformUserId}";
}
else
{
uriBuilder.Query = $"{uriBuilder.Query}&as_user_id={user.PlatformUserId}";
}
If I replace user.PlatformUserId with a hardcoded, known value, (and comment out the call to _userService.Get) I can use AB with 1000 requests and a concurrency of 20 and have 0 occurrences of the issue. That leads me to believe I have it narrowed down to the offending flow, but am not sure of the correct way to do this.

How to make a Blazor page update the content of one html tag with incoming data from gRPC service

So i'm testing with Blazor and gRPC and my dificulty at the moment is on how to pass the content of a variable that is on a class, specifically the gRPC GreeterService Class to the Blazor page when new information arrives. Notice that my aplication is a client and a server, and i make an initial comunication for the server and then the server starts to send to the client data(numbers) in unary mode, every time it has new data to send. I have all this working, but now i'm left it that final implementation.
This is my Blazor page
#page "/greeter"
#inject GrpcService1.GreeterService GreeterService1
#using BlazorApp1.Data
<h1>Grpc Connection</h1>
<input type="text" #bind="#myID" />
<button #onclick="#SayHello">SayHello</button>
<p>#Greetmsg</p>
<p></p>
#code {
string Name;
string Greetmsg;
async Task SayHello()
{
this.Greetmsg = await this.GreeterService1.SayHello(this.myID);
}
}
The method that later receives the communication from the server if the hello is accepted there is something like this:
public override async Task<RequestResponse> GiveNumbers(BalconyFullUpdate request, ServerCallContext context)
{
RequestResponse resp = new RequestResponse { RequestAccepted = false };
if (request.Token == publicAuthToken)
{
number = request.Number;
resp = true;
}
return await Task.FromResult(resp);
}
Every time that a new number arrives i want to show it in the UI.
Another way i could do this was, within a while condition, i could do a call to the server requesting a new number just like the SayHello request, that simply awaits for a server response, that only will come when he has a new number to send. When it comes the UI is updated. I'm just reluctant to do it this way because i'm afraid that for some reason the client request is forgotten and the client just sit's there waiting for a response that will never come. I know that i could implement a timeout on the client side to handle that, and on the server maybe i could pause the response, with a thread pause or something like that, and when the method that generates the new number has a new number, it could unpause the response to the client(no clue on how to do that). This last solution looks to me much more difficult to do than the first one.
What are your thoughts about it? And solutions..
##################### UPDATE ##########################
Now i'm trying to use a singleton, grab its instance in the Blazor page, and subcribe to a inner event of his.
This is the singleton:
public class ThreadSafeSingletonString
{
private static ThreadSafeSingletonString _instance;
private static readonly object _padlock = new object();
private ThreadSafeSingletonString()
{
}
public static ThreadSafeSingletonString Instance
{
get
{
if (_instance == null)
{
lock(_padlock)
{
if (_instance == null)
{
_instance = new ThreadSafeSingletonString();
_instance.number="";
}
}
}
return _instance;
}
set
{
_instance.number= value.number;
_instance.NotifyDataChanged();
}
}
public int number{ get; set; }
public event Action OnChange;
private void NotifyDataChanged() => OnChange?.Invoke();
And in Blazor page in code section i have:
protected override void OnInitialized()
{
threadSafeSingleton.OnChange += updateNumber();
}
public System.Action updateNumber()
{
this.fromrefresh = threadSafeSingleton.number + " que vem.";
Console.WriteLine("Passou pelo UpdateNumber");
this.StateHasChanged();
return StateHasChanged;
}
Unfortunatly the updatenumber function never gets executed...
To force a refresh of the ui you can call the StateHasChanged() method on your component:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.componentbase.statehaschanged?view=aspnetcore-3.1
Notifies the component that its state has changed. When applicable, this will cause the component to be re-rendered.
Hope this helps
Simple Request
After fully understanding that your problem is just to Update the Page not to get unsyncronous messages from the server with a bi directional connection. So jou just have to change your page like (please not there is no need to change the files generated by gRPC, I called it Number.proto so my service is named NumberService):
async Task SayHello()
{
//Request via gRPC
var channel = new Channel(Host + ":" + Port, ChannelCredentials.Insecure);
var client = new this.NumberService.NumberServiceClient(channel);
var request = new Number{
identification = "ABC"
};
var result = await client.SendNumber(request).RequestAccepted;
await channel.ShutdownAsync();
//Update page
this.Greetmsg = result;
InvokeAsync(StateHasChanged);//Required to refresh page
}
Bi Directional
For making a continious bi directional connection you need to change the proto file to use streams like:
service ChatService {
rpc chat(stream ChatMessage) returns (stream ChatMessageFromServer);
}
This Chant sample is from the https://github.com/meteatamel/grpc-samples-dotnet
The main challenge on this is do divide the task waiting for the gRPC server from the client. I found out that BackgroundService is good for this. So create a Service inherited from BackgroundService where place the while loop waiting for the server in the ExecuteAsyncmethod. Also define a Action callback to update the page (alternative you can use an event)
public class MyChatService : BackgroundService
{
Random _random = new Random();
public Action<int> Callback { get; set; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// Replace next lines with the code request and wait for server...
using (_call = _chatService.chat())
{
// Read messages from the response stream
while (await _call.ResponseStream.MoveNext(CancellationToken.None))
{
var serverMessage = _call.ResponseStream.Current;
var otherClientMessage = serverMessage.Message;
var displayMessage = string.Format("{0}:{1}{2}", otherClientMessage.From, otherClientMessage.Message, Environment.NewLine);
if (Callback != null) Callback(displayMessage);
}
// Format and display the message
}
}
}
}
On the page init and the BackgroundService and set the callback:
#page "/greeter"
#using System.Threading
<p>Current Number: #currentNumber</p>
#code {
int currentNumber = 0;
MyChatService myChatService;
protected override async Task OnInitializedAsync()
{
myChatService = new MyChatService();
myChatService.Callback = i =>
{
currentNumber = i;
InvokeAsync(StateHasChanged);
};
await myChatService.StartAsync(new CancellationToken());
}
}
More information on BackgroundService in .net core can be found here: https://gunnarpeipman.com/dotnet-core-worker-service/

Publish on .NET Core 2.2 SignalR Hub as EXE not working on Self-Host but works in Visual Studio

I have a Hub that works inside Visual Studio Community 2017 for ASP.Net Core & SignalR. Everything works beautifully as long as it runs under VS. I read what is available & am not getting any luck. I have a HostedService that on StartAsync kicks off a thread with the Background prop set to true. This thread reads from a socket & then calls SendMessage on the Hub. I'm not sure what I'm doing wrong. It publishes an exe, but it is not working.
I have read all that I can find. I added a Hosted Service that is added under Startup.
// STARTUP
public class cRioHubStartup {
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
services.AddMvc();
services.AddHostedService<cRioHubHostService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
string virtDir = cRioHubGlobals.getHubUrl().VirtualDirectory;
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
//app.UseHsts();
}
app.UseDefaultFiles();
app.UseStaticFiles();
//app.UseCors(CorsOptions.AllowAll);
app.UseSignalR(routes =>
{
routes.MapHub<cRioHub>(virtDir);
});
app.UseMvc();
app.Run(async (context) =>
{
await context.Response.WriteAsync("cRioHub Started!");
});
}
/*
var hubContext = provider.GetService<IHubContext<cRioHub>>();
services.AddSingleton(provider =>
{
var hubContext = provider.GetService<IHubContext<cRioHub>>();
var update = new Update(hubContext);
return update;
});
*/
}
// HUB HOSTED SERVICE which kicks off background thread
public class cRioHubHostService : IHostedService, IDisposable
{
private static Thread _t = null;
public Thread thread
{
get { return _t; }
}
// Summary: Triggered when the application host is ready to start the service.
public Task StartAsync(CancellationToken cancellationToken)
{
_t = LaunchHub();
return Task.CompletedTask;
}
// Summary: Triggered when the application host is performing a graceful shutdown.
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public void Dispose()
{
_t = null;
}
public static Thread LaunchHub()
{
Object orfu = new object(); // saved for fut. use if needed
// set up hub
cRioHubConnection hub = new cRioHubConnection(cRioHubGlobals.getHubUrl(), cRioHubGlobals.getHubUrl().Name);
cRioHubGlobals.setHubConnection(new cRioHubConnection(hub));
//Globals.HubCnxn.SendMessage("Take2!");
// call thread to start TCP client wh. writes back to the hub on cRio msg. arrival
Thread t = new Thread(cRioHubTcpClient.cRioRunClient);
t.IsBackground = true;
t.Start(orfu);
return t;
}
public static void cRioRunClient(Object orfu)
{
string consMsg = "";
string urlHub = cRioHubGlobals.getHubUrl().makeUrl();
string urlCRio = cRioHubGlobals.getCRioUrl().makeUrl();
string fmtHubUrl = "Hub URL={0}" ;
string fmtCRioUrl = "cRio URL={0}";
consMsg = String.Format(fmtHubUrl, urlHub);
Console.WriteLine(consMsg);
consMsg = String.Format(fmtCRioUrl, urlCRio);
Console.WriteLine(consMsg);
cRioHubGlobals.setCRioTcpClient(new cRioHubTcpClient(orfu)); // gets its connection info from cRioHubGlobals
cRioHubGlobals.getCRioTcpClient().Message += (s, a) => Console.WriteLine("Client: " + a.Message);
Task clientTask = cRioHubGlobals.getCRioTcpClient().RunAsync();
Console.WriteLine("Program: Hit any char to stop.");
ConsoleEx.ReadChar();
cRioHubGlobals.getCRioTcpClient().Stop = true;
cRioHubGlobals.getCRioTcpClient().Dispose();
clientTask = null;
}
public static Task cRioStopClient()
{
Task tskHub = null;
cRioHubTcpClient client = cRioHubGlobals.getCRioTcpClient();
if (client != null)
{
client.Stop = true;
client.Dispose();
tskHub = cRioHubGlobals.getHubConnection().Stop();
}
Console.WriteLine("Stopping service!");
return tskHub;
}
The problem is the publisher with the appsettings & launch. If you choose a port other than the default 5000, it is not working. If you choose 5000, it works. This appears to be a bug.

Hot to get count of connection in SignalR Core Group

How can I get count of connections in specific SignalR Core group?
Something like that:
this.Clients.Group("Something").Count();
It would be also good if SendAsync would return how many clients send message:
var count = await this.Clients.Client(this.Context.ConnectionId).SendAsync("msg", msg);
I need this to determine if group is empty. Data should be correct if user disconnects from SignalR Core with force (unplug net cable)...
You can use the OnConnected and OnDisconnected Events and save the connection / connectionIds.
I use something like this with additional informations:
internal static ConcurrentDictionary<string, ConnectionInfos> Users = new ConcurrentDictionary<string, ConnectionInfos>();
public override async Task OnConnectedAsync()
{
var connId = Context.ConnectionId;
if (!Users.Keys.Any(x => x == connId))
{
Users.TryAdd(connId, new ConnectionInfos { /*...*/});
}
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
var id = Context.ConnectionId;
if (Users.TryRemove(id, out ConnectionInfos ci))
{
//Connection removed
}
await base.OnDisconnectedAsync(exception);
}

Jersey2 Client reuse not working AsyncInvoker

I am trying to reuse a Jersey2(Jersey 2.16) Client for async invocation. However after 2 requests, I see that the threads going into a waiting state, waiting on a lock. Since client creation is an expensive operation, I am trying to reuse the client in the async calls. The issue occurs only with ApacheConnectorProvider as the connector class. I want to use ApacheConnectorProvider, as I need to use a proxy and set SSL properties and I want to use PoolingHttpClientConnectionManager.
The sample code is given below:
public class Example {
Integer eventId = 0;
private ClientConfig getClientConfig()
{
ClientConfig clientConfig = new ClientConfig();
ApacheConnectorProvider provider = new ApacheConnectorProvider();
clientConfig.property(ClientProperties.REQUEST_ENTITY_PROCESSING,RequestEntityProcessing.BUFFERED);
clientConfig.connectorProvider(provider);
return clientConfig;
}
private Client createClient()
{
Client client = ClientBuilder.newClient(getClientConfig());
return client;
}
public void testAsyncCall()
{
Client client = createClient();
System.out.println("Testing a new Async call on thread " + Thread.currentThread().getId());
JSONObject jsonObject = new JSONObject();
jsonObject.put("value", eventId++);
invoker(client, "http://requestb.in/nn0sffnn" , jsonObject);
invoker(client, "http://requestb.in/nn0sffnn" , jsonObject);
invoker(client, "http://requestb.in/nn0sffnn" , jsonObject);
client.close();
}
private void invoker(Client client, String URI, JSONObject jsonObject)
{
final Future<Response> responseFuture = client.target(URI)
.request()
.async()
.post(Entity.entity(jsonObject.toJSONString(), MediaType.TEXT_PLAIN));
try {
Response r = responseFuture.get();
System.out.println("Response is on URI " + URI + " : " + r.getStatus());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String args[])
{
Example client1 = new Example();
client1.testAsyncCall();
return;
}
}
The response I see is:
Testing a new Async call on thread 1
Response is on URI http://requestb.in/nn0sffnn : 200
Response is on URI http://requestb.in/nn0sffnn : 200
On looking at the thread stack, I see the following trace:
"jersey-client-async-executor-0" prio=6 tid=0x043a4c00 nid=0x56f0 waiting on condition [0x03e5f000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x238ee148> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
at org.apache.http.pool.PoolEntryFuture.await(PoolEntryFuture.java:133)
at org.apache.http.pool.AbstractConnPool.getPoolEntryBlocking(AbstractConnPool.java:282)
at org.apache.http.pool.AbstractConnPool.access$000(AbstractConnPool.java:64)
at org.apache.http.pool.AbstractConnPool$2.getPoolEntry(AbstractConnPool.java:177)
at org.apache.http.pool.AbstractConnPool$2.getPoolEntry(AbstractConnPool.java:170)
Can someone give me a suggestion as to how to reuse Client objects for async requests and may be how to get over this issue as well.

Resources