2024-11-23 04:38:09 +00:00
|
|
|
using BotSharp.Abstraction.Infrastructures.Enums;
|
2024-11-08 18:10:15 +00:00
|
|
|
using StackExchange.Redis;
|
|
|
|
|
|
|
|
|
|
namespace BotSharp.Core.Infrastructures.Events;
|
|
|
|
|
|
|
|
|
|
public class RedisSubscriber : IEventSubscriber
|
|
|
|
|
{
|
|
|
|
|
private readonly IConnectionMultiplexer _redis;
|
|
|
|
|
private readonly ISubscriber _subscriber;
|
|
|
|
|
private readonly ILogger _logger;
|
|
|
|
|
|
|
|
|
|
public RedisSubscriber(IConnectionMultiplexer redis, ILogger<RedisSubscriber> logger)
|
|
|
|
|
{
|
|
|
|
|
_redis = redis;
|
|
|
|
|
_logger = logger;
|
|
|
|
|
_subscriber = _redis.GetSubscriber();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task SubscribeAsync(string channel, Func<string, string, Task> received)
|
|
|
|
|
{
|
|
|
|
|
await _subscriber.SubscribeAsync(channel, async (ch, message) =>
|
|
|
|
|
{
|
|
|
|
|
_logger.LogInformation($"Received event from channel: {ch} message: {message}");
|
|
|
|
|
await received(ch, message);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-01 23:06:21 +00:00
|
|
|
public async Task SubscribeAsync(string channel, string group, int? port, bool priorityEnabled,
|
2024-11-29 23:12:44 +00:00
|
|
|
Func<string, string, Task> received,
|
|
|
|
|
CancellationToken? stoppingToken = null)
|
2024-11-08 18:10:15 +00:00
|
|
|
{
|
|
|
|
|
var db = _redis.GetDatabase();
|
|
|
|
|
|
2024-11-23 14:48:22 +00:00
|
|
|
if (priorityEnabled)
|
|
|
|
|
{
|
|
|
|
|
await CreateConsumerGroup(db, $"{channel}-{EventPriority.Low}", group);
|
|
|
|
|
await CreateConsumerGroup(db, $"{channel}-{EventPriority.Medium}", group);
|
|
|
|
|
await CreateConsumerGroup(db, $"{channel}-{EventPriority.High}", group);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
await CreateConsumerGroup(db, channel, group);
|
|
|
|
|
}
|
2024-11-23 04:38:09 +00:00
|
|
|
|
2024-12-27 18:16:23 +00:00
|
|
|
await CreateConsumerGroup(db, $"{channel}-Error", group);
|
|
|
|
|
|
2024-12-01 23:06:21 +00:00
|
|
|
var consumer = Environment.MachineName;
|
|
|
|
|
if (port.HasValue)
|
|
|
|
|
{
|
|
|
|
|
consumer += $"-{port}";
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-23 04:38:09 +00:00
|
|
|
while (true)
|
|
|
|
|
{
|
2024-11-24 15:10:03 +00:00
|
|
|
await Task.Delay(100);
|
|
|
|
|
|
2024-11-29 23:12:44 +00:00
|
|
|
if (stoppingToken.HasValue && stoppingToken.Value.IsCancellationRequested)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogInformation($"Stopping consumer channel & group: [{channel}, {group}]");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-15 16:27:37 +00:00
|
|
|
try
|
2024-11-23 04:38:09 +00:00
|
|
|
{
|
2024-12-15 16:27:37 +00:00
|
|
|
if (priorityEnabled)
|
2024-11-23 14:48:22 +00:00
|
|
|
{
|
2024-12-15 16:27:37 +00:00
|
|
|
if (await HandleGroupMessage(db, $"{channel}-{EventPriority.High}", group, consumer, received) > 0)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (await HandleGroupMessage(db, $"{channel}-{EventPriority.Medium}", group, consumer, received) > 0)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2024-11-24 02:02:43 +00:00
|
|
|
|
2024-12-15 16:27:37 +00:00
|
|
|
await HandleGroupMessage(db, $"{channel}-{EventPriority.Low}", group, consumer, received);
|
|
|
|
|
}
|
|
|
|
|
else
|
2024-11-23 14:48:22 +00:00
|
|
|
{
|
2024-12-15 16:27:37 +00:00
|
|
|
await HandleGroupMessage(db, channel, group, consumer, received);
|
2024-11-23 14:48:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
2024-12-15 16:27:37 +00:00
|
|
|
catch (Exception ex)
|
2024-11-23 04:38:09 +00:00
|
|
|
{
|
2024-12-15 16:27:37 +00:00
|
|
|
_logger.LogError($"Error processing message: {ex.Message}\r\n{ex}");
|
|
|
|
|
await Task.Delay(1000 * 60);
|
2024-11-23 04:38:09 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-01 23:06:21 +00:00
|
|
|
private async Task<int> HandleGroupMessage(IDatabase db, string channel, string group, string consumer, Func<string, string, Task> received)
|
2024-11-23 04:38:09 +00:00
|
|
|
{
|
2024-12-01 23:06:21 +00:00
|
|
|
var entries = await db.StreamReadGroupAsync(channel, group, consumer, count: 1);
|
2024-11-23 04:38:09 +00:00
|
|
|
foreach (var entry in entries)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogInformation($"Consumer {Environment.MachineName} received: {channel} {entry.Values[0].Value}");
|
|
|
|
|
await db.StreamAcknowledgeAsync(channel, group, entry.Id);
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await received(channel, entry.Values[0].Value);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2024-12-27 18:16:23 +00:00
|
|
|
_logger.LogError($"Error processing message: {ex.Message}, event id: {channel} {entry.Id} {entry.Values[0].Value}");
|
|
|
|
|
|
|
|
|
|
// Add a message to the Error stream, keeping only the latest 1 million messages
|
|
|
|
|
await db.StreamAddAsync($"{channel}-Error",
|
|
|
|
|
RedisPublisher.AssembleErrorMessage(entry.Values[0].Value, ex.Message),
|
|
|
|
|
messageId: entry.Id,
|
|
|
|
|
maxLength: 1000 * 10000);
|
|
|
|
|
|
2024-12-21 15:41:15 +00:00
|
|
|
// Slow down the consumer if there are errors
|
|
|
|
|
await Task.Delay(1000 * 10);
|
2024-11-23 04:38:09 +00:00
|
|
|
}
|
2024-12-27 18:16:23 +00:00
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
await db.StreamDeleteAsync(channel, [entry.Id]);
|
|
|
|
|
}
|
2024-11-23 04:38:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return entries.Length;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task CreateConsumerGroup(IDatabase db, string channel, string group)
|
|
|
|
|
{
|
2024-11-08 18:10:15 +00:00
|
|
|
// Create the consumer group if it doesn't exist
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await db.StreamCreateConsumerGroupAsync(channel, group, StreamPosition.NewMessages, createStream: true);
|
|
|
|
|
}
|
|
|
|
|
catch (RedisServerException ex) when (ex.Message.Contains("BUSYGROUP"))
|
|
|
|
|
{
|
|
|
|
|
// Group already exists, ignore the error
|
|
|
|
|
_logger.LogWarning($"Consumer group '{group}' already exists (caught exception).");
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogError($"Error creating consumer group: '{group}' {ex.Message}");
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|