BotSharp/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs

189 lines
6.7 KiB
C#
Raw Normal View History

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 RedisPublisher : IEventPublisher
{
private readonly IConnectionMultiplexer _redis;
private readonly ISubscriber _subscriber;
private readonly ILogger _logger;
public RedisPublisher(IConnectionMultiplexer redis, ILogger<RedisPublisher> logger)
{
_redis = redis;
_logger = logger;
_subscriber = _redis.GetSubscriber();
}
public async Task BroadcastAsync(string channel, string message)
{
await _subscriber.PublishAsync(channel, message);
}
2024-11-26 05:56:10 +00:00
public async Task<string?> PublishAsync(string channel, string message, EventPriority? priority = null)
2024-11-08 18:10:15 +00:00
{
var db = _redis.GetDatabase();
2024-11-21 04:25:17 +00:00
2024-11-23 04:38:09 +00:00
// convert to apporiate channel by priority
2024-11-23 14:48:22 +00:00
if (priority != null)
{
channel = $"{channel}-{priority}";
}
2024-11-23 04:38:09 +00:00
2024-11-21 04:25:17 +00:00
if (CheckMessageExists(db, channel, "message", message))
{
2025-01-24 21:50:28 +00:00
_logger.LogInformation($"The message already exists {channel} {message}");
2024-11-26 05:56:10 +00:00
return null;
2024-11-21 04:25:17 +00:00
}
2024-11-08 18:10:15 +00:00
// Add a message to the stream, keeping only the latest 1 million messages
2024-11-21 04:25:17 +00:00
var messageId = await db.StreamAddAsync(channel,
AssembleMessage(message),
2024-11-08 18:10:15 +00:00
maxLength: 1000 * 10000);
2024-11-21 04:25:17 +00:00
_logger.LogInformation($"Published message {channel} {message} ({messageId})");
2024-11-26 05:56:10 +00:00
return messageId;
2024-11-21 04:25:17 +00:00
}
2024-11-23 04:38:09 +00:00
private bool CheckMessageExists(IDatabase db, string channel, string fieldName, string desiredValue)
2024-11-21 04:25:17 +00:00
{
// Define the range to fetch all messages
RedisValue start = "-"; // Start from the smallest ID
RedisValue end = "+"; // End at the largest ID
int count = 10; // Number of messages to retrieve
// Fetch the latest 10 messages
2024-11-23 04:38:09 +00:00
var streamEntries = db.StreamRange(channel, start, end, count, Order.Descending);
2024-11-21 04:25:17 +00:00
if (streamEntries.Length == 0)
{
return false;
}
// Check if any message contains the specific field value
bool exists = streamEntries.Any(entry =>
{
// Each entry contains a collection of Name-Value pairs
foreach (var nameValue in entry.Values)
{
if (nameValue.Name == fieldName && nameValue.Value == desiredValue)
{
return true;
}
}
return false;
});
return exists;
2024-11-08 18:10:15 +00:00
}
2024-11-11 04:13:50 +00:00
2024-12-27 18:16:23 +00:00
public static NameValueEntry[] AssembleMessage(RedisValue message)
{
return
[
new NameValueEntry("message", message),
2025-01-24 21:50:28 +00:00
new NameValueEntry("timestamp", DateTime.UtcNow.ToString("o")),
new NameValueEntry("machine", Environment.MachineName)
];
}
2024-12-27 18:16:23 +00:00
public static NameValueEntry[] AssembleErrorMessage(RedisValue message, string error)
{
return
[
new NameValueEntry("message", message),
new NameValueEntry("timestamp", DateTime.UtcNow.ToString("o")),
2025-01-24 21:50:28 +00:00
new NameValueEntry("machine", Environment.MachineName),
2024-12-27 18:16:23 +00:00
new NameValueEntry("error", error)
];
}
2024-11-11 04:13:50 +00:00
public async Task ReDispatchAsync(string channel, int count = 10, string order = "asc")
{
var db = _redis.GetDatabase();
var entries = await db.StreamRangeAsync(channel, "-", "+", count: count, messageOrder: order == "asc" ? Order.Ascending : Order.Descending);
foreach (var entry in entries)
{
_logger.LogInformation($"Fetched message: {channel} {entry.Values[0].Value} ({entry.Id})");
try
{
var message = entry.Values.First(x => x.Name == "message").Value;
var messageId = await db.StreamAddAsync(channel,
2024-12-17 06:37:18 +00:00
AssembleMessage(message),
maxLength: 1000 * 10000);
2024-11-11 04:13:50 +00:00
_logger.LogWarning($"ReDispatched message: {channel} {entry.Values[0].Value} ({messageId})");
// Optionally delete the message to save space
await db.StreamDeleteAsync(channel, [entry.Id]);
}
catch (Exception ex)
{
2024-11-18 22:31:18 +00:00
_logger.LogError($"Error processing message: {ex.Message}, event id: {channel} {entry.Id}\r\n{ex}");
2024-11-11 04:13:50 +00:00
}
}
}
2024-11-21 04:25:17 +00:00
public async Task ReDispatchPendingAsync(string channel, string group, int count = 10)
{
var db = _redis.GetDatabase();
// Step 1: Fetch pending messages using XPending
try
{
var pendingInfo = await db.StreamPendingAsync(channel, group);
if (pendingInfo.PendingMessageCount == 0)
{
Console.WriteLine("No pending messages found.");
return;
}
// Step 2: Fetch pending message IDs
List<StreamEntry> pendingMessages = new List<StreamEntry>();
foreach (var consumer in pendingInfo.Consumers)
{
var pendingMessageIds = await db.StreamPendingMessagesAsync(channel, group, count: consumer.PendingMessageCount, consumerName: consumer.Name);
var messageIds = pendingMessageIds.Select(x => x.MessageId).ToArray();
// Step 3: Use XClaim to fetch the actual message
var claimedMessages = await db.StreamClaimAsync(channel, group, consumer.Name, minIdleTimeInMs: 60 * 1000, messageIds);
pendingMessages.AddRange(claimedMessages);
await db.StreamAcknowledgeAsync(channel, group, messageIds);
}
// Step 4: Process the messages
foreach (var message in pendingMessages)
{
/*if (message.IsNull)
{
await db.StreamAcknowledgeAsync(channel, group, [message.Id]);
}
else
{
var messageId = await db.StreamAddAsync(channel, "message", message.Values[0].Value);
_logger.LogWarning($"ReDispatched message: {channel} {message.Values[0].Value} ({messageId})");
await db.StreamDeleteAsync(channel, [messageId]);
}*/
}
}
catch (RedisException ex)
{
Console.WriteLine($"Redis error: {ex.Message}");
}
}
2024-11-30 15:16:04 +00:00
public async Task RemoveAsync(string channel, int count = 10)
{
var db = _redis.GetDatabase();
var entries = await db.StreamRangeAsync(channel, "-", "+", count: count, messageOrder: Order.Ascending);
2024-12-31 17:07:38 +00:00
var deletedCount = await db.StreamDeleteAsync(channel, entries.Select(x => x.Id).ToArray());
_logger.LogWarning($"Deleted {deletedCount} messages from Redis stream {channel}");
2024-11-30 15:16:04 +00:00
}
2024-11-08 18:10:15 +00:00
}