c# 生产者消费者模式之内存/redis队列实现

简介

1. 引言

在现代应用开发中,队列系统是处理异步任务、解耦系统组件的关键工具。本文将介绍一个基于 C# 实现的通用队列处理系统,支持内存队列和 Redis 队列两种模式,适用于各种后台任务处理场景。

2. 系统架构

2.1 核心组件

本项目包含以下核心组件:

组件 职责 文件 任务模型 定义任务结构和属性 GenericQueueTask.cs 、 GenericRedisQueueTask.cs 队列处理器 管理任务调度和执行 GenericQueueProcessor.cs 、 RedisQueueProcessor.cs Redis 客户端 封装 Redis 操作 RedisQueueClient.cs 示例代码 展示系统使用方法 Program.cs

2.2 设计理念

系统设计遵循以下核心原则:

  • 解耦 :业务逻辑与队列系统分离
  • 可靠性 :支持重试、死信队列、分布式锁
  • 扩展性 :支持多种任务类型和执行逻辑
  • 监控 :提供详细的统计信息和日志

4 队列处理器

内存队列处理流程

  1. 任务入队 :将任务添加到 ConcurrentQueue
  2. 多线程消费 :启动多个线程并行处理任务
  3. 执行任务 :调用任务的 ExecuteAsync 方法
  4. 异常处理 :捕获执行异常,判断是否需要重试
  5. 死信处理 :超过重试次数的任务触发死信事件

Redis 队列处理流程

  1. 任务入队 :将任务序列化后存入 Redis 列表
  2. 阻塞出队 :使用 BRPOP 命令阻塞获取任务
  3. 分布式锁 :使用 Redis 实现分布式锁,避免重复消费
  4. 执行任务 :根据任务类型调用对应的执行器
  5. 死信处理 :失败任务移入 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;
    }
}
相关推荐
ABin-阿斌2 小时前
通过 Redisson防止数据重复创建
redis
虹科网络安全2 小时前
艾体宝新闻 | Redis 月度更新速览:2025 年 12 月
数据库·redis·缓存
kylezhao20193 小时前
C# 中实现自定义的窗口最大化、最小化和关闭按钮
开发语言·c#
月巴月巴白勺合鸟月半5 小时前
PDF转图片的另外一种方法
pdf·c#
m5655bj5 小时前
使用 C# 对比两个 PDF 文档的差异
pdf·c#·visual studio
Never_Satisfied5 小时前
C#插值字符串中大括号表示方法
前端·c#
七七七七076 小时前
【Redis】Ubuntu22.04安装redis++
数据库·redis·缓存
J_liaty6 小时前
Spring Security整合JWT与Redis实现权限认证
java·redis·spring·spring-security
什么都不会的Tristan6 小时前
redis-原理篇-Dict
数据库·redis·缓存