简介
1. 引言
在现代应用开发中,队列系统是处理异步任务、解耦系统组件的关键工具。本文将介绍一个基于 C# 实现的通用队列处理系统,支持内存队列和 Redis 队列两种模式,适用于各种后台任务处理场景。
2. 系统架构
2.1 核心组件
本项目包含以下核心组件:
组件 职责 文件 任务模型 定义任务结构和属性 GenericQueueTask.cs 、 GenericRedisQueueTask.cs 队列处理器 管理任务调度和执行 GenericQueueProcessor.cs 、 RedisQueueProcessor.cs Redis 客户端 封装 Redis 操作 RedisQueueClient.cs 示例代码 展示系统使用方法 Program.cs
2.2 设计理念
系统设计遵循以下核心原则:
- 解耦 :业务逻辑与队列系统分离
- 可靠性 :支持重试、死信队列、分布式锁
- 扩展性 :支持多种任务类型和执行逻辑
- 监控 :提供详细的统计信息和日志
4 队列处理器
内存队列处理流程
- 任务入队 :将任务添加到 ConcurrentQueue
- 多线程消费 :启动多个线程并行处理任务
- 执行任务 :调用任务的 ExecuteAsync 方法
- 异常处理 :捕获执行异常,判断是否需要重试
- 死信处理 :超过重试次数的任务触发死信事件
Redis 队列处理流程
- 任务入队 :将任务序列化后存入 Redis 列表
- 阻塞出队 :使用 BRPOP 命令阻塞获取任务
- 分布式锁 :使用 Redis 实现分布式锁,避免重复消费
- 执行任务 :根据任务类型调用对应的执行器
- 死信处理 :失败任务移入 Redis 死信队列
代码:
测试代码
csharp
internal class Program
{
static async Task Main(string[] args)
{
#region 通用内存队列消费示例
//// ========== 1. 初始化通用队列 ==========
//var queueProcessor = new GenericQueueProcessor(2); // 2个消费线程
//// 订阅死信事件(业务侧处理失败的死信任务)
//queueProcessor.OnTaskDeadLetter += (sender, e) =>
//{
// Console.WriteLine($"[业务-死信处理] 任务{e.Task.TaskId}失败:{e.Exception.Message}");
// // 业务侧逻辑:写入死信表、发送告警、人工排查等
//};
//var httpTask = new GenericQueueTask
//{
// TaskName = "调用第三方支付API",
// MaxRetryCount = 3,
// // 业务封装:HTTP请求 + Polly重试+熔断(队列完全不感知)
// ExecuteAsync = async () =>
// {
// // 业务自定义HTTP重试策略
// AsyncRetryPolicy httpRetryPolicy = Policy
// .Handle<HttpRequestException>()
// .Or<TaskCanceledException>()
// .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
// // 业务自定义熔断策略
// var circuitBreaker = Policy
// .Handle<HttpRequestException>()
// .CircuitBreakerAsync(3, TimeSpan.FromSeconds(5));
// // 组合策略执行HTTP请求
// await Policy.WrapAsync(httpRetryPolicy, circuitBreaker).ExecuteAsync(async () =>
// {
// using (var httpClient = new HttpClient())
// {
// var response = await httpClient.PostAsync("https://api.pay.com/notify", new StringContent("{}"));
// response.EnsureSuccessStatusCode();
// Console.WriteLine("[业务] HTTP请求执行完成");
// }
// });
// }
//};
//queueProcessor.Enqueue(httpTask);
#endregion
#region 通用redis队列消费示例
// ========== 1. 初始化Redis队列 ==========
// Redis连接字符串(替换为你的实际地址)
string redisConnStr = "10.10.170.88:63793";
// 初始化Redis队列客户端(队列名称:order_task_queue)
var redisClient = new RedisQueueClient(redisConnStr, "order_task_queue");
// 初始化队列处理器(2个消费线程)
var queueProcessor = new RedisQueueProcessor(redisClient, 2);
// 订阅死信事件(业务侧处理失败任务)
queueProcessor.OnTaskDeadLetter += (sender, e) =>
{
Console.WriteLine($"[业务-死信处理] 任务{e.Task.TaskId}失败:{e.Exception.Message}");
// 业务侧逻辑:写入日志、发送告警、人工排查等
};
queueProcessor.RegisterTaskExecutor("PayHttpTask", async (taskParams) =>
{
var param = JsonConvert.DeserializeObject<PayHttpParam>(taskParams);
// 业务自定义HTTP重试策略
var httpRetryPolicy = Policy
.Handle<HttpRequestException>()
.Or<TaskCanceledException>()
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
// 业务自定义熔断策略
var circuitBreaker = Policy
.Handle<HttpRequestException>()
.CircuitBreakerAsync(3, TimeSpan.FromSeconds(5));
// 组合策略执行HTTP请求
await Policy.WrapAsync(httpRetryPolicy, circuitBreaker).ExecuteAsync(async () =>
{
using (var httpClient = new HttpClient())
{
httpClient.Timeout = TimeSpan.FromSeconds(10);
var requestContent = new StringContent(JsonConvert.SerializeObject(new { OrderId = param.OrderId, Amount = param.Amount }));
requestContent.Headers.ContentType.MediaType = "application/json";
var response = await httpClient.PostAsync(param.ApiUrl, requestContent);
response.EnsureSuccessStatusCode();
Console.WriteLine($"[业务] 支付API调用成功:{param.OrderId}");
}
});
});
// ========== 3. 启动队列消费 ==========
queueProcessor.StartConsuming();
// 添加HTTP任务
var httpTask = new GenericRedisQueueTask
{
TaskName = "调用支付API",
TaskType = "PayHttpTask",
MaxRetryCount = 3,
TaskParams = JsonConvert.SerializeObject(new PayHttpParam
{
OrderId = 1001,
Amount = 99.9m,
ApiUrl = "https://jsonplaceholder.typicode.com/posts" // 测试用API
})
};
redisClient.Enqueue(httpTask);
//测试使用
Console.Read();
//测试使用
#endregion
}
}
// 业务参数模型(HTTP任务)
public class PayHttpParam
{
public int OrderId { get; set; }
public decimal Amount { get; set; }
public string ApiUrl { get; set; }
}
内存对列
csharp
/// <summary>
/// 通用队列任务模型(队列仅处理此模型,业务侧封装所有逻辑)
/// </summary>
public class GenericQueueTask
{
/// <summary>
/// 任务唯一标识(队列用于日志/追踪)
/// </summary>
public string TaskId { get; set; } = Guid.NewGuid().ToString("N");
/// <summary>
/// 任务名称(便于日志排查)
/// </summary>
public string TaskName { get; set; }
/// <summary>
/// 业务自定义的执行逻辑(包含所有业务操作+自定义容错:数据库重试/网络重试/熔断等)
/// 队列只负责调用这个方法,不关心内部逻辑
/// </summary>
public Func<Task> ExecuteAsync { get; set; }
/// <summary>
/// 任务元数据(业务侧自定义,队列不解析)
/// </summary>
public string Metadata { get; set; }
/// <summary>
/// 业务侧自定义的最大重试次数(队列仅用于判断是否重新入队,不参与重试逻辑)
/// </summary>
public int MaxRetryCount { get; set; } = 3;
/// <summary>
/// 已重试次数(队列仅记录,不干预)
/// </summary>
public int RetriedCount { get; set; } = 0;
}
csharp
/// <summary>
/// 通用内存队列处理器(只负责任务调度,不关心业务逻辑)
/// 核心职责:入队/出队、多线程消费、异常兜底、重试入队(仅触发,不处理重试逻辑)
/// </summary>
public class GenericQueueProcessor : IDisposable
{
// 线程安全队列(仅存储通用任务模型)
private readonly ConcurrentQueue<GenericQueueTask> _taskQueue = new ConcurrentQueue<GenericQueueTask>();
// 消费停止信号
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
// 消费线程数(可配置)
private readonly int _consumerCount;
// 消费任务列表(用于优雅停止)
private readonly Task[] _consumerTasks;
/// <summary>
/// 队列统计信息(供业务侧监控)
/// </summary>
public QueueStatistics Statistics { get; } = new QueueStatistics();
/// <summary>
/// 初始化通用队列
/// </summary>
/// <param name="consumerCount">消费线程数(默认4,建议根据业务类型调整)</param>
public GenericQueueProcessor(int consumerCount = 4)
{
if (consumerCount < 1)
throw new ArgumentOutOfRangeException(nameof(consumerCount), "消费线程数不能小于1");
_consumerCount = consumerCount;
_consumerTasks = new Task[consumerCount];
}
/// <summary>
/// 业务侧调用:添加任务到队列
/// </summary>
/// <param name="task">通用任务模型(业务侧已封装好执行逻辑+容错)</param>
/// <exception cref="ArgumentNullException"></exception>
public void Enqueue(GenericQueueTask task)
{
if (task == null) throw new ArgumentNullException(nameof(task));
if (task.ExecuteAsync == null) throw new ArgumentNullException(nameof(task.ExecuteAsync), "业务执行逻辑不能为空");
_taskQueue.Enqueue(task);
Statistics.TotalEnqueued++;
Statistics.QueueLength = _taskQueue.Count;
Console.WriteLine($"[队列] 任务{task.TaskId}({task.TaskName})已入队,当前队列长度:{Statistics.QueueLength}");
}
/// <summary>
/// 启动队列消费
/// </summary>
public void StartConsuming()
{
Console.WriteLine($"[队列] 启动{_consumerCount}个消费线程...");
for (int i = 0; i < _consumerCount; i++)
{
int consumerId = i + 1;
_consumerTasks[i] = Task.Run(() => ConsumeLoopAsync(consumerId, _cts.Token), _cts.Token);
}
}
/// <summary>
/// 消费循环(队列核心逻辑:只做调度,不干预业务)
/// </summary>
private async Task ConsumeLoopAsync(int consumerId, CancellationToken token)
{
Console.WriteLine($"[队列-消费者{consumerId}] 已启动");
while (!token.IsCancellationRequested)
{
try
{
// 尝试出队
if (_taskQueue.TryDequeue(out var task))
{
Statistics.QueueLength = _taskQueue.Count;
Statistics.TotalDequeued++;
Console.WriteLine($"[队列-消费者{consumerId}] 开始执行任务{task.TaskId}({task.TaskName})");
// 执行业务自定义逻辑(队列只调用,不关心内部是数据库/HTTP/重试)
await ExecuteBusinessTaskAsync(task, consumerId);
}
else
{
// 队列为空时休眠,降低CPU占用
await Task.Delay(100, token);
}
}
catch (OperationCanceledException)
{
Console.WriteLine($"[队列-消费者{consumerId}] 收到停止信号,退出消费");
break;
}
catch (Exception ex)
{
// 队列层面的异常兜底(避免消费线程崩溃)
Console.WriteLine($"[队列-消费者{consumerId}] 消费循环异常:{ex.Message}");
await Task.Delay(500, token);
}
}
Console.WriteLine($"[队列-消费者{consumerId}] 已退出");
}
/// <summary>
/// 执行业务任务(队列仅做:执行+顶层异常捕获+重试入队判断)
/// </summary>
private async Task ExecuteBusinessTaskAsync(GenericQueueTask task, int consumerId)
{
try
{
// 调用业务自定义的执行逻辑(包含业务操作+自己的重试/熔断)
await task.ExecuteAsync();
// 业务执行成功
Statistics.TotalSucceeded++;
Console.WriteLine($"[队列-消费者{consumerId}] 任务{task.TaskId}执行成功");
}
catch (Exception ex)
{
// 业务执行失败:队列只负责判断是否重新入队,不处理重试逻辑
Statistics.TotalFailed++;
Console.WriteLine($"[队列-消费者{consumerId}] 任务{task.TaskId}执行失败:{ex.Message}");
// 判断是否需要重新入队(仅触发,重试逻辑由业务侧在ExecuteAsync中实现)
if (task.RetriedCount < task.MaxRetryCount)
{
task.RetriedCount++;
_taskQueue.Enqueue(task); // 重新入队
Statistics.TotalRetried++;
Statistics.QueueLength = _taskQueue.Count;
Console.WriteLine($"[队列-消费者{consumerId}] 任务{task.TaskId}已重新入队(已重试{task.RetriedCount}/{task.MaxRetryCount}次)");
}
else
{
// 达到最大重试次数,标记为死信(队列仅记录,后续处理由业务侧决定)
Statistics.TotalDeadLetter++;
Console.WriteLine($"[队列-消费者{consumerId}] 任务{task.TaskId}达到最大重试次数,标记为死信");
// 触发死信事件,由业务侧处理(比如写入死信表、告警)
OnTaskDeadLetter?.Invoke(this, new DeadLetterEventArgs(task, ex));
}
}
}
/// <summary>
/// 优雅停止队列消费(等待当前任务执行完成)
/// </summary>
/// <param name="timeout">超时时间</param>
/// <returns></returns>
public bool StopConsuming(TimeSpan timeout)
{
try
{
_cts.Cancel();
bool allStopped = Task.WaitAll(_consumerTasks, timeout);
Console.WriteLine($"[队列] 消费线程停止{(allStopped ? "成功" : "超时")},当前队列剩余任务数:{_taskQueue.Count}");
return allStopped;
}
catch (AggregateException ex)
{
ex.Handle(e => e is OperationCanceledException);
return true;
}
}
/// <summary>
/// 死信任务事件(业务侧订阅,处理失败的死信任务)
/// </summary>
public event EventHandler<DeadLetterEventArgs> OnTaskDeadLetter;
public void Dispose()
{
_cts.Cancel();
_cts.Dispose();
}
}
/// <summary>
/// 队列统计信息(供业务侧监控)
/// </summary>
public class QueueStatistics
{
/// <summary>
/// 总入队数
/// </summary>
public int TotalEnqueued { get; set; }
/// <summary>
/// 总出队数
/// </summary>
public int TotalDequeued { get; set; }
/// <summary>
/// 总成功数
/// </summary>
public int TotalSucceeded { get; set; }
/// <summary>
/// 总失败数
/// </summary>
public int TotalFailed { get; set; }
/// <summary>
/// 总重试数
/// </summary>
public int TotalRetried { get; set; }
/// <summary>
/// 总死信数
/// </summary>
public int TotalDeadLetter { get; set; }
/// <summary>
/// 当前队列长度
/// </summary>
public int QueueLength { get; set; }
}
/// <summary>
/// 死信事件参数(传递任务和异常信息给业务侧)
/// </summary>
public class DeadLetterEventArgs : EventArgs
{
public GenericQueueTask Task { get; }
public Exception Exception { get; }
public DeadLetterEventArgs(GenericQueueTask task, Exception exception)
{
Task = task;
Exception = exception;
}
}
redis队列
csharp
/// <summary>
/// Redis队列工具类(封装通用Redis操作)
/// </summary>
public class RedisQueueClient : IDisposable
{
// Redis连接(单例,避免频繁创建)
private readonly IConnectionMultiplexer _redisConn;
private readonly IDatabase _redisDb;
// Redis队列Key(可配置)
private readonly string _queueKey;
// 死信队列Key
private readonly string _deadLetterKey;
// 分布式锁前缀
private const string _lockPrefix = "redis_queue_lock_";
// 锁超时时间(避免死锁)
private readonly TimeSpan _lockTimeout = TimeSpan.FromSeconds(30);
/// <summary>
/// 初始化Redis队列客户端
/// </summary>
/// <param name="redisConnStr">Redis连接字符串(如:127.0.0.1:6379,password=123456)</param>
/// <param name="queueName">队列名称(区分不同业务队列)</param>
/// <param name="dbNum">Redis数据库编号</param>
public RedisQueueClient(string redisConnStr, string queueName, int dbNum = 0)
{
_redisConn = ConnectionMultiplexer.Connect(redisConnStr);
_redisDb = _redisConn.GetDatabase(dbNum);
_queueKey = $"redis_queue:{queueName}";
_deadLetterKey = $"redis_queue:dead_letter:{queueName}";
}
/// <summary>
/// 入队(LPUSH)
/// </summary>
public void Enqueue(GenericRedisQueueTask task)
{
if (task == null) throw new ArgumentNullException(nameof(task));
_redisDb.ListLeftPush(_queueKey, task.ToJson());
Console.WriteLine($"[Redis队列] 任务{task.TaskId}({task.TaskName})已入队");
}
/// <summary>
/// 阻塞出队(BRPOP,避免空轮询)
/// </summary>
/// <param name="timeout">阻塞超时时间(秒)</param>
/// <returns>任务对象(null=超时)</returns>
public GenericRedisQueueTask Dequeue(int timeout = 5)
{
var redisValue = _redisDb.ListRightPop(
key: (RedisKey)_queueKey,
flags: CommandFlags.None
);
if (redisValue.IsNull) return null;
var task = GenericRedisQueueTask.FromJson(redisValue);
Console.WriteLine($"[Redis队列] 任务{task.TaskId}已出队");
return task;
}
/// <summary>
/// 获取分布式锁(避免重复消费)
/// </summary>
public bool AcquireLock(string taskId)
{
string lockKey = $"{_lockPrefix}{taskId}";
// SET NX EX:不存在则设置,带过期时间
return _redisDb.StringSet(lockKey, Thread.CurrentThread.ManagedThreadId.ToString(), _lockTimeout, When.NotExists);
}
/// <summary>
/// 释放分布式锁
/// </summary>
public void ReleaseLock(string taskId)
{
string lockKey = $"{_lockPrefix}{taskId}";
_redisDb.KeyDelete(lockKey);
}
/// <summary>
/// 任务重新入队(重试)
/// </summary>
public void ReEnqueue(GenericRedisQueueTask task)
{
task.RetriedCount++;
Enqueue(task);
Console.WriteLine($"[Redis队列] 任务{task.TaskId}重新入队(已重试{task.RetriedCount}/{task.MaxRetryCount}次)");
}
/// <summary>
/// 移入死信队列
/// </summary>
public void MoveToDeadLetter(GenericRedisQueueTask task, string errorMsg)
{
var deadLetterTask = new
{
Task = task,
ErrorMsg = errorMsg,
DeadLetterTime = DateTime.Now
};
_redisDb.ListLeftPush(_deadLetterKey, JsonConvert.SerializeObject(deadLetterTask));
Console.WriteLine($"[Redis队列] 任务{task.TaskId}移入死信队列:{errorMsg}");
}
/// <summary>
/// 获取队列长度
/// </summary>
public long GetQueueLength() => _redisDb.ListLength(_queueKey);
/// <summary>
/// 获取死信队列长度
/// </summary>
public long GetDeadLetterLength() => _redisDb.ListLength(_deadLetterKey);
public void Dispose()
{
_redisConn.Close();
_redisConn.Dispose();
}
}
csharp
/// <summary>
/// 通用Redis队列任务模型(支持JSON序列化)
/// 注意:ExecuteAsync无法直接序列化,需通过TaskType+TaskParam间接执行(解耦核心)
/// </summary>
public class GenericRedisQueueTask
{
/// <summary>
/// 任务唯一标识(Redis中唯一)
/// </summary>
public string TaskId { get; set; } = Guid.NewGuid().ToString("N");
/// <summary>
/// 任务名称(便于排查)
/// </summary>
public string TaskName { get; set; }
/// <summary>
/// 业务任务类型(如:OrderDbTask/PayHttpTask),业务侧根据此类型找到对应的执行逻辑
/// (核心:替代直接序列化ExecuteAsync,解决委托无法序列化的问题)
/// </summary>
public string TaskType { get; set; }
/// <summary>
/// 业务任务参数(JSON字符串,业务侧反序列化后执行)
/// </summary>
public string TaskParams { get; set; }
/// <summary>
/// 最大重试次数(业务自定义)
/// </summary>
public int MaxRetryCount { get; set; } = 3;
/// <summary>
/// 已重试次数
/// </summary>
public int RetriedCount { get; set; } = 0;
/// <summary>
/// 任务入队时间
/// </summary>
public DateTime EnqueueTime { get; set; } = DateTime.Now;
/// <summary>
/// 序列化为JSON(Redis存储用)
/// </summary>
public string ToJson() => JsonConvert.SerializeObject(this);
/// <summary>
/// 从JSON反序列化
/// </summary>
public static GenericRedisQueueTask FromJson(string json) => JsonConvert.DeserializeObject<GenericRedisQueueTask>(json);
}
csharp
/// <summary>
/// Redis通用队列处理器(只做调度,不耦合业务)
/// </summary>
public class RedisQueueProcessor : IDisposable
{
private readonly RedisQueueClient _redisClient;
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
private readonly int _consumerCount;
private readonly Task[] _consumerTasks;
// 业务侧注册的任务执行器(根据TaskType找到对应的执行逻辑)
private readonly Dictionary<string, Func<string, Task>> _taskExecutors = new Dictionary<string, Func<string, Task>>();
/// <summary>
/// 队列统计信息
/// </summary>
public QueueStatistics Statistics { get; } = new QueueStatistics();
/// <summary>
/// 初始化Redis队列处理器
/// </summary>
public RedisQueueProcessor(RedisQueueClient redisClient, int consumerCount = 4)
{
_redisClient = redisClient ?? throw new ArgumentNullException(nameof(redisClient));
if (consumerCount < 1)
throw new ArgumentOutOfRangeException(nameof(consumerCount), "消费线程数不能小于1");
_consumerCount = consumerCount;
_consumerTasks = new Task[consumerCount];
}
/// <summary>
/// 业务侧注册任务执行器(核心:解耦任务类型和执行逻辑)
/// </summary>
/// <param name="taskType">任务类型(如:OrderDbTask)</param>
/// <param name="executor">执行逻辑(入参:TaskParams,异步执行)</param>
public void RegisterTaskExecutor(string taskType, Func<string, Task> executor)
{
if (string.IsNullOrEmpty(taskType)) throw new ArgumentNullException(nameof(taskType));
if (executor == null) throw new ArgumentNullException(nameof(executor));
_taskExecutors[taskType] = executor;
Console.WriteLine($"[队列] 注册任务执行器:{taskType}");
}
/// <summary>
/// 启动消费线程
/// </summary>
public void StartConsuming()
{
Console.WriteLine($"[Redis队列] 启动{_consumerCount}个消费线程...");
for (int i = 0; i < _consumerCount; i++)
{
int consumerId = i + 1;
_consumerTasks[i] = Task.Run(() => ConsumeLoopAsync(consumerId, _cts.Token), _cts.Token);
}
}
/// <summary>
/// 消费循环(核心:出队→加锁→执行业务→释放锁)
/// </summary>
private async Task ConsumeLoopAsync(int consumerId, CancellationToken token)
{
Console.WriteLine($"[Redis队列-消费者{consumerId}] 已启动");
while (!token.IsCancellationRequested)
{
try
{
// 1. 阻塞出队(避免空轮询)
var task = _redisClient.Dequeue(5);
if (task == null) continue;
Statistics.TotalDequeued++;
Statistics.QueueLength = (int)_redisClient.GetQueueLength();
// 2. 获取分布式锁(避免多进程重复消费)
if (!_redisClient.AcquireLock(task.TaskId))
{
Console.WriteLine($"[Redis队列-消费者{consumerId}] 任务{task.TaskId}已被其他消费者锁定,跳过");
continue;
}
try
{
// 3. 执行业务逻辑(队列只调度,不关心内部容错)
await ExecuteBusinessTaskAsync(task, consumerId);
}
finally
{
// 4. 释放锁(无论成功失败都释放)
_redisClient.ReleaseLock(task.TaskId);
Console.WriteLine($"[Redis队列-消费者{consumerId}] 任务{task.TaskId}锁已释放");
}
}
catch (OperationCanceledException)
{
Console.WriteLine($"[Redis队列-消费者{consumerId}] 收到停止信号,退出");
break;
}
catch (Exception ex)
{
Console.WriteLine($"[Redis队列-消费者{consumerId}] 消费循环异常:{ex.Message}");
await Task.Delay(500, token);
}
}
Console.WriteLine($"[Redis队列-消费者{consumerId}] 已退出");
}
/// <summary>
/// 执行业务任务(队列仅调度,容错由业务侧实现)
/// </summary>
private async Task ExecuteBusinessTaskAsync(GenericRedisQueueTask task, int consumerId)
{
try
{
Console.WriteLine($"[Redis队列-消费者{consumerId}] 开始执行任务{task.TaskId}({task.TaskName})");
// 查找业务侧注册的执行器
if (!_taskExecutors.TryGetValue(task.TaskType, out var executor))
{
throw new KeyNotFoundException($"未找到任务类型{task.TaskType}的执行器");
}
// 执行业务逻辑(业务侧已封装重试/熔断等容错)
await executor(task.TaskParams);
// 执行成功
Statistics.TotalSucceeded++;
Console.WriteLine($"[Redis队列-消费者{consumerId}] 任务{task.TaskId}执行成功");
}
catch (Exception ex)
{
// 执行失败
Statistics.TotalFailed++;
Console.WriteLine($"[Redis队列-消费者{consumerId}] 任务{task.TaskId}执行失败:{ex.Message}");
// 判断是否重试
if (task.RetriedCount < task.MaxRetryCount)
{
_redisClient.ReEnqueue(task);
Statistics.TotalRetried++;
}
else
{
// 移入死信队列
_redisClient.MoveToDeadLetter(task, ex.Message);
Statistics.TotalDeadLetter++;
// 触发死信事件(业务侧订阅处理)
OnTaskDeadLetter?.Invoke(this, new DeadLetterEventArgs(task, ex));
}
}
}
/// <summary>
/// 优雅停止消费
/// </summary>
public bool StopConsuming(TimeSpan timeout)
{
try
{
_cts.Cancel();
bool allStopped = Task.WaitAll(_consumerTasks, timeout);
Console.WriteLine($"[Redis队列] 消费线程停止{(allStopped ? "成功" : "超时")},队列剩余任务数:{_redisClient.GetQueueLength()}");
return allStopped;
}
catch (AggregateException ex)
{
ex.Handle(e => e is OperationCanceledException);
return true;
}
}
/// <summary>
/// 死信任务事件(业务侧订阅)
/// </summary>
public event EventHandler<DeadLetterEventArgs> OnTaskDeadLetter;
public void Dispose()
{
_cts.Cancel();
_cts.Dispose();
_redisClient.Dispose();
}
}
/// <summary>
/// 队列统计信息
/// </summary>
public class QueueStatistics
{
public int TotalDequeued { get; set; }
public int TotalSucceeded { get; set; }
public int TotalFailed { get; set; }
public int TotalRetried { get; set; }
public int TotalDeadLetter { get; set; }
public int QueueLength { get; set; }
}
/// <summary>
/// 死信事件参数
/// </summary>
public class DeadLetterEventArgs : EventArgs
{
public GenericRedisQueueTask Task { get; }
public Exception Exception { get; }
public DeadLetterEventArgs(GenericRedisQueueTask task, Exception exception)
{
Task = task;
Exception = exception;
}
}